Skip to main content

max / makenotwork

7.5 KB · 248 lines History Blame Raw
1 //! Seed initial forum data for development. Run with `--seed` flag.
2 //!
3 //! The orchestration lives here; the rows it writes are in `rows`, the people
4 //! who write them in `users`, and the forum content itself in `content`, which
5 //! is data rather than code and was most of this file's size.
6
7 mod content;
8 mod rows;
9 mod users;
10
11 use content::{seed_music_general, seed_music_mixing, seed_music_sound_design};
12 use rows::{
13 seed_category, seed_community, seed_membership, seed_membership_upsert, seed_post, seed_thread,
14 };
15 use users::{seed_harness_users, seed_users};
16
17 use mt_core::types::CommunityRole;
18 use sqlx::PgPool;
19
20 pub async fn run(pool: &PgPool) {
21 // Guard: skip if data already exists (threads have no unique constraint)
22 let thread_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM threads")
23 .fetch_one(pool)
24 .await
25 .unwrap_or(0);
26 if thread_count > 0 {
27 tracing::info!("seed data already exists, skipping");
28 return;
29 }
30
31 // --- users
32
33 let users = seed_users(pool).await;
34 let harness = seed_harness_users(pool).await;
35
36 // --- communities
37
38 let rust_id = seed_community(
39 pool,
40 "Rust Programming",
41 "rust",
42 Some("All things Rust: language, ecosystem, tooling."),
43 )
44 .await;
45
46 let music_id = seed_community(
47 pool,
48 "Music Production",
49 "music",
50 Some("DAWs, plugins, mixing, mastering, sound design."),
51 )
52 .await;
53
54 let selfhosted_id = seed_community(
55 pool,
56 "Self-Hosted",
57 "selfhosted",
58 Some("Running your own infrastructure. No cloud required."),
59 )
60 .await;
61
62 // --- categories
63
64 // Rust
65 let rust_general = seed_category(
66 pool,
67 rust_id,
68 "General",
69 "general",
70 0,
71 Some("Anything that doesn't fit elsewhere."),
72 )
73 .await;
74 let rust_help = seed_category(
75 pool,
76 rust_id,
77 "Help & Questions",
78 "help",
79 1,
80 Some("Ask for help, get answers."),
81 )
82 .await;
83 let _rust_show = seed_category(
84 pool,
85 rust_id,
86 "Show & Tell",
87 "show",
88 2,
89 Some("Share what you've built."),
90 )
91 .await;
92 let _rust_meta = seed_category(
93 pool,
94 rust_id,
95 "Meta",
96 "meta",
97 3,
98 Some("Discussion about this community itself."),
99 )
100 .await;
101
102 // Music, the main test community
103 let music_general = seed_category(
104 pool,
105 music_id,
106 "General",
107 "general",
108 0,
109 Some("General music production discussion."),
110 )
111 .await;
112 let music_mixing = seed_category(
113 pool,
114 music_id,
115 "Mixing & Mastering",
116 "mixing",
117 1,
118 Some("Techniques for getting a polished mix."),
119 )
120 .await;
121 let music_sound = seed_category(
122 pool,
123 music_id,
124 "Sound Design",
125 "sound-design",
126 2,
127 Some("Synthesis, sampling, and sound creation."),
128 )
129 .await;
130
131 // Self-Hosted
132 let sh_general = seed_category(
133 pool,
134 selfhosted_id,
135 "General",
136 "general",
137 0,
138 Some("General self-hosting discussion."),
139 )
140 .await;
141 let _sh_homelab = seed_category(
142 pool,
143 selfhosted_id,
144 "Homelab",
145 "homelab",
146 1,
147 Some("Hardware, networking, and home server setups."),
148 )
149 .await;
150
151 // --- memberships
152
153 for user in &users {
154 seed_membership(pool, user.id, music_id, CommunityRole::Member).await;
155 }
156 // admin owns all, maxmj is moderator of music
157 seed_membership_upsert(pool, users[0].id, rust_id, CommunityRole::Owner).await;
158 seed_membership_upsert(pool, users[0].id, music_id, CommunityRole::Owner).await;
159 seed_membership_upsert(pool, users[0].id, selfhosted_id, CommunityRole::Owner).await;
160 seed_membership_upsert(pool, users[1].id, rust_id, CommunityRole::Member).await;
161 seed_membership_upsert(pool, users[1].id, music_id, CommunityRole::Moderator).await;
162
163 // The harness accounts get the roles the browser axis needs to write:
164 // ordinary membership for thread/reply/flag, and Owner + Moderator on two
165 // different communities so a moderation action has somewhere to land. The
166 // roles are seeded rather than granted at login because login only ever
167 // upserts identity and MNW perks, never membership.
168 for account in &harness {
169 seed_membership_upsert(pool, account.id, rust_id, account.rust_role).await;
170 seed_membership_upsert(pool, account.id, music_id, account.music_role).await;
171 seed_membership_upsert(pool, account.id, selfhosted_id, CommunityRole::Member).await;
172 }
173
174 // --- Rust community: a few threads
175
176 let welcome_id = seed_thread(
177 pool,
178 rust_general,
179 users[0].id,
180 "Welcome, read before posting",
181 true,
182 false,
183 )
184 .await;
185 seed_post(pool, welcome_id, users[0].id, "Welcome to the Rust Programming community. Please be respectful, stay on topic, and use code blocks for code snippets.").await;
186
187 let async_id = seed_thread(
188 pool,
189 rust_general,
190 users[1].id,
191 "How do I get started with async Rust?",
192 false,
193 false,
194 )
195 .await;
196 seed_post(pool, async_id, users[1].id, "I've been writing synchronous Rust for a few months and want to start using async/await. What runtime should I pick? Is tokio the only option?\n\nAny recommended tutorials or blog posts would be great.").await;
197 seed_post(pool, async_id, users[0].id, "Tokio is the most popular and what most web frameworks (Axum, Actix) use. There's also `async-std` and `smol`, but the ecosystem gravitates toward tokio.\n\nStart with the tokio tutorial: it covers spawning tasks, channels, and I/O.").await;
198
199 let error_id = seed_thread(
200 pool,
201 rust_help,
202 users[1].id,
203 "Best practices for error handling in Axum",
204 false,
205 false,
206 )
207 .await;
208 seed_post(pool, error_id, users[1].id, "What's the recommended way to handle errors in Axum handlers? Should I use `anyhow`, `thiserror`, or something else? I keep writing `.map_err(|e| ...)` everywhere.").await;
209
210 // --- Self-Hosted: a few threads
211
212 let caddy_id = seed_thread(
213 pool,
214 sh_general,
215 users[0].id,
216 "Caddy vs nginx for reverse proxy",
217 false,
218 false,
219 )
220 .await;
221 seed_post(pool, caddy_id, users[0].id, "I have been using nginx for years but Caddy's automatic HTTPS is tempting. Anyone made the switch? What are the tradeoffs?").await;
222 seed_post(pool, caddy_id, users[3].id, "Switched last year. Caddy's config is so much simpler. Automatic cert renewal is great. Only downside is slightly higher memory usage but it is negligible for small setups.").await;
223
224 // --- Music community: bulk seed
225
226 seed_music_general(pool, music_general, &users).await;
227 seed_music_mixing(pool, music_mixing, &users).await;
228 seed_music_sound_design(pool, music_sound, &users).await;
229
230 // reply_count is computed live at read time, nothing to backfill.
231
232 let total_threads: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM threads")
233 .fetch_one(pool)
234 .await
235 .unwrap_or(0);
236 let total_posts: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM posts")
237 .fetch_one(pool)
238 .await
239 .unwrap_or(0);
240
241 tracing::info!(
242 "seeded 3 communities, {} users, {} threads, {} posts",
243 users.len(),
244 total_threads,
245 total_posts
246 );
247 }
248