371 lines
21 KiB
Python
371 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Dev seed script — populates the local database with test data.
|
||
|
||
Usage:
|
||
python seed.py # insert seed data (skips if already seeded)
|
||
python seed.py --reset # delete seed data first, then re-insert
|
||
|
||
Requires: docker CLI accessible, juwan-postgres container running.
|
||
All IDs use a fixed range (100_001 – 109_999) to avoid collision with
|
||
snowflake-generated production IDs.
|
||
"""
|
||
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
|
||
CONTAINER = "juwan-postgres"
|
||
DB_USER = "postgres"
|
||
DB_NAME = "app"
|
||
|
||
# All seed users share this password: test1234
|
||
# bcrypt hash generated via pgcrypto crypt()
|
||
PASSWORD = "test1234"
|
||
|
||
|
||
def psql(sql: str) -> str:
|
||
r = subprocess.run(
|
||
["docker", "exec", "-i", CONTAINER, "psql", "-U", DB_USER, "-d", DB_NAME,
|
||
"-v", "ON_ERROR_STOP=1", "--no-psqlrc", "-t", "-A"],
|
||
input=sql, capture_output=True, text=True, timeout=30,
|
||
)
|
||
if r.returncode != 0:
|
||
print(f"psql error:\n{r.stderr}", file=sys.stderr)
|
||
sys.exit(1)
|
||
return r.stdout.strip()
|
||
|
||
|
||
def already_seeded() -> bool:
|
||
cnt = psql("SELECT count(*) FROM games WHERE id BETWEEN 100001 AND 109999;")
|
||
return int(cnt) > 0
|
||
|
||
|
||
def reset():
|
||
print("Resetting seed data …")
|
||
psql("""
|
||
DELETE FROM wallet_transactions WHERE user_id BETWEEN 100001 AND 109999;
|
||
DELETE FROM wallets WHERE user_id BETWEEN 100001 AND 109999;
|
||
DELETE FROM comment_likes WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM post_likes WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM comments WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM posts WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM order_state_logs WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM orders WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM shop_invitations WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM shop_players WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM player_services WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM shops WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM players WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM favorites WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM user_follows WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM user_verifications WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM user_preferences WHERE user_id BETWEEN 100001 AND 109999;
|
||
DELETE FROM users WHERE id BETWEEN 100001 AND 109999;
|
||
DELETE FROM games WHERE id BETWEEN 100001 AND 109999;
|
||
""")
|
||
|
||
|
||
def seed():
|
||
print("Seeding …")
|
||
|
||
# ── Games ──────────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO games (id, name, icon, category, sort_order) VALUES
|
||
(100001, '英雄联盟', '🎮', 'MOBA', 1),
|
||
(100002, '王者荣耀', '👑', 'MOBA', 2),
|
||
(100003, 'VALORANT', '🔫', 'FPS', 3),
|
||
(100004, '永劫无间', '⚔️', 'ACT', 4),
|
||
(100005, '原神', '🌟', 'RPG', 5),
|
||
(100006, 'CS2', '💣', 'FPS', 6),
|
||
(100007, '绝地求生', '🪖', 'FPS', 7),
|
||
(100008, '和平精英', '🎯', 'FPS', 8);
|
||
""")
|
||
|
||
# ── Users ──────────────────────────────────────────────────────
|
||
# password_hash via pgcrypto: crypt('test1234', gen_salt('bf'))
|
||
psql(f"""
|
||
INSERT INTO users (id, username, password_hash, email, nickname, avatar, bio, "current_role", verified_roles, verification_status) VALUES
|
||
(100001, 'player_lux',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'lux@test.local', '光辉女郎', '', '王者段位代练,擅长中单法师',
|
||
'player', ARRAY['consumer','player'],
|
||
'{{"consumer":"approved","player":"approved"}}'::jsonb),
|
||
|
||
(100002, 'player_yasuo',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'yasuo@test.local', '疾风剑豪', '', '国服亚索,上分快准狠',
|
||
'player', ARRAY['consumer','player'],
|
||
'{{"consumer":"approved","player":"approved"}}'::jsonb),
|
||
|
||
(100003, 'player_jett',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'jett@test.local', '飞刀小姐', '', 'VALORANT 不朽段位',
|
||
'player', ARRAY['consumer','player'],
|
||
'{{"consumer":"approved","player":"approved"}}'::jsonb),
|
||
|
||
(100004, 'owner_star',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'star@test.local', '星辰工作室', '', '专业代练工作室,诚信经营',
|
||
'owner', ARRAY['consumer','owner'],
|
||
'{{"consumer":"approved","owner":"approved"}}'::jsonb),
|
||
|
||
(100005, 'owner_wolf',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'wolf@test.local', '狼群电竞', '', '高端局代练团队',
|
||
'owner', ARRAY['consumer','owner'],
|
||
'{{"consumer":"approved","owner":"approved"}}'::jsonb),
|
||
|
||
(100006, 'consumer_test',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'consumer@test.local', '普通玩家小明', '', '想上钻石',
|
||
'consumer', ARRAY['consumer'],
|
||
'{{"consumer":"approved"}}'::jsonb),
|
||
|
||
(100007, 'consumer_test2',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'consumer2@test.local', '快乐玩家小红', '', '想找人带上分',
|
||
'consumer', ARRAY['consumer'],
|
||
'{{"consumer":"approved"}}'::jsonb),
|
||
|
||
(100008, 'player_owner_duo',
|
||
crypt('{PASSWORD}', gen_salt('bf')),
|
||
'duo@test.local', '全能选手', '', '既是打手也是店主',
|
||
'player', ARRAY['consumer','player','owner'],
|
||
'{{"consumer":"approved","player":"approved","owner":"approved"}}'::jsonb);
|
||
""")
|
||
|
||
# ── User preferences ───────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO user_preferences (user_id) VALUES
|
||
(100001),(100002),(100003),(100004),(100005),(100006),(100007),(100008);
|
||
""")
|
||
|
||
# ── User verifications ─────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO user_verifications (id, user_id, role, status, materials, reviewed_by, reviewed_at) VALUES
|
||
(100001, 100001, 'player', 'approved', '{"id_card":"mock://id1.jpg","rank_screenshot":"mock://rank1.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100002, 100002, 'player', 'approved', '{"id_card":"mock://id2.jpg","rank_screenshot":"mock://rank2.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100003, 100003, 'player', 'approved', '{"id_card":"mock://id3.jpg","rank_screenshot":"mock://rank3.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100004, 100004, 'owner', 'approved', '{"business_license":"mock://biz1.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100005, 100005, 'owner', 'approved', '{"business_license":"mock://biz2.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100006, 100008, 'player', 'approved', '{"id_card":"mock://id8.jpg","rank_screenshot":"mock://rank8.jpg"}'::jsonb, 702627789228081152, NOW()),
|
||
(100007, 100008, 'owner', 'approved', '{"business_license":"mock://biz8.jpg"}'::jsonb, 702627789228081152, NOW());
|
||
""")
|
||
|
||
# ── Players ────────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO players (id, user_id, status, rating, total_orders, completed_orders, gender, tags, games) VALUES
|
||
(100001, 100001, 'available', 4.85, 128, 120, false, ARRAY['中单','法师','上分快'], ARRAY[100001,100002]::bigint[]),
|
||
(100002, 100002, 'available', 4.72, 95, 88, true, ARRAY['上单','刺客','高端局'], ARRAY[100001]::bigint[]),
|
||
(100003, 100003, 'busy', 4.90, 67, 65, false, ARRAY['决斗','不朽段位'], ARRAY[100003]::bigint[]),
|
||
(100004, 100008, 'available', 4.60, 42, 38, true, ARRAY['全能','多游戏'], ARRAY[100001,100003,100005]::bigint[]);
|
||
""")
|
||
|
||
# ── Shops ──────────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO shops (id, owner_id, name, description, rating, total_orders, player_count, commission_type, commission_value, dispatch_mode, announcements) VALUES
|
||
(100001, 100004, '星辰代练工作室', '专业LOL/王者代练,7天无理由退款', 4.80, 256, 2, 'percentage', 15.00, 'manual',
|
||
ARRAY['新店开业,首单九折!','招募高端局打手,待遇从优']),
|
||
(100002, 100005, '狼群电竞俱乐部', '高端局代练团队,大师以上段位保证', 4.65, 180, 1, 'percentage', 12.00, 'auto',
|
||
ARRAY['本周特惠:钻石到大师只需199']),
|
||
(100003, 100008, '全能工作室', '多游戏代练,总有一款适合你', 4.50, 80, 1, 'fixed', 10.00, 'manual',
|
||
ARRAY['支持LOL/VALORANT/原神']);
|
||
""")
|
||
|
||
# ── Shop players ───────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO shop_players (id, shop_id, player_id, is_primary) VALUES
|
||
(109001, 100001, 100001, true),
|
||
(109002, 100001, 100002, false),
|
||
(109003, 100002, 100003, true),
|
||
(109004, 100003, 100004, true);
|
||
""")
|
||
|
||
# Update players.shop_id cache
|
||
psql("""
|
||
UPDATE players SET shop_id = 100001 WHERE id = 100001;
|
||
UPDATE players SET shop_id = 100001 WHERE id = 100002;
|
||
UPDATE players SET shop_id = 100002 WHERE id = 100003;
|
||
UPDATE players SET shop_id = 100003 WHERE id = 100004;
|
||
""")
|
||
|
||
# ── Player services ────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO player_services (id, player_id, game_id, title, description, price, unit, rank_range, availability) VALUES
|
||
(100001, 100001, 100001, 'LOL 钻石上分', '钻石到大师,稳定上分', 99.00, '段', '钻石→大师', ARRAY['周一至周五','晚间']),
|
||
(100002, 100001, 100001, 'LOL 排位陪玩', '钻石段位陪玩,轻松愉快', 30.00, '局', '钻石', ARRAY['全天']),
|
||
(100003, 100001, 100002, '王者荣耀 星耀上分', '星耀到王者,快速安全', 68.00, '段', '星耀→王者', ARRAY['全天']),
|
||
(100004, 100002, 100001, 'LOL 大师冲击', '大师到宗师,高端局专精', 199.00, '段', '大师→宗师', ARRAY['周末','晚间']),
|
||
(100005, 100002, 100001, 'LOL 定位赛代打', '10局定位赛,保底铂金', 50.00, '10局','定位赛', ARRAY['全天']),
|
||
(100006, 100003, 100003, 'VALORANT 不朽上分', '永恒到不朽,决斗位专精', 150.00, '段', '永恒→不朽', ARRAY['晚间','周末']),
|
||
(100007, 100003, 100003, 'VALORANT 陪玩', '不朽段位陪玩,教学向', 40.00, '局', '不朽', ARRAY['全天']),
|
||
(100008, 100004, 100001, 'LOL 黄金上分', '黄金到铂金,新手友好', 39.00, '段', '黄金→铂金', ARRAY['全天']),
|
||
(100009, 100004, 100003, 'VALORANT 白银上分', '白银到黄金,基础教学', 45.00, '段', '白银→黄金', ARRAY['全天']),
|
||
(100010, 100004, 100005, '原神深渊代打', '深渊12层满星', 80.00, '次', '深渊12层', ARRAY['全天']);
|
||
""")
|
||
|
||
# ── Wallets ────────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO wallets (user_id, balance, frozen_balance) VALUES
|
||
(100001, 2580.00, 0.00),
|
||
(100002, 1890.00, 0.00),
|
||
(100003, 3200.00, 0.00),
|
||
(100004, 5600.00, 0.00),
|
||
(100005, 4200.00, 0.00),
|
||
(100006, 500.00, 0.00),
|
||
(100007, 300.00, 0.00),
|
||
(100008, 1500.00, 0.00);
|
||
""")
|
||
|
||
# ── Orders ─────────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO orders (id, consumer_id, player_id, shop_id, service_snapshot, status, total_price, note) VALUES
|
||
(100001, 100006, 100001, 100001,
|
||
'{"serviceId":100001,"title":"LOL 钻石上分","price":99.00,"unit":"段","gameName":"英雄联盟"}'::jsonb,
|
||
'completed', 99.00, '希望快点上分'),
|
||
(100002, 100006, 100002, 100001,
|
||
'{"serviceId":100004,"title":"LOL 大师冲击","price":199.00,"unit":"段","gameName":"英雄联盟"}'::jsonb,
|
||
'in_progress', 199.00, '大师冲宗师'),
|
||
(100003, 100007, 100003, 100002,
|
||
'{"serviceId":100006,"title":"VALORANT 不朽上分","price":150.00,"unit":"段","gameName":"VALORANT"}'::jsonb,
|
||
'pending_accept', 150.00, null),
|
||
(100004, 100007, 100001, 100001,
|
||
'{"serviceId":100002,"title":"LOL 排位陪玩","price":30.00,"unit":"局","gameName":"英雄联盟"}'::jsonb,
|
||
'completed', 30.00, '陪玩两局'),
|
||
(100005, 100006, 100004, 100003,
|
||
'{"serviceId":100010,"title":"原神深渊代打","price":80.00,"unit":"次","gameName":"原神"}'::jsonb,
|
||
'pending_payment', 80.00, '深渊满星');
|
||
""")
|
||
|
||
# ── Order state logs ───────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO order_state_logs (id, order_id, from_status, to_status, action, actor_id, actor_role) VALUES
|
||
(100001, 100001, null, 'pending_payment', 'create', 100006, 'consumer'),
|
||
(100002, 100001, 'pending_payment', 'pending_accept', 'pay', 100006, 'consumer'),
|
||
(100003, 100001, 'pending_accept', 'in_progress', 'accept', 100001, 'player'),
|
||
(100004, 100001, 'in_progress', 'pending_close', 'finish', 100001, 'player'),
|
||
(100005, 100001, 'pending_close', 'completed', 'confirm', 100006, 'consumer'),
|
||
(100006, 100002, null, 'pending_payment', 'create', 100006, 'consumer'),
|
||
(100007, 100002, 'pending_payment', 'pending_accept', 'pay', 100006, 'consumer'),
|
||
(100008, 100002, 'pending_accept', 'in_progress', 'accept', 100002, 'player'),
|
||
(100009, 100003, null, 'pending_payment', 'create', 100007, 'consumer'),
|
||
(100010, 100003, 'pending_payment', 'pending_accept', 'pay', 100007, 'consumer'),
|
||
(100011, 100004, null, 'pending_payment', 'create', 100007, 'consumer'),
|
||
(100012, 100004, 'pending_payment', 'pending_accept', 'pay', 100007, 'consumer'),
|
||
(100013, 100004, 'pending_accept', 'in_progress', 'accept', 100001, 'player'),
|
||
(100014, 100004, 'in_progress', 'pending_close', 'finish', 100001, 'player'),
|
||
(100015, 100004, 'pending_close', 'completed', 'confirm', 100007, 'consumer'),
|
||
(100016, 100005, null, 'pending_payment', 'create', 100006, 'consumer');
|
||
""")
|
||
|
||
# ── Wallet transactions ────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO wallet_transactions (id, user_id, type, amount, balance_after, description, order_id) VALUES
|
||
(100001, 100006, 'topup', 500.00, 500.00, '充值', null),
|
||
(100002, 100006, 'payment', -99.00, 401.00, 'LOL 钻石上分', 100001),
|
||
(100003, 100001, 'income', 84.15, 2580.00, 'LOL 钻石上分(扣佣)', 100001),
|
||
(100004, 100006, 'payment', -199.00, 202.00, 'LOL 大师冲击', 100002),
|
||
(100005, 100007, 'topup', 300.00, 300.00, '充值', null),
|
||
(100006, 100007, 'payment', -30.00, 270.00, 'LOL 排位陪玩', 100004),
|
||
(100007, 100001, 'income', 25.50, 2605.50, 'LOL 排位陪玩(扣佣)', 100004);
|
||
""")
|
||
|
||
# ── Community posts ────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO posts (id, author_id, author_role, title, content, tags, like_count, comment_count) VALUES
|
||
(100001, 100001, 'player', '新赛季中单法师强度排行',
|
||
'新赛季中单法师的强度有了不少变化,这里给大家分析一下当前版本的T0-T2梯队。\n\nT0: 阿狸、辛德拉\nT1: 维克托、泽丽\nT2: 佐伊、妮蔻\n\n大家觉得还有哪些英雄被低估了?',
|
||
ARRAY['英雄联盟','攻略','中单'], 12, 3),
|
||
(100002, 100003, 'player', 'VALORANT 新地图攻略分享',
|
||
'新地图的几个关键烟点和架枪位分享给大家。决斗位在这张图上优势很大,特别是A点的peek角度。',
|
||
ARRAY['VALORANT','攻略','地图'], 8, 1),
|
||
(100003, 100004, 'owner', '星辰工作室招募公告',
|
||
'星辰代练工作室现招募以下段位打手:\n- LOL 大师及以上\n- 王者荣耀 王者50星以上\n\n待遇优厚,佣金比例可谈。有意者私信联系。',
|
||
ARRAY['招募','工作室'], 5, 2),
|
||
(100004, 100006, 'consumer', '求推荐靠谱的LOL代练',
|
||
'钻石卡了好久上不去,想找个靠谱的代练帮忙上大师。有推荐的吗?最好是有保障的工作室。',
|
||
ARRAY['英雄联盟','求推荐'], 3, 2),
|
||
(100005, 100002, 'player', '上分心得:如何从钻石到大师',
|
||
'很多人卡在钻石上不去,其实关键在于几个点:\n1. 英雄池不要太广,精通2-3个就够\n2. 对线期不要浪,稳住发育\n3. 多看小地图,把握团战时机\n\n希望对大家有帮助!',
|
||
ARRAY['英雄联盟','心得','上分'], 15, 4);
|
||
""")
|
||
|
||
# ── Comments ───────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO comments (id, post_id, author_id, content, like_count) VALUES
|
||
(100001, 100001, 100002, '阿狸确实强,ban率太高了', 3),
|
||
(100002, 100001, 100006, '维克托感觉也是T0级别的', 1),
|
||
(100003, 100001, 100003, '法师版本确实舒服', 2),
|
||
(100004, 100002, 100001, '新地图确实好玩,决斗位优势大', 1),
|
||
(100005, 100003, 100001, '有兴趣,已私信', 0),
|
||
(100006, 100003, 100002, '星辰工作室不错,之前合作过', 2),
|
||
(100007, 100004, 100001, '可以看看我的主页,钻石到大师是我的强项', 1),
|
||
(100008, 100004, 100004, '欢迎来星辰工作室了解,我们有专业的代练团队', 0),
|
||
(100009, 100005, 100006, '写得好,学到了', 2),
|
||
(100010, 100005, 100007, '确实,英雄池精了比广好', 1),
|
||
(100011, 100005, 100001, '补充一点:心态也很重要,连跪就休息', 3),
|
||
(100012, 100005, 100003, '赞同,我也是靠精通少数英雄上的不朽', 1);
|
||
""")
|
||
|
||
# ── Post likes ─────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO post_likes (id, post_id, user_id) VALUES
|
||
(109101, 100001, 100002),(109102, 100001, 100003),(109103, 100001, 100006),(109104, 100001, 100007),
|
||
(109105, 100002, 100001),(109106, 100002, 100006),
|
||
(109107, 100003, 100001),(109108, 100003, 100002),
|
||
(109109, 100004, 100001),(109110, 100004, 100004),
|
||
(109111, 100005, 100001),(109112, 100005, 100003),(109113, 100005, 100006),(109114, 100005, 100007);
|
||
""")
|
||
|
||
# ── Comment likes ──────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO comment_likes (id, comment_id, user_id) VALUES
|
||
(109201, 100001, 100001),(109202, 100001, 100006),(109203, 100001, 100007),
|
||
(109204, 100003, 100001),(109205, 100003, 100006),
|
||
(109206, 100006, 100001),(109207, 100006, 100004),
|
||
(109208, 100009, 100001),(109209, 100009, 100007),
|
||
(109210, 100011, 100002),(109211, 100011, 100006),(109212, 100011, 100007);
|
||
""")
|
||
|
||
# ── User follows ───────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO user_follows (id, follower_id, followee_id) VALUES
|
||
(100001, 100006, 100001),
|
||
(100002, 100006, 100002),
|
||
(100003, 100007, 100003),
|
||
(100004, 100007, 100001),
|
||
(100005, 100001, 100002),
|
||
(100006, 100002, 100001);
|
||
""")
|
||
|
||
# ── Favorites ──────────────────────────────────────────────────
|
||
psql("""
|
||
INSERT INTO favorites (id, user_id, target_type, target_id) VALUES
|
||
(100001, 100006, 'player', 100001),
|
||
(100002, 100006, 'shop', 100001),
|
||
(100003, 100007, 'player', 100003),
|
||
(100004, 100007, 'shop', 100002);
|
||
""")
|
||
|
||
print("Done! Seed data inserted.")
|
||
print()
|
||
print("Test accounts (password: test1234):")
|
||
print(" player_lux — 打手,LOL/王者,星辰工作室成员")
|
||
print(" player_yasuo — 打手,LOL高端局,星辰工作室成员")
|
||
print(" player_jett — 打手,VALORANT,狼群俱乐部成员")
|
||
print(" owner_star — 店主,星辰代练工作室")
|
||
print(" owner_wolf — 店主,狼群电竞俱乐部")
|
||
print(" consumer_test — 普通消费者")
|
||
print(" consumer_test2 — 普通消费者")
|
||
print(" player_owner_duo — 打手+店主,全能工作室")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
if "--reset" in sys.argv:
|
||
reset()
|
||
elif already_seeded():
|
||
print("Seed data already exists. Use --reset to re-seed.")
|
||
sys.exit(0)
|
||
seed()
|