Skip to main content

max / makenotwork

Split seed.rs, and stop calling forum content code 1219 lines, of which roughly 900 were three inline thread tables and a 55-post list sitting inside the functions that loop over them. The orchestration was 228 lines of that file and read as a footnote to its own data. seed/mod.rs keeps `run`, so `multithreaded::seed::run` is unchanged. The row writers move to `rows`, the seeded accounts to `users`, and each category's table becomes a const in its own file under `content/`, leaving four short loops that say what they do with it. The three fixed harness UUIDs move with `users`, and they are pinned on the MNW side by a test in `server/src/seed/harness.rs`: that is the file to find before editing them. Every one of the 507 string literals is byte-identical to what was there.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01EEmeiSJnmyL98QzA5Dwsvz
Author: Max Johnson <me@maxj.phd> · 2026-09-05 02:21 UTC
Signed with PGP, not checked
Commit: 958ec41475a5aebc29cfe4a0bac73d8582571d55
Parent: 2e65304
9 files changed, +1282 insertions, -500 deletions
@@ -1,1219 +1,0 @@
1 - //! Seed initial forum data for development. Run with `--seed` flag.
2 -
3 - use mt_core::types::CommunityRole;
4 - use sqlx::PgPool;
5 - use uuid::Uuid;
6 -
7 - pub async fn run(pool: &PgPool) {
8 - // Guard: skip if data already exists (threads have no unique constraint)
9 - let thread_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM threads")
10 - .fetch_one(pool)
11 - .await
12 - .unwrap_or(0);
13 - if thread_count > 0 {
14 - tracing::info!("seed data already exists, skipping");
15 - return;
16 - }
17 -
18 - // --- users
19 -
20 - let users = seed_users(pool).await;
21 - let harness = seed_harness_users(pool).await;
22 -
23 - // --- communities
24 -
25 - let rust_id = seed_community(
26 - pool,
27 - "Rust Programming",
28 - "rust",
29 - Some("All things Rust: language, ecosystem, tooling."),
30 - )
31 - .await;
32 -
33 - let music_id = seed_community(
34 - pool,
35 - "Music Production",
36 - "music",
37 - Some("DAWs, plugins, mixing, mastering, sound design."),
38 - )
39 - .await;
40 -
41 - let selfhosted_id = seed_community(
42 - pool,
43 - "Self-Hosted",
44 - "selfhosted",
45 - Some("Running your own infrastructure. No cloud required."),
46 - )
47 - .await;
48 -
49 - // --- categories
50 -
51 - // Rust
52 - let rust_general = seed_category(
53 - pool,
54 - rust_id,
55 - "General",
56 - "general",
57 - 0,
58 - Some("Anything that doesn't fit elsewhere."),
59 - )
60 - .await;
61 - let rust_help = seed_category(
62 - pool,
63 - rust_id,
64 - "Help & Questions",
65 - "help",
66 - 1,
67 - Some("Ask for help, get answers."),
68 - )
69 - .await;
70 - let _rust_show = seed_category(
71 - pool,
72 - rust_id,
73 - "Show & Tell",
74 - "show",
75 - 2,
76 - Some("Share what you've built."),
77 - )
78 - .await;
79 - let _rust_meta = seed_category(
80 - pool,
81 - rust_id,
82 - "Meta",
83 - "meta",
84 - 3,
85 - Some("Discussion about this community itself."),
86 - )
87 - .await;
88 -
89 - // Music, the main test community
90 - let music_general = seed_category(
91 - pool,
92 - music_id,
93 - "General",
94 - "general",
95 - 0,
96 - Some("General music production discussion."),
97 - )
98 - .await;
99 - let music_mixing = seed_category(
100 - pool,
101 - music_id,
102 - "Mixing & Mastering",
103 - "mixing",
104 - 1,
105 - Some("Techniques for getting a polished mix."),
106 - )
107 - .await;
108 - let music_sound = seed_category(
109 - pool,
110 - music_id,
111 - "Sound Design",
112 - "sound-design",
113 - 2,
114 - Some("Synthesis, sampling, and sound creation."),
115 - )
116 - .await;
117 -
118 - // Self-Hosted
119 - let sh_general = seed_category(
120 - pool,
121 - selfhosted_id,
122 - "General",
123 - "general",
124 - 0,
125 - Some("General self-hosting discussion."),
126 - )
127 - .await;
128 - let _sh_homelab = seed_category(
129 - pool,
130 - selfhosted_id,
131 - "Homelab",
132 - "homelab",
133 - 1,
134 - Some("Hardware, networking, and home server setups."),
135 - )
136 - .await;
137 -
138 - // --- memberships
139 -
140 - for user in &users {
141 - seed_membership(pool, user.id, music_id, CommunityRole::Member).await;
142 - }
143 - // admin owns all, maxmj is moderator of music
144 - seed_membership_upsert(pool, users[0].id, rust_id, CommunityRole::Owner).await;
145 - seed_membership_upsert(pool, users[0].id, music_id, CommunityRole::Owner).await;
146 - seed_membership_upsert(pool, users[0].id, selfhosted_id, CommunityRole::Owner).await;
147 - seed_membership_upsert(pool, users[1].id, rust_id, CommunityRole::Member).await;
148 - seed_membership_upsert(pool, users[1].id, music_id, CommunityRole::Moderator).await;
149 -
150 - // The harness accounts get the roles the browser axis needs to write:
151 - // ordinary membership for thread/reply/flag, and Owner + Moderator on two
152 - // different communities so a moderation action has somewhere to land. The
153 - // roles are seeded rather than granted at login because login only ever
154 - // upserts identity and MNW perks, never membership.
155 - for account in &harness {
156 - seed_membership_upsert(pool, account.id, rust_id, account.rust_role).await;
157 - seed_membership_upsert(pool, account.id, music_id, account.music_role).await;
158 - seed_membership_upsert(pool, account.id, selfhosted_id, CommunityRole::Member).await;
159 - }
160 -
161 - // --- Rust community: a few threads
162 -
163 - let welcome_id = seed_thread(
164 - pool,
165 - rust_general,
166 - users[0].id,
167 - "Welcome, read before posting",
168 - true,
169 - false,
170 - )
171 - .await;
172 - 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;
173 -
174 - let async_id = seed_thread(
175 - pool,
176 - rust_general,
177 - users[1].id,
178 - "How do I get started with async Rust?",
179 - false,
180 - false,
181 - )
182 - .await;
183 - 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;
184 - 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;
185 -
186 - let error_id = seed_thread(
187 - pool,
188 - rust_help,
189 - users[1].id,
190 - "Best practices for error handling in Axum",
191 - false,
192 - false,
193 - )
194 - .await;
195 - 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;
196 -
197 - // --- Self-Hosted: a few threads
198 -
199 - let caddy_id = seed_thread(
200 - pool,
201 - sh_general,
202 - users[0].id,
203 - "Caddy vs nginx for reverse proxy",
204 - false,
205 - false,
206 - )
207 - .await;
208 - 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;
209 - 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;
210 -
211 - // --- Music community: bulk seed
212 -
213 - seed_music_general(pool, music_general, &users).await;
214 - seed_music_mixing(pool, music_mixing, &users).await;
215 - seed_music_sound_design(pool, music_sound, &users).await;
216 -
217 - // reply_count is computed live at read time, nothing to backfill.
218 -
219 - let total_threads: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM threads")
220 - .fetch_one(pool)
221 - .await
222 - .unwrap_or(0);
223 - let total_posts: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM posts")
224 - .fetch_one(pool)
225 - .await
226 - .unwrap_or(0);
227 -
228 - tracing::info!(
229 - "seeded 3 communities, {} users, {} threads, {} posts",
230 - users.len(),
231 - total_threads,
232 - total_posts
233 - );
234 - }
235 -
236 - struct SeedUser {
237 - id: Uuid,
238 - }
239 -
240 - /// A harness account: the local row plus the roles it carries per community.
241 - struct HarnessUser {
242 - id: Uuid,
243 - rust_role: CommunityRole,
244 - music_role: CommunityRole,
245 - }
246 -
247 - /// The three accounts the browser harness logs in as.
248 - ///
249 - /// `mnw_account_id` is the join to MNW and these values are fixed on both
250 - /// sides: the MNW example seed creates the same three ids in
251 - /// `server/src/seed/harness.rs`, which is what lets roles be assigned here
252 - /// before anyone has logged in. The two lists have to be edited together, and
253 - /// the MNW side has a test pinning the literals to make that hard to forget.
254 - ///
255 - /// `is_fan_plus`/`is_creator` mirror the perks MNW will report at login. They
256 - /// are denormalised onto the row for post rendering, and login overwrites them
257 - /// from userinfo, so seeding them wrong is cosmetic rather than a privilege
258 - /// question. Seed them right anyway: a pre-login render of a seeded post is one
259 - /// of the things the harness looks at.
260 - async fn seed_harness_users(pool: &PgPool) -> Vec<HarnessUser> {
261 - let harness_data = [
262 - (
263 - "00000000-0000-0000-0000-00000000f001",
264 - "harness_fan",
265 - "Harness Fan",
266 - true,
267 - false,
268 - CommunityRole::Member,
269 - CommunityRole::Member,
270 - ),
271 - (
272 - "00000000-0000-0000-0000-00000000f002",
273 - "harness_creator",
274 - "Harness Creator",
275 - false,
276 - true,
277 - CommunityRole::Member,
278 - CommunityRole::Member,
279 - ),
280 - (
281 - "00000000-0000-0000-0000-00000000f003",
282 - "harness_owner",
283 - "Harness Owner",
284 - false,
285 - false,
286 - CommunityRole::Owner,
287 - CommunityRole::Moderator,
288 - ),
289 - ];
290 -
291 - let mut users = Vec::new();
292 - for (uuid_str, username, display_name, is_fan_plus, is_creator, rust_role, music_role) in
293 - harness_data
294 - {
295 - let id = Uuid::parse_str(uuid_str).unwrap();
296 - sqlx::query(
297 - "INSERT INTO users (mnw_account_id, username, display_name, is_fan_plus, is_creator)
298 - VALUES ($1, $2, $3, $4, $5)
299 - ON CONFLICT (mnw_account_id) DO UPDATE
300 - SET username = EXCLUDED.username,
301 - display_name = EXCLUDED.display_name,
302 - is_fan_plus = EXCLUDED.is_fan_plus,
303 - is_creator = EXCLUDED.is_creator",
304 - )
305 - .bind(id)
306 - .bind(username)
307 - .bind(display_name)
308 - .bind(is_fan_plus)
309 - .bind(is_creator)
310 - .execute(pool)
311 - .await
312 - .expect("failed to seed harness user");
313 - users.push(HarnessUser {
314 - id,
315 - rust_role,
316 - music_role,
317 - });
318 - }
319 - users
320 - }
321 -
322 - async fn seed_users(pool: &PgPool) -> Vec<SeedUser> {
323 - let user_data = [
324 - ("00000000-0000-0000-0000-000000000001", "admin", "Admin"),
325 - ("00000000-0000-0000-0000-000000000002", "maxmj", "Max"),
326 - (
327 - "00000000-0000-0000-0000-000000000003",
328 - "synthwave99",
329 - "Juno",
330 - ),
331 - ("00000000-0000-0000-0000-000000000004", "basshunter", "Erik"),
332 - ("00000000-0000-0000-0000-000000000005", "tape_hiss", "Rae"),
333 - ("00000000-0000-0000-0000-000000000006", "drumroom", "Cole"),
334 - ("00000000-0000-0000-0000-000000000007", "patchwork", "Lina"),
335 - ("00000000-0000-0000-0000-000000000008", "detuned", "Kai"),
336 - ("00000000-0000-0000-0000-000000000009", "resampled", "Noor"),
337 - ("00000000-0000-0000-0000-00000000000a", "clipgain", "Wren"),
338 - ];
339 -
340 - let mut users = Vec::new();
341 - for (uuid_str, username, display_name) in &user_data {
342 - let id = Uuid::parse_str(uuid_str).unwrap();
343 - sqlx::query(
344 - "INSERT INTO users (mnw_account_id, username, display_name)
345 - VALUES ($1, $2, $3)
346 - ON CONFLICT (mnw_account_id) DO NOTHING",
347 - )
348 - .bind(id)
349 - .bind(username)
350 - .bind(display_name)
351 - .execute(pool)
352 - .await
353 - .expect("failed to seed user");
354 - users.push(SeedUser { id });
355 - }
356 - users
357 - }
358 -
359 - /// 35 threads in Music/General, enough for 2 pages.
360 - async fn seed_music_general(pool: &PgPool, category_id: Uuid, users: &[SeedUser]) {
361 - let threads: &[(&str, &str, &[&str])] = &[
362 - (
363 - "Welcome to Music Production",
364 - "Ground rules: be kind, share knowledge, no self-promo spam. Use the right category for your topic.",
365 - &["Glad to be here.", "Thanks for setting this up."],
366 - ),
367 - (
368 - "What DAW are you using in 2026?",
369 - "Curious what everyone is running these days. I have been on Ableton for years but keep hearing about Bitwig.",
370 - &[
371 - "Reaper. Lightweight, cheap, endlessly customizable.",
372 - "Bitwig since version 4. The modulation system is unmatched.",
373 - "FL Studio. Started on it when I was 14, never left.",
374 - "Ableton but I keep a Reaper install for editing and batch processing.",
375 - "Logic. It just works and the stock plugins are surprisingly good.",
376 - ],
377 - ),
378 - (
379 - "Favorite free plugins?",
380 - "What are your go-to free VSTs? Looking for synths, effects, anything.",
381 - &[
382 - "Vital is the obvious answer. Best free synth by a mile.",
383 - "Valhalla Supermassive for reverb/delay. Sounds incredible for free.",
384 - "TDR Nova for EQ. Clean, surgical, zero CPU.",
385 - "Dexed if you want FM synthesis. Basically a free DX7.",
386 - ],
387 - ),
388 - (
389 - "How do you organize your sample library?",
390 - "My sample folder is a disaster. Thousands of files with no structure. How do you keep things findable?",
391 - &[
392 - "Genre > Type > Character. So like Electronic > Kicks > Punchy. Took a weekend to sort but worth it.",
393 - "I use tags instead of folders. AudioFiles has been great for this actually.",
394 - "Honestly I just search. If the filename is descriptive enough, search wins over folders every time.",
395 - ],
396 - ),
397 - (
398 - "Analog vs digital debate in 2026",
399 - "Is there still a meaningful difference? Modern plugins are so good that I genuinely cannot tell in blind tests anymore.",
400 - &[
401 - "The difference is workflow, not sound. Hardware forces commitment.",
402 - "I sold all my hardware last year. Zero regrets. Plugins are there.",
403 - "The tactile experience matters. Turning a real knob is different from clicking a virtual one.",
404 - "Both. I track through analog preamps and process in the box.",
405 - ],
406 - ),
407 - (
408 - "How long did it take you to finish your first track?",
409 - "Been producing for 3 months and I cannot seem to finish anything. Is this normal?",
410 - &[
411 - "Totally normal. Took me a year to finish something I was not embarrassed by.",
412 - "Force yourself to finish bad tracks. The skill of finishing is separate from the skill of producing.",
413 - "Set a deadline. 48 hours from start to bounce. Quality does not matter, completion does.",
414 - "Still working on my first one honestly.",
415 - "About 6 months. And it was terrible. But I finished it and that mattered.",
416 - ],
417 - ),
418 - (
419 - "Studio monitors vs headphones for mixing?",
420 - "I live in an apartment and cannot treat the room properly. Should I just mix on headphones?",
421 - &[
422 - "Headphones are fine if you know their character. Use a reference plugin like Sonarworks.",
423 - "I mix on DT 770s and reference on my car stereo. Not ideal but it works.",
424 - "Even cheap acoustic treatment helps. Hang some moving blankets.",
425 - ],
426 - ),
427 - (
428 - "What's your creative process?",
429 - "Do you start with drums, melody, a sample? Curious how other people approach a blank session.",
430 - &[
431 - "Always start with chords. Everything else follows from the harmony.",
432 - "I jam on a synth until something clicks, then build around that loop.",
433 - "Sound design first. Find an interesting texture, then write something that serves it.",
434 - "Drums. Always drums. Get the groove right and the rest writes itself.",
435 - "I hum melodies into my phone all day, then sit down and try to recreate them.",
436 - "Start with a reference track. Analyze the arrangement, then write something inspired by the structure.",
437 - ],
438 - ),
439 - (
440 - "Best MIDI controllers under $200?",
441 - "Looking for something with keys and some knobs. Not a full workstation, just something to play ideas into the DAW.",
442 - &[
443 - "Arturia Keystep 37. Great keybed for the price, plus the arpeggiator and sequencer.",
444 - "Novation Launchkey MK3. Deep DAW integration if you use Ableton.",
445 - "Akai MPK Mini. Tiny but surprisingly capable.",
446 - ],
447 - ),
448 - (
449 - "CPU overload: how do you deal with it?",
450 - "My sessions keep hitting 100% CPU. Running a Ryzen 5 3600 with 16GB RAM. Is it time to upgrade or am I doing something wrong?",
451 - &[
452 - "Freeze tracks you are not actively working on. Most DAWs support this.",
453 - "Bounce MIDI to audio once you are happy with the sound. Frees up the plugin.",
454 - "Check your buffer size. 256 for recording, 1024+ for mixing.",
455 - "Your CPU is fine. It is probably one plugin eating everything. Check per-plugin CPU.",
456 - ],
457 - ),
458 - (
459 - "Music theory: how much do you actually use?",
460 - "I know basic chords and scales but never studied theory formally. How much do you all actually apply?",
461 - &[
462 - "Enough to communicate with other musicians. Circle of fifths, chord functions, basic voice leading.",
463 - "Almost none. I go by ear and fix what sounds wrong.",
464 - "A lot. Understanding why something sounds good helps you recreate it faster.",
465 - ],
466 - ),
467 - (
468 - "Side-chaining techniques",
469 - "What is your preferred method for side-chaining? Compressor? Volume automation? LFO tool?",
470 - &[
471 - "Trackspacer. Not technically side-chain compression but solves the same problem better.",
472 - "Volume shaper plugin. More precise than a compressor and you can draw the exact curve.",
473 - "Classic compressor side-chain. Simple, works, everyone knows how to do it.",
474 - "I automate the volume manually. Full control, no artifacts.",
475 - ],
476 - ),
477 - (
478 - "How do you avoid ear fatigue?",
479 - "I can mix for maybe 2 hours before everything starts sounding the same. Any tips?",
480 - &[
481 - "Take breaks every 45 minutes. Walk around, drink water, reset.",
482 - "Mix at low volume. If it sounds good quiet, it will sound good loud.",
483 - "Reference constantly. Switch to a commercial track every 15 minutes.",
484 - ],
485 - ),
486 - (
487 - "Collaboration: how do you share projects?",
488 - "Want to collaborate with a friend who uses a different DAW. What is the best approach?",
489 - &[
490 - "Stems. Bounce each track as a WAV, share via cloud storage.",
491 - "MIDI + stems for the parts that need to be editable.",
492 - "We just use the same DAW. Easier than any workaround.",
493 - ],
494 - ),
495 - (
496 - "Loudness standards for streaming platforms",
497 - "Spotify targets -14 LUFS, Apple Music -16. Should I master to a specific target or just make it sound good?",
498 - &[
499 - "Master to what sounds best, then check loudness after. Most platforms normalize anyway.",
500 - "I aim for -12 to -14. Leaves headroom but still competitive.",
Lines truncated
@@ -1,0 +1,59 @@
1 + //! The 55 replies that make one thread long enough to page.
2 +
3 + pub(super) const POSTS: [&str; 55] = [
4 + "Been on Ableton since version 8. Cannot imagine switching at this point.",
5 + "I tried Bitwig for a month. The modulation system is amazing but I missed Ableton's workflow.",
6 + "Reaper deserves more love. The customization is insane once you learn ReaScript.",
7 + "FL Studio's piano roll is still the best. Nothing else comes close for MIDI editing.",
8 + "Logic's Drummer track is legitimately good. Saves me hours on demo tracks.",
9 + "Has anyone tried Ardour? Curious about the open source option.",
10 + "Ardour is great for recording and mixing. Less so for electronic production.",
11 + "Studio One has the best drag-and-drop workflow. Everything just snaps where you want it.",
12 + "I use different DAWs for different tasks. Ableton for production, Reaper for mixing, FL for beats.",
13 + "That sounds exhausting. I would rather master one tool than juggle three.",
14 + "Cubase is still king for film scoring. The expression maps and articulation system are unmatched.",
15 + "Pro Tools is dead for everything except professional studios that need industry compatibility.",
16 + "Hot take: the DAW matters way less than people think. Spend that energy on learning mixing.",
17 + "Agree. I have heard incredible music made in every DAW. And terrible music too.",
18 + "The best DAW is the one you know. Switching costs months of productivity.",
19 + "I switch between FL and Ableton depending on the genre. Hip hop in FL, electronic in Ableton.",
20 + "Reason had so much potential. Shame it never gained traction against the big players.",
21 + "Anyone using hardware-only setups? No DAW at all?",
22 + "I did a hardware-only album last year. It was liberating but the mixdown was painful.",
23 + "Renoise. Tracker workflow is fast once you learn the key commands.",
24 + "I miss trackers. Something about the vertical scrolling interface makes rhythm programming intuitive.",
25 + "What about Maschine or MPC as a DAW? Those have gotten pretty capable.",
26 + "MPC standalone is great for jamming but I always end up exporting to a proper DAW for mixing.",
27 + "Bitwig on Linux is underrated. Full native support, no WINE hacks.",
28 + "I produce on Linux with Bitwig. Works perfectly. Even plugin support is solid with yabridge.",
29 + "The iPad is becoming a legit production platform. GarageBand to Logic hands off cleanly now.",
30 + "Mobile production is fun for sketching ideas but I would not mix on a tablet.",
31 + "Anyone tried the new AI features in some DAWs? Stem separation, auto-mixing, etc.",
32 + "AI stem separation is useful for sampling. The mixing AI is gimmicky.",
33 + "I use LALAL.ai for stem extraction. Better results than the built-in DAW tools.",
34 + "The plugin format wars are annoying. VST3, AU, CLAP. Can we just pick one?",
35 + "CLAP is the future. Open standard, better multithreading, no licensing fees.",
36 + "VST3 is fine. It works everywhere. CLAP is cool but the ecosystem is not there yet.",
37 + "AU is Apple-only, so it is irrelevant for anyone on Windows or Linux.",
38 + "Reaper supports everything. VST2, VST3, AU, CLAP, JS, LV2. No format wars needed.",
39 + "Honestly the DAW you start with tends to be the one you stick with. The muscle memory runs deep.",
40 + "I started on GarageBand, moved to Logic, tried Ableton, came back to Logic. Full circle.",
41 + "FL Studio's lifetime free updates are unbeatable. Paid once in 2015, still getting new versions.",
42 + "That is genuinely impressive. No other DAW offers that deal.",
43 + "The subscription model some companies are pushing is concerning. I want to own my tools.",
44 + "Agree. If the company shuts down, subscriptions die. Perpetual licenses survive.",
45 + "Ableton's standard-to-suite upgrade price is brutal though. Almost the cost of a new DAW.",
46 + "Wait for sales. Ableton does 20-25% off once or twice a year.",
47 + "I use the DAW that came free with my audio interface. Ableton Lite. It is enough for what I do.",
48 + "Lite versions are a great starting point. Upgrade when you hit the track limit.",
49 + "Track limits are artificial but they do force you to commit and bounce stems.",
50 + "Anyone still using Cakewalk? It went free and then got acquired again.",
51 + "Cakewalk is solid but the UI feels dated compared to modern DAWs.",
52 + "UI matters more than people admit. You stare at this thing for hours. It should look good.",
53 + "Function over form. I will take ugly and fast over pretty and slow any day.",
54 + "My dream DAW would have Ableton's session view, FL's piano roll, Logic's stock plugins, and Reaper's customization.",
55 + "So basically everything good from everything. Not unreasonable honestly.",
56 + "The DAW market is mature enough that they are all good. Pick one and make music.",
57 + "This thread has been going for a while. I think the conclusion is: whatever works for you.",
58 + "Final answer: all DAWs are good. Now go make music instead of debating tools.",
59 + ];
@@ -1,0 +1,336 @@
1 + //! Music/General: 35 threads, enough for two pages.
2 +
3 + pub(super) const THREADS: &[(&str, &str, &[&str])] = &[
4 + (
5 + "Welcome to Music Production",
6 + "Ground rules: be kind, share knowledge, no self-promo spam. Use the right category for your topic.",
7 + &["Glad to be here.", "Thanks for setting this up."],
8 + ),
9 + (
10 + "What DAW are you using in 2026?",
11 + "Curious what everyone is running these days. I have been on Ableton for years but keep hearing about Bitwig.",
12 + &[
13 + "Reaper. Lightweight, cheap, endlessly customizable.",
14 + "Bitwig since version 4. The modulation system is unmatched.",
15 + "FL Studio. Started on it when I was 14, never left.",
16 + "Ableton but I keep a Reaper install for editing and batch processing.",
17 + "Logic. It just works and the stock plugins are surprisingly good.",
18 + ],
19 + ),
20 + (
21 + "Favorite free plugins?",
22 + "What are your go-to free VSTs? Looking for synths, effects, anything.",
23 + &[
24 + "Vital is the obvious answer. Best free synth by a mile.",
25 + "Valhalla Supermassive for reverb/delay. Sounds incredible for free.",
26 + "TDR Nova for EQ. Clean, surgical, zero CPU.",
27 + "Dexed if you want FM synthesis. Basically a free DX7.",
28 + ],
29 + ),
30 + (
31 + "How do you organize your sample library?",
32 + "My sample folder is a disaster. Thousands of files with no structure. How do you keep things findable?",
33 + &[
34 + "Genre > Type > Character. So like Electronic > Kicks > Punchy. Took a weekend to sort but worth it.",
35 + "I use tags instead of folders. AudioFiles has been great for this actually.",
36 + "Honestly I just search. If the filename is descriptive enough, search wins over folders every time.",
37 + ],
38 + ),
39 + (
40 + "Analog vs digital debate in 2026",
41 + "Is there still a meaningful difference? Modern plugins are so good that I genuinely cannot tell in blind tests anymore.",
42 + &[
43 + "The difference is workflow, not sound. Hardware forces commitment.",
44 + "I sold all my hardware last year. Zero regrets. Plugins are there.",
45 + "The tactile experience matters. Turning a real knob is different from clicking a virtual one.",
46 + "Both. I track through analog preamps and process in the box.",
47 + ],
48 + ),
49 + (
50 + "How long did it take you to finish your first track?",
51 + "Been producing for 3 months and I cannot seem to finish anything. Is this normal?",
52 + &[
53 + "Totally normal. Took me a year to finish something I was not embarrassed by.",
54 + "Force yourself to finish bad tracks. The skill of finishing is separate from the skill of producing.",
55 + "Set a deadline. 48 hours from start to bounce. Quality does not matter, completion does.",
56 + "Still working on my first one honestly.",
57 + "About 6 months. And it was terrible. But I finished it and that mattered.",
58 + ],
59 + ),
60 + (
61 + "Studio monitors vs headphones for mixing?",
62 + "I live in an apartment and cannot treat the room properly. Should I just mix on headphones?",
63 + &[
64 + "Headphones are fine if you know their character. Use a reference plugin like Sonarworks.",
65 + "I mix on DT 770s and reference on my car stereo. Not ideal but it works.",
66 + "Even cheap acoustic treatment helps. Hang some moving blankets.",
67 + ],
68 + ),
69 + (
70 + "What's your creative process?",
71 + "Do you start with drums, melody, a sample? Curious how other people approach a blank session.",
72 + &[
73 + "Always start with chords. Everything else follows from the harmony.",
74 + "I jam on a synth until something clicks, then build around that loop.",
75 + "Sound design first. Find an interesting texture, then write something that serves it.",
76 + "Drums. Always drums. Get the groove right and the rest writes itself.",
77 + "I hum melodies into my phone all day, then sit down and try to recreate them.",
78 + "Start with a reference track. Analyze the arrangement, then write something inspired by the structure.",
79 + ],
80 + ),
81 + (
82 + "Best MIDI controllers under $200?",
83 + "Looking for something with keys and some knobs. Not a full workstation, just something to play ideas into the DAW.",
84 + &[
85 + "Arturia Keystep 37. Great keybed for the price, plus the arpeggiator and sequencer.",
86 + "Novation Launchkey MK3. Deep DAW integration if you use Ableton.",
87 + "Akai MPK Mini. Tiny but surprisingly capable.",
88 + ],
89 + ),
90 + (
91 + "CPU overload: how do you deal with it?",
92 + "My sessions keep hitting 100% CPU. Running a Ryzen 5 3600 with 16GB RAM. Is it time to upgrade or am I doing something wrong?",
93 + &[
94 + "Freeze tracks you are not actively working on. Most DAWs support this.",
95 + "Bounce MIDI to audio once you are happy with the sound. Frees up the plugin.",
96 + "Check your buffer size. 256 for recording, 1024+ for mixing.",
97 + "Your CPU is fine. It is probably one plugin eating everything. Check per-plugin CPU.",
98 + ],
99 + ),
100 + (
101 + "Music theory: how much do you actually use?",
102 + "I know basic chords and scales but never studied theory formally. How much do you all actually apply?",
103 + &[
104 + "Enough to communicate with other musicians. Circle of fifths, chord functions, basic voice leading.",
105 + "Almost none. I go by ear and fix what sounds wrong.",
106 + "A lot. Understanding why something sounds good helps you recreate it faster.",
107 + ],
108 + ),
109 + (
110 + "Side-chaining techniques",
111 + "What is your preferred method for side-chaining? Compressor? Volume automation? LFO tool?",
112 + &[
113 + "Trackspacer. Not technically side-chain compression but solves the same problem better.",
114 + "Volume shaper plugin. More precise than a compressor and you can draw the exact curve.",
115 + "Classic compressor side-chain. Simple, works, everyone knows how to do it.",
116 + "I automate the volume manually. Full control, no artifacts.",
117 + ],
118 + ),
119 + (
120 + "How do you avoid ear fatigue?",
121 + "I can mix for maybe 2 hours before everything starts sounding the same. Any tips?",
122 + &[
123 + "Take breaks every 45 minutes. Walk around, drink water, reset.",
124 + "Mix at low volume. If it sounds good quiet, it will sound good loud.",
125 + "Reference constantly. Switch to a commercial track every 15 minutes.",
126 + ],
127 + ),
128 + (
129 + "Collaboration: how do you share projects?",
130 + "Want to collaborate with a friend who uses a different DAW. What is the best approach?",
131 + &[
132 + "Stems. Bounce each track as a WAV, share via cloud storage.",
133 + "MIDI + stems for the parts that need to be editable.",
134 + "We just use the same DAW. Easier than any workaround.",
135 + ],
136 + ),
137 + (
138 + "Loudness standards for streaming platforms",
139 + "Spotify targets -14 LUFS, Apple Music -16. Should I master to a specific target or just make it sound good?",
140 + &[
141 + "Master to what sounds best, then check loudness after. Most platforms normalize anyway.",
142 + "I aim for -12 to -14. Leaves headroom but still competitive.",
143 + "The number matters less than the dynamic range. A dynamic -14 sounds better than a squashed -8.",
144 + ],
145 + ),
146 + (
147 + "What headphones do you use?",
148 + "Looking for mixing headphones. Budget around $150-300.",
149 + &[
150 + "Sennheiser HD 600. Flat, detailed, comfortable for long sessions.",
151 + "Beyerdynamic DT 990 Pro. Wide soundstage. A bit bright but you learn the character.",
152 + "AKG K712. Open-back, great imaging.",
153 + "Audio-Technica ATH-M50x. Closed-back, good isolation, slightly hyped low end.",
154 + ],
155 + ),
156 + (
157 + "Sampling ethics",
158 + "Where do you draw the line with sampling? Royalty-free packs? Vinyl? Other artists' released music?",
159 + &[
160 + "Royalty-free is fair game obviously. For anything else, clear it or make it unrecognizable.",
161 + "I only sample stuff I have created myself or bought a license for.",
162 + "Sampling is an art form. The key is transformation.",
163 + ],
164 + ),
165 + (
166 + "When to call a mix done?",
167 + "I keep tweaking endlessly. How do you know when to stop?",
168 + &[
169 + "When changes become lateral instead of improvements. If you are just going back and forth, stop.",
170 + "Set a deadline. Ship it. You can always do a v2 later.",
171 + "Compare to your reference. If it holds up, it is done.",
172 + "When you start breaking things that were already working.",
173 + ],
174 + ),
175 + (
176 + "Learning synthesis from scratch",
177 + "I have been using presets forever. Want to learn to design my own sounds. Where do I start?",
178 + &[
179 + "Start with subtractive synthesis. One oscillator, one filter, one envelope. Understand what each does.",
180 + "Syntorial. It is a paid app but it teaches synthesis by ear, not from theory.",
181 + "Pick one synth and learn it deeply. Vital or Serum are good because they visualize everything.",
182 + ],
183 + ),
184 + (
185 + "Vocal processing chain",
186 + "What is your typical vocal chain? EQ, compression, de-essing, etc.",
187 + &[
188 + "Gain staging, subtractive EQ, compressor (light 2:1), de-esser, additive EQ, reverb send.",
189 + "Similar but I add a saturation plugin before the compressor. Gives warmth.",
190 + "Depends entirely on the vocal and the genre. There is no universal chain.",
191 + ],
192 + ),
193 + (
194 + "Budget acoustic treatment",
195 + "Can I treat a small room for under $200? What should I prioritize?",
196 + &[
197 + "First reflections. Put absorption panels at mirror points on side walls.",
198 + "Bass traps in the corners are the highest impact single thing you can do.",
199 + "Rockwool panels in wooden frames. DIY for about $50 per panel.",
200 + "A thick rug on the floor and heavy curtains help more than people think.",
201 + ],
202 + ),
203 + (
204 + "Favorite reverb plugin?",
205 + "Looking for something versatile. Plates, halls, rooms, all in one.",
206 + &[
207 + "Valhalla Room. $50, sounds amazing, low CPU.",
208 + "FabFilter Pro-R 2. The decay rate EQ is genius.",
209 + "Stock reverb in your DAW is probably fine for 90% of use cases.",
210 + ],
211 + ),
212 + (
213 + "How important is mastering?",
214 + "Can I just slap a limiter on the master bus and call it a day?",
215 + &[
216 + "For demos and personal releases, yes. For commercial releases, get it mastered.",
217 + "Mastering is about perspective. A second set of ears in a treated room catches things you miss.",
218 + "iZotope Ozone is good enough for DIY mastering if you learn what each module does.",
219 + ],
220 + ),
221 + (
222 + "Managing multiple projects at once",
223 + "I have like 30 unfinished projects. How do you manage the backlog?",
224 + &[
225 + "Pick three. Finish them. Delete the rest. Harsh but effective.",
226 + "I give each project a status: idea, in progress, mixing, done. Only 2 can be in progress at once.",
227 + "Set a monthly goal. One finished track per month, minimum.",
228 + ],
229 + ),
230 + (
231 + "Track referencing workflow",
232 + "How do you reference against commercial tracks while mixing?",
233 + &[
234 + "Import the reference directly into the session. Level match with a LUFS meter, A/B constantly.",
235 + "I use a dedicated plugin that lets me switch between my mix and references with one click.",
236 + "Listen to the reference on the same speakers, back to back. Do not overthink the workflow.",
237 + ],
238 + ),
239 + (
240 + "Gain staging basics",
241 + "Keep hearing about gain staging but not sure I understand it. ELI5?",
242 + &[
243 + "Every plugin in your chain should receive signal at a reasonable level. If you push hot into a compressor, it behaves differently than if you feed it -18dBFS.",
244 + "Basically: do not clip anything in the chain before the final limiter. Use trim/gain plugins if needed.",
245 + "Aim for peaks around -12 to -6 on each channel. Leaves headroom on the master.",
246 + ],
247 + ),
248 + (
249 + "Lo-fi production tips",
250 + "Want to make lo-fi hip hop style beats. What gives that characteristic sound?",
251 + &[
252 + "Bitcrushing, vinyl noise, tape saturation, de-tuned samples, swing on the drums.",
253 + "Record stuff through a cheap mic or tape deck. Real degradation sounds better than plugins simulating it.",
254 + "Side-chain the whole mix to the kick. Slow attack, slow release. That pumping effect is key.",
255 + "Use jazz chord voicings. Maj7, min9, dom13. That is the harmonic foundation.",
256 + ],
257 + ),
258 + (
259 + "What's your backup strategy?",
260 + "Lost a project file last week and it hurt. How do you backup your work?",
261 + &[
262 + "Git for project files, rsync to a NAS for samples and bounces.",
263 + "Time Machine plus a monthly clone to an external drive.",
264 + "Cloud sync. Dropbox for active projects, cold storage archive for finished work.",
265 + "Three copies: local SSD, NAS, off-site. If it does not exist in three places, it does not exist.",
266 + ],
267 + ),
268 + (
269 + "Distortion as a creative tool",
270 + "Using distortion for more than just guitars. What are your favorite ways to use it?",
271 + &[
272 + "Parallel saturation on vocals. Blend in just enough to add presence without obvious distortion.",
273 + "Run a sub bass through a wavefolder. Instant aggression.",
274 + "Distort a reverb return. Shoegaze in a box.",
275 + ],
276 + ),
277 + (
278 + "Hardware synths worth buying in 2026?",
279 + "With plugins being so good, is there any reason to buy hardware synths?",
280 + &[
281 + "Workflow. Playing a hardware synth is a different experience than clicking a mouse.",
282 + "The Korg Minilogue XD is still hard to beat for the price. Four voices, great effects.",
283 + "I would skip hardware unless you perform live. For studio work, plugins win on convenience.",
284 + "Elektron Digitone. FM synthesis in a box with an incredible sequencer.",
285 + ],
286 + ),
287 + (
288 + "How to get better at arrangement",
289 + "My loops sound great but full tracks feel flat. Any tips for arrangement?",
290 + &[
291 + "Study arrangements of songs you like. Map them out on paper. Intro, verse, chorus, bridge. Note what enters and exits.",
292 + "Contrast. If the chorus is dense, strip back the verse. Dynamics come from what you remove.",
293 + "Energy curve. Every section should either build tension or release it. Never stay flat.",
294 + "Reference tracks. Drop one into your session and match the structure.",
295 + "Automate everything. Filter sweeps, volume rides, effect sends. Movement keeps the listener engaged.",
296 + ],
297 + ),
298 + (
299 + "Mixing in mono",
300 + "People say to check your mix in mono. Why? And how often should I do it?",
301 + &[
302 + "Phase cancellation. If something disappears in mono, your stereo image has phase issues.",
303 + "I check mono every 30 minutes during a mix. Just hit the mono button, listen for 10 seconds.",
304 + "Some clubs play in mono. If your mix falls apart, half your audience hears a broken version.",
305 + ],
306 + ),
307 + (
308 + "Best YouTube channels for learning production?",
309 + "What channels do you actually learn from vs just watch for entertainment?",
310 + &[
311 + "Dan Worrall. No-nonsense, deep technical knowledge, great explanations.",
312 + "You Suck at Producing. Funny but genuinely educational.",
313 + "In The Mix. Good for beginners. Covers basics thoroughly.",
314 + "Pensado's Place for mixing techniques from industry engineers.",
315 + ],
316 + ),
317 + (
318 + "Dealing with creative blocks",
319 + "Have not made anything in two weeks. Everything I start sounds boring. How do you push through?",
320 + &[
321 + "Constraints. Give yourself a rule: only use three sounds, or finish in one hour. Limits breed creativity.",
322 + "Listen to music outside your genre. Completely different styles can spark unexpected ideas.",
323 + "Recreate a track you love. You will learn technique and usually branch off into something original.",
324 + "Take a break. Burnout is real. Come back when you actually want to, not when you feel you should.",
325 + ],
326 + ),
327 + (
328 + "Automation tips",
329 + "What do you automate most in your mixes?",
330 + &[
331 + "Volume. Subtle 1-2dB rides on vocals and leads make a huge difference.",
332 + "Filter cutoff. Automate a low-pass sweep into the chorus for instant energy lift.",
333 + "Reverb send. More reverb in quiet sections, less in dense sections.",
334 + ],
335 + ),
336 + ];
@@ -1,0 +1,141 @@
1 + //! Mixing and Mastering: 15 threads.
2 +
3 + pub(super) const THREADS: &[(&str, &str, &[&str])] = &[
4 + (
5 + "EQ before or after compression?",
6 + "Classic debate. What is your default chain order and why?",
7 + &[
8 + "Subtractive EQ first to clean up, then compress, then additive EQ to shape.",
9 + "Depends on the source. Muddy vocal? EQ first. Clean recording? Compress first.",
10 + "I do both. Light EQ before, heavier shaping after.",
11 + ],
12 + ),
13 + (
14 + "Parallel compression techniques",
15 + "How are you using parallel compression? Just on drums or on everything?",
16 + &[
17 + "Drums and vocals mainly. Crush a copy, blend it in at like -12dB.",
18 + "I parallel compress the entire mix bus lightly. Adds glue without squashing dynamics.",
19 + "The NY compression trick on drums is still unbeatable. Heavy compression, blend to taste.",
20 + ],
21 + ),
22 + (
23 + "Multiband compression: when to use it?",
24 + "I never quite know when multiband is the right tool vs a regular compressor.",
25 + &[
26 + "When different frequency ranges need different treatment. A boomy vocal with sibilance needs different ratios for lows and highs.",
27 + "On the master bus for gentle tonal balancing. Very light ratios, like 1.5:1.",
28 + "Rarely. Most problems are better solved with EQ. Multiband comp is a precision tool, not a default.",
29 + ],
30 + ),
31 + (
32 + "Mixing with saturation",
33 + "How much saturation do you add and where in the chain?",
34 + &[
35 + "A tiny bit on every channel. Tape emulation at the end of each strip. Adds harmonic richness.",
36 + "I saturate the mix bus and nothing else. Keeps it cohesive.",
37 + "Saturation on bass is essential for me. Makes it audible on small speakers.",
38 + "Early in the chain, before compression. Saturated signals compress differently.",
39 + ],
40 + ),
41 + (
42 + "Taming harsh vocals",
43 + "My vocal recordings always end up harsh around 3-5kHz. De-esser? EQ? Both?",
44 + &[
45 + "Dynamic EQ on the 2-5kHz range. Only dips when it gets harsh, leaves the presence otherwise.",
46 + "De-esser is specifically for sibilance (6-10kHz). For 3-5kHz harshness, use a dynamic EQ or multiband.",
47 + "Check your microphone and preamp. Some mics just have a harsh presence peak. A different mic might solve it at the source.",
48 + ],
49 + ),
50 + (
51 + "Reverb mixing tips",
52 + "My reverbs either sound obvious or nonexistent. How do you make them sit right?",
53 + &[
54 + "Pre-delay. 20-50ms of pre-delay separates the dry signal from the reverb and adds clarity.",
55 + "High-cut the reverb at 6-8kHz. Bright reverb tails make everything washy.",
56 + "Send to reverb, then EQ the return. Treat the reverb as its own instrument.",
57 + ],
58 + ),
59 + (
60 + "Mixing low end: kick and bass coexistence",
61 + "How do you make the kick and bass work together without one masking the other?",
62 + &[
63 + "Pick one to own the sub frequencies. If the kick has the sub, high-pass the bass at 60Hz. Or vice versa.",
64 + "Side-chain the bass to the kick. Classic but effective.",
65 + "Complementary EQ. Boost the kick at 60Hz, cut the bass there. Boost the bass at 100Hz, cut the kick.",
66 + "In most genres, the kick owns 40-80Hz and the bass lives at 80-200Hz. Arrange them to not compete.",
67 + ],
68 + ),
69 + (
70 + "Mix bus processing",
71 + "What do you put on your mix bus? When do you add it?",
72 + &[
73 + "Gentle glue compressor (SSL style, 2:1, slow attack) from the start. Mix into it.",
74 + "EQ, compression, slight saturation, limiter. But everything is doing very little. 1-2dB each.",
75 + "Nothing until the final mix. I want to hear what I actually have before processing it.",
76 + ],
77 + ),
78 + (
79 + "Reference level for mixing",
80 + "What volume level do you monitor at while mixing?",
81 + &[
82 + "Calibrated to 85dB SPL for critical listening. Most of the time I mix around 75dB.",
83 + "Quiet. If I can have a conversation over the mix, the volume is right.",
84 + "I check at multiple levels. Low for balance, medium for detail, loud for short reality checks.",
85 + ],
86 + ),
87 + (
88 + "Width and stereo imaging",
89 + "How do you create width in a mix without it collapsing in mono?",
90 + &[
91 + "Keep the center strong (kick, bass, lead vocal, snare). Spread supporting elements.",
92 + "Use different methods: hard panning, stereo delay, chorus, mid-side EQ. Variety avoids phase issues.",
93 + "Always check mono. If it sounds thin in mono, your width is relying on phase tricks that cancel.",
94 + ],
95 + ),
96 + (
97 + "Handling dynamic range in modern productions",
98 + "Everything is so compressed now. How do you keep dynamics while still being competitive in loudness?",
99 + &[
100 + "Mix dynamically. Let the mastering engineer handle loudness. That is literally their job.",
101 + "Automate volume instead of compressing. Ride the fader for consistent level without squashing transients.",
102 + "Use upward compression. Raises the quiet parts without touching the peaks.",
103 + ],
104 + ),
105 + (
106 + "Delay vs reverb for depth",
107 + "When do you reach for a delay instead of a reverb to create depth?",
108 + &[
109 + "Delay for rhythmic depth (synced to tempo). Reverb for spatial depth (rooms, halls).",
110 + "Short delays (30-80ms) create depth without the wash of reverb. Great for keeping things tight.",
111 + "I use both together. Short delay into a reverb return. Best of both worlds.",
112 + ],
113 + ),
114 + (
115 + "Panning strategies",
116 + "Do you follow specific panning rules or go by feel?",
117 + &[
118 + "LCR panning. Hard left, center, hard right. Simple but effective. Forces commitment.",
119 + "I pan where the instruments would be on a stage. Drums from the drummer's perspective.",
120 + "By feel. Whatever serves the song. Rules are starting points, not laws.",
121 + ],
122 + ),
123 + (
124 + "Mixing with headphones: tips and tricks",
125 + "For those who primarily mix on headphones, what are your strategies?",
126 + &[
127 + "Crossfeed plugin. Simulates speaker crosstalk so panning sounds more natural.",
128 + "Reference constantly on speakers when you can. Even laptop speakers reveal balance issues.",
129 + "Open-back headphones for mixing, closed-back for tracking. The soundstage difference matters.",
130 + ],
131 + ),
132 + (
133 + "The loudness war is over, right?",
134 + "With streaming normalization, does anyone still master to -6 LUFS?",
135 + &[
136 + "EDM and hip hop still push hard. Genre expectations matter more than streaming targets.",
137 + "The war is over for most genres. I master to -10 to -14 depending on the track.",
138 + "Yes. And it sounds better. More dynamics, more punch, more musicality. No reason to smash it.",
139 + ],
140 + ),
141 + ];
@@ -1,0 +1,82 @@
1 + //! The forum content itself: which threads exist, who posts in them, and what
2 + //! they say. Each category's table is a sibling const, so the loop that writes
3 + //! it stays readable next to the other three.
4 +
5 + mod discussion;
6 + mod general;
7 + mod mixing;
8 + mod sound_design;
9 +
10 + use super::rows::{seed_post, seed_thread};
11 + use super::users::SeedUser;
12 + use sqlx::PgPool;
13 + use uuid::Uuid;
14 +
15 + /// 35 threads in Music/General, enough for 2 pages.
16 + pub(super) async fn seed_music_general(pool: &PgPool, category_id: Uuid, users: &[SeedUser]) {
17 + let threads = general::THREADS;
18 +
19 + // Pinned welcome thread
20 + let welcome_id = seed_thread(pool, category_id, users[0].id, threads[0].0, true, false).await;
21 + seed_post(pool, welcome_id, users[0].id, threads[0].1).await;
22 + for (i, reply) in threads[0].2.iter().enumerate() {
23 + seed_post(pool, welcome_id, users[(i + 2) % users.len()].id, reply).await;
24 + }
25 +
26 + // Remaining threads, one with lots of replies for post pagination
27 + for (idx, (title, body, replies)) in threads.iter().enumerate().skip(1) {
28 + let author = &users[(idx + 1) % users.len()];
29 + let thread_id = seed_thread(pool, category_id, author.id, title, false, false).await;
30 + seed_post(pool, thread_id, author.id, body).await;
31 +
32 + for (i, reply) in replies.iter().enumerate() {
33 + let replier = &users[(idx + i + 3) % users.len()];
34 + seed_post(pool, thread_id, replier.id, reply).await;
35 + }
36 +
37 + // Thread #1 (DAW thread): add 55 extra posts to test post pagination (50/page)
38 + if idx == 1 {
39 + seed_long_discussion(pool, thread_id, users).await;
40 + }
41 + }
42 + }
43 +
44 + /// 15 threads in Mixing & Mastering.
45 + pub(super) async fn seed_music_mixing(pool: &PgPool, category_id: Uuid, users: &[SeedUser]) {
46 + let threads = mixing::THREADS;
47 +
48 + for (idx, (title, body, replies)) in threads.iter().enumerate() {
49 + let author = &users[(idx + 2) % users.len()];
50 + let thread_id = seed_thread(pool, category_id, author.id, title, false, false).await;
51 + seed_post(pool, thread_id, author.id, body).await;
52 + for (i, reply) in replies.iter().enumerate() {
53 + let replier = &users[(idx + i + 4) % users.len()];
54 + seed_post(pool, thread_id, replier.id, reply).await;
55 + }
56 + }
57 + }
58 +
59 + /// 15 threads in Sound Design.
60 + pub(super) async fn seed_music_sound_design(pool: &PgPool, category_id: Uuid, users: &[SeedUser]) {
61 + let threads = sound_design::THREADS;
62 +
63 + for (idx, (title, body, replies)) in threads.iter().enumerate() {
64 + let author = &users[(idx + 5) % users.len()];
65 + let thread_id = seed_thread(pool, category_id, author.id, title, false, false).await;
66 + seed_post(pool, thread_id, author.id, body).await;
67 + for (i, reply) in replies.iter().enumerate() {
68 + let replier = &users[(idx + i + 6) % users.len()];
69 + seed_post(pool, thread_id, replier.id, reply).await;
70 + }
71 + }
72 + }
73 +
74 + /// Add 55 extra posts to a thread to test post pagination (50 posts/page).
75 + pub(super) async fn seed_long_discussion(pool: &PgPool, thread_id: Uuid, users: &[SeedUser]) {
76 + let posts = discussion::POSTS;
77 +
78 + for (i, body) in posts.iter().enumerate() {
79 + let author = &users[(i + 4) % users.len()];
80 + seed_post(pool, thread_id, author.id, body).await;
81 + }
82 + }
@@ -1,0 +1,144 @@
1 + //! Sound Design: 15 threads.
2 +
3 + pub(super) const THREADS: &[(&str, &str, &[&str])] = &[
4 + (
5 + "FM synthesis for beginners",
6 + "FM synthesis always seemed impenetrable to me. Where do you start?",
7 + &[
8 + "Two operators. Carrier and modulator. Change the ratio between them and listen. That is literally it to start.",
9 + "Dexed is free and models the DX7. Great for learning because there are tons of tutorials.",
10 + "Start with simple ratios: 1:1 for warmth, 1:2 for brightness, 1:3 for nasality. Then experiment.",
11 + ],
12 + ),
13 + (
14 + "Wavetable synthesis tips",
15 + "Using Vital/Serum and want to go beyond presets. What are your wavetable tricks?",
16 + &[
17 + "Import your own audio as a wavetable. Record a word, import it, scrub through it with an LFO.",
18 + "Morph between simple waveforms. A slow morph from saw to square adds movement without being obvious.",
19 + "Stack two oscillators with different wavetable positions and detune slightly. Instant width.",
20 + ],
21 + ),
22 + (
23 + "How to make a supersaw",
24 + "Every tutorial makes it sound easy but mine never sound as full as professional supersaws.",
25 + &[
26 + "More unison voices, more detune, slight chorus. But the real secret is layering: a supersaw is usually 2-3 layers, not one patch.",
27 + "High-pass the supersaw and layer a clean sub underneath. The fullness comes from the sub, not the saw.",
28 + "OTT on the supersaw bus. Controversial but it works.",
29 + "Mid-side processing. Keep the center mono, spread the sides. Instant width and power.",
30 + ],
31 + ),
32 + (
33 + "Granular synthesis: practical uses?",
34 + "Granular synths look cool but I cannot figure out when to actually use them. What do you use granular for?",
35 + &[
36 + "Pads and textures. Take a field recording, granularize it, and you have a unique atmosphere.",
37 + "Glitch effects. Short grain size + random position = controlled chaos.",
38 + "Stretching vocals to infinity. Load a vocal chop, set huge grain size, and you have a drone.",
39 + ],
40 + ),
41 + (
42 + "Designing kick drums from scratch",
43 + "How do you synthesize a kick drum? I always just use samples.",
44 + &[
45 + "Sine wave with a pitch envelope. Start at 200-300Hz, drop to 40-60Hz in about 50ms. Add a click layer for attack.",
46 + "Three layers: sub (sine, pitch envelope), body (short noise burst, filtered), click (very short transient).",
47 + "Operator in Ableton can do it in seconds. One sine oscillator, pitch envelope with fast decay.",
48 + ],
49 + ),
50 + (
51 + "Making sounds feel organic",
52 + "My synth patches sound too static and digital. How do you add life?",
53 + &[
54 + "LFOs on everything. Subtle random LFO on pitch, filter, amplitude. Humans are never perfectly steady.",
55 + "Velocity sensitivity. Map velocity to filter cutoff and amplitude. Changes how it responds to playing.",
56 + "Record the performance, do not program it. Even mouse-drawn MIDI lacks human timing.",
57 + "Add noise. A tiny bit of noise mixed in makes digital sounds feel more analog.",
58 + ],
59 + ),
60 + (
61 + "Resampling workflow",
62 + "What is your resampling process? Bounce to audio and re-process?",
63 + &[
64 + "Exactly. Design a sound, bounce it, chop it up, throw it in a sampler, add new processing.",
65 + "I resample through effects. Play a pad, record it through a reverb and delay, chop the result.",
66 + "Granular resampling. Take anything, granularize it, it becomes something entirely new.",
67 + ],
68 + ),
69 + (
70 + "Favorite Vital patches you have designed",
71 + "Share your favorite sound you have made in Vital. What was the approach?",
72 + &[
73 + "A pluck using two wavetables with very fast envelope decay, short reverb. Simple but cuts through any mix.",
74 + "A pad that morphs between 4 wavetable frames with a slow random LFO. Different every time it plays.",
75 + "A bass using the filter FM feature. Self-oscillating filter modulated by an oscillator. Absolutely nasty.",
76 + ],
77 + ),
78 + (
79 + "Foley and field recording for music",
80 + "Anyone incorporate real-world recordings into their music?",
81 + &[
82 + "All the time. City ambience, rain, coffee shop noise. Layer it under pads for realism.",
83 + "I record weird objects and use them as percussion. A spoon on a mug, keys jangling, books dropped.",
84 + "Bird recordings slowed down 10x sound like alien synth pads. Nature is the best sound designer.",
85 + "Contact microphone on everything. Metal fences, bridges, pipes. Gold mine of textures.",
86 + ],
87 + ),
88 + (
89 + "Modular synthesis without hardware",
90 + "Want to explore modular concepts but cannot afford hardware. Software options?",
91 + &[
92 + "VCV Rack. Free, open source, massive module library. It is basically Eurorack on your computer.",
93 + "Bitwig's modulation system is semi-modular. Very deep and all built into the DAW.",
94 + "Max/MSP or Pure Data if you want to go deep. Steep learning curve but unlimited.",
95 + "Cherry Audio Voltage Modular. Cheaper than real hardware and great module selection.",
96 + ],
97 + ),
98 + (
99 + "Designing cinematic impacts and risers",
100 + "How do you create those massive cinematic impacts and tension risers?",
101 + &[
102 + "Layer, layer, layer. Noise sweep + sub drop + metallic hit + reverb tail = one impact.",
103 + "Reverse cymbal with rising pitch is the classic riser. Add a filter sweep and noise for intensity.",
104 + "Record something large. Slam a door. Drop a heavy book. Process it with convolution reverb in a cathedral IR.",
105 + ],
106 + ),
107 + (
108 + "Phase distortion synthesis",
109 + "Casio CZ-series used phase distortion. Anyone still using this technique?",
110 + &[
111 + "Underrated. It is different from FM: smoother harmonics, easier to control.",
112 + "There are a few VST recreations. CZ V from Arturia is faithful to the originals.",
113 + "You can sort of approximate it in any synth by modulating the phase of one oscillator with another, but true PD has a distinct character.",
114 + ],
115 + ),
116 + (
117 + "Processing drums creatively",
118 + "Beyond standard mixing, how do you use creative processing on drums?",
119 + &[
120 + "Bit-crush the snare slightly. Adds grit and lo-fi character.",
121 + "Send drums to a reverb, compress the reverb return hard. Massive room sound.",
122 + "Granular processing on a drum loop. Freeze interesting moments, stretch hits into textures.",
123 + "Run the whole drum bus through a guitar amp sim. Instant rock energy.",
124 + ],
125 + ),
126 + (
127 + "Additive synthesis: is it practical?",
128 + "Additive seems powerful in theory but is it actually useful for sound design?",
129 + &[
130 + "Very useful for evolving pads and organ-like tones. Direct control over individual harmonics.",
131 + "Resynthesis is where additive shines. Analyze a sound, then modify individual partials.",
132 + "In practice, subtractive and wavetable get you there faster. Additive is more of a research tool.",
133 + ],
134 + ),
135 + (
136 + "Sound design for games vs music",
137 + "Anyone here do game audio? How is the process different from music production?",
138 + &[
139 + "Everything needs to loop without a click. And sounds need to work at different pitches and speeds for real-time variation.",
140 + "Much more focus on functionality. A UI click needs to be satisfying but not distracting. Very different from making a synth lead.",
141 + "Middleware like FMOD or Wwise adds a whole layer of implementation. You end up programming behaviors, not making sounds.",
142 + ],
143 + ),
144 + ];
@@ -1,0 +1,247 @@
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 + }
@@ -1,0 +1,144 @@
1 + //! The row writers every content module goes through: one function per table,
2 + //! each returning the id it wrote so the caller can hang children off it.
3 +
4 + use mt_core::types::CommunityRole;
5 + use sqlx::PgPool;
6 + use uuid::Uuid;
7 +
8 + pub(super) async fn seed_community(
9 + pool: &PgPool,
10 + name: &str,
11 + slug: &str,
12 + description: Option<&str>,
13 + ) -> Uuid {
14 + sqlx::query_scalar(
15 + "INSERT INTO communities (name, slug, description)
16 + VALUES ($1, $2, $3)
17 + ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name
18 + RETURNING id",
19 + )
20 + .bind(name)
21 + .bind(slug)
22 + .bind(description)
23 + .fetch_one(pool)
24 + .await
25 + .expect("failed to seed community")
26 + }
27 +
28 + pub(super) async fn seed_category(
29 + pool: &PgPool,
30 + community_id: Uuid,
31 + name: &str,
32 + slug: &str,
33 + sort_order: i32,
34 + description: Option<&str>,
35 + ) -> Uuid {
36 + sqlx::query_scalar(
37 + "INSERT INTO categories (community_id, name, slug, description, sort_order)
38 + VALUES ($1, $2, $3, $4, $5)
39 + ON CONFLICT (community_id, slug) DO UPDATE SET name = EXCLUDED.name
40 + RETURNING id",
41 + )
42 + .bind(community_id)
43 + .bind(name)
44 + .bind(slug)
45 + .bind(description)
46 + .bind(sort_order)
47 + .fetch_one(pool)
48 + .await
49 + .expect("failed to seed category")
50 + }
51 +
52 + pub(super) async fn seed_membership(
53 + pool: &PgPool,
54 + user_id: Uuid,
55 + community_id: Uuid,
56 + role: CommunityRole,
57 + ) {
58 + sqlx::query(
59 + "INSERT INTO memberships (user_id, community_id, role)
60 + VALUES ($1, $2, $3)
61 + ON CONFLICT (user_id, community_id) DO NOTHING",
62 + )
63 + .bind(user_id)
64 + .bind(community_id)
65 + .bind(role.as_str())
66 + .execute(pool)
67 + .await
68 + .expect("failed to seed membership");
69 + }
70 +
71 + pub(super) async fn seed_membership_upsert(
72 + pool: &PgPool,
73 + user_id: Uuid,
74 + community_id: Uuid,
75 + role: CommunityRole,
76 + ) {
77 + sqlx::query(
78 + "INSERT INTO memberships (user_id, community_id, role)
79 + VALUES ($1, $2, $3)
80 + ON CONFLICT (user_id, community_id) DO UPDATE SET role = EXCLUDED.role",
81 + )
82 + .bind(user_id)
83 + .bind(community_id)
84 + .bind(role.as_str())
85 + .execute(pool)
86 + .await
87 + .expect("failed to seed membership");
88 + }
89 +
90 + pub(super) async fn seed_thread(
91 + pool: &PgPool,
92 + category_id: Uuid,
93 + author_id: Uuid,
94 + title: &str,
95 + pinned: bool,
96 + locked: bool,
97 + ) -> Uuid {
98 + sqlx::query_scalar(
99 + "INSERT INTO threads (category_id, author_id, title, pinned, locked)
100 + VALUES ($1, $2, $3, $4, $5)
101 + RETURNING id",
102 + )
103 + .bind(category_id)
104 + .bind(author_id)
105 + .bind(title)
106 + .bind(pinned)
107 + .bind(locked)
108 + .fetch_one(pool)
109 + .await
110 + .expect("failed to seed thread")
111 + }
112 +
113 + pub(super) async fn seed_post(
114 + pool: &PgPool,
115 + thread_id: Uuid,
116 + author_id: Uuid,
117 + body_markdown: &str,
118 + ) -> Uuid {
119 + // Render through the same strict markdown path production uses so seeded
120 + // posts render identically to real ones (previously a hand-rolled escape
121 + // diverged from docengine output).
122 + let body_html = crate::routes::helpers::render_markdown(body_markdown);
123 +
124 + let post_id: Uuid = sqlx::query_scalar(
125 + "INSERT INTO posts (thread_id, author_id, body_markdown, body_html)
126 + VALUES ($1, $2, $3, $4)
127 + RETURNING id",
128 + )
129 + .bind(thread_id)
130 + .bind(author_id)
131 + .bind(body_markdown)
132 + .bind(&body_html)
133 + .fetch_one(pool)
134 + .await
135 + .expect("failed to seed post");
136 +
137 + sqlx::query("UPDATE threads SET last_activity_at = now() WHERE id = $1")
138 + .bind(thread_id)
139 + .execute(pool)
140 + .await
141 + .expect("failed to update thread activity");
142 +
143 + post_id
144 + }
@@ -1,0 +1,129 @@
1 + //! The seeded people: three fixed harness accounts and the ordinary users
2 + //! everything else is written as.
3 +
4 + use mt_core::types::CommunityRole;
5 + use sqlx::PgPool;
6 + use uuid::Uuid;
7 +
8 + pub(super) struct SeedUser {
9 + pub(super) id: Uuid,
10 + }
11 +
12 + /// A harness account: the local row plus the roles it carries per community.
13 + pub(super) struct HarnessUser {
14 + pub(super) id: Uuid,
15 + pub(super) rust_role: CommunityRole,
16 + pub(super) music_role: CommunityRole,
17 + }
18 +
19 + /// The three accounts the browser harness logs in as.
20 + ///
21 + /// `mnw_account_id` is the join to MNW and these values are fixed on both
22 + /// sides: the MNW example seed creates the same three ids in
23 + /// `server/src/seed/harness.rs`, which is what lets roles be assigned here
24 + /// before anyone has logged in. The two lists have to be edited together, and
25 + /// the MNW side has a test pinning the literals to make that hard to forget.
26 + ///
27 + /// `is_fan_plus`/`is_creator` mirror the perks MNW will report at login. They
28 + /// are denormalised onto the row for post rendering, and login overwrites them
29 + /// from userinfo, so seeding them wrong is cosmetic rather than a privilege
30 + /// question. Seed them right anyway: a pre-login render of a seeded post is one
31 + /// of the things the harness looks at.
32 + pub(super) async fn seed_harness_users(pool: &PgPool) -> Vec<HarnessUser> {
33 + let harness_data = [
34 + (
35 + "00000000-0000-0000-0000-00000000f001",
36 + "harness_fan",
37 + "Harness Fan",
38 + true,
39 + false,
40 + CommunityRole::Member,
41 + CommunityRole::Member,
42 + ),
43 + (
44 + "00000000-0000-0000-0000-00000000f002",
45 + "harness_creator",
46 + "Harness Creator",
47 + false,
48 + true,
49 + CommunityRole::Member,
50 + CommunityRole::Member,
51 + ),
52 + (
53 + "00000000-0000-0000-0000-00000000f003",
54 + "harness_owner",
55 + "Harness Owner",
56 + false,
57 + false,
58 + CommunityRole::Owner,
59 + CommunityRole::Moderator,
60 + ),
61 + ];
62 +
63 + let mut users = Vec::new();
64 + for (uuid_str, username, display_name, is_fan_plus, is_creator, rust_role, music_role) in
65 + harness_data
66 + {
67 + let id = Uuid::parse_str(uuid_str).unwrap();
68 + sqlx::query(
69 + "INSERT INTO users (mnw_account_id, username, display_name, is_fan_plus, is_creator)
70 + VALUES ($1, $2, $3, $4, $5)
71 + ON CONFLICT (mnw_account_id) DO UPDATE
72 + SET username = EXCLUDED.username,
73 + display_name = EXCLUDED.display_name,
74 + is_fan_plus = EXCLUDED.is_fan_plus,
75 + is_creator = EXCLUDED.is_creator",
76 + )
77 + .bind(id)
78 + .bind(username)
79 + .bind(display_name)
80 + .bind(is_fan_plus)
81 + .bind(is_creator)
82 + .execute(pool)
83 + .await
84 + .expect("failed to seed harness user");
85 + users.push(HarnessUser {
86 + id,
87 + rust_role,
88 + music_role,
89 + });
90 + }
91 + users
92 + }
93 +
94 + pub(super) async fn seed_users(pool: &PgPool) -> Vec<SeedUser> {
95 + let user_data = [
96 + ("00000000-0000-0000-0000-000000000001", "admin", "Admin"),
97 + ("00000000-0000-0000-0000-000000000002", "maxmj", "Max"),
98 + (
99 + "00000000-0000-0000-0000-000000000003",
100 + "synthwave99",
101 + "Juno",
102 + ),
103 + ("00000000-0000-0000-0000-000000000004", "basshunter", "Erik"),
104 + ("00000000-0000-0000-0000-000000000005", "tape_hiss", "Rae"),
105 + ("00000000-0000-0000-0000-000000000006", "drumroom", "Cole"),
106 + ("00000000-0000-0000-0000-000000000007", "patchwork", "Lina"),
107 + ("00000000-0000-0000-0000-000000000008", "detuned", "Kai"),
108 + ("00000000-0000-0000-0000-000000000009", "resampled", "Noor"),
109 + ("00000000-0000-0000-0000-00000000000a", "clipgain", "Wren"),
110 + ];
111 +
112 + let mut users = Vec::new();
113 + for (uuid_str, username, display_name) in &user_data {
114 + let id = Uuid::parse_str(uuid_str).unwrap();
115 + sqlx::query(
116 + "INSERT INTO users (mnw_account_id, username, display_name)
117 + VALUES ($1, $2, $3)
118 + ON CONFLICT (mnw_account_id) DO NOTHING",
119 + )
120 + .bind(id)
121 + .bind(username)
122 + .bind(display_name)
123 + .execute(pool)
124 + .await
125 + .expect("failed to seed user");
126 + users.push(SeedUser { id });
127 + }
128 + users
129 + }