chore: 添加 dev 环境测试数据 seed 脚本

This commit is contained in:
zetaloop
2026-04-24 05:22:26 +08:00
parent 790f145dd1
commit 37faf1c920
+370
View File
@@ -0,0 +1,370 @@
#!/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 user_id BETWEEN 100001 AND 109999;
DELETE FROM post_likes WHERE user_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 shop_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 (shop_id, player_id, is_primary) VALUES
(100001, 100001, true),
(100001, 100002, false),
(100002, 100003, true),
(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 (post_id, user_id) VALUES
(100001, 100002),(100001, 100003),(100001, 100006),(100001, 100007),
(100002, 100001),(100002, 100006),
(100003, 100001),(100003, 100002),
(100004, 100001),(100004, 100004),
(100005, 100001),(100005, 100003),(100005, 100006),(100005, 100007);
""")
# ── Comment likes ──────────────────────────────────────────────
psql("""
INSERT INTO comment_likes (comment_id, user_id) VALUES
(100001, 100001),(100001, 100006),(100001, 100007),
(100003, 100001),(100003, 100006),
(100006, 100001),(100006, 100004),
(100009, 100001),(100009, 100007),
(100011, 100002),(100011, 100006),(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()