Skip to main content

max / makenotwork

6.9 KB · 225 lines History Blame Raw
1 # MNW Code Patterns
2
3 Recurring patterns, macros, and conventions used throughout the MNW codebase.
4
5 ## Macros
6
7 ### `impl_str_enum!`
8
9 **Location:** `src/db/enums.rs`
10
11 Generates `Display`, `FromStr`, and sqlx `Type`/`Encode`/`Decode` for enums that map to text columns. The sqlx impls delegate to `String`, so the enum works with both TEXT and VARCHAR columns.
12
13 ```rust
14 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15 #[serde(rename_all = "snake_case")]
16 pub enum CodePurpose {
17 Discount,
18 FreeAccess,
19 FreeTrial,
20 }
21
22 impl_str_enum!(CodePurpose {
23 Discount => "discount",
24 FreeAccess => "free_access",
25 FreeTrial => "free_trial",
26 });
27 ```
28
29 **What it generates:**
30 - `Display::fmt()` writes the mapped string literal
31 - `FromStr::from_str()` parses back, with error `"invalid CodePurpose: xyz"`
32 - `sqlx::Type`/`Encode`/`Decode` so sqlx can read/write the enum directly in queries
33
34 **Used for:** `DiscountType`, `CodePurpose`, `WaitlistStatus`, `TransactionStatus`, `ItemType`, `FileScanStatus`, `SelectionMethod`, `LoginTokenPurpose`, and more.
35
36 ### `define_pg_uuid_id!`
37
38 **Location:** `src/db/id_types.rs`
39
40 Creates newtype wrappers around `uuid::Uuid` for type-safe IDs. Prevents accidental mixing of ID types at compile time (e.g., `UserId` vs `ProjectId`), while remaining transparent in the database layer and JSON APIs.
41
42 ```rust
43 define_pg_uuid_id!(
44 UserId,
45 ProjectId,
46 ItemId,
47 VersionId,
48 TransactionId,
49 // ... 30+ more
50 );
51 ```
52
53 **What each type gets:**
54 - `Copy`, `Clone`, `Hash`, `Eq`, `Ord`
55 - `::new()` generates a UUID v4
56 - `::from_uuid(uuid)` and `From<Uuid>`
57 - `.as_uuid()` and `Deref<Target=Uuid>`
58 - `::nil()` for test fixtures
59 - `serde(transparent)` -- serializes as a UUID string
60 - `sqlx::Type`/`Encode`/`Decode` for PostgreSQL UUID columns
61 - `Display` and `FromStr`
62
63 ### `impl_into_response!`
64
65 **Location:** `src/templates/mod.rs`
66
67 Bulk-implements `IntoResponse` for Askama template structs, so they can be returned directly from route handlers.
68
69 ```rust
70 impl_into_response!(
71 IndexTemplate,
72 LoginTemplate,
73 DashboardUserTemplate,
74 // ... 90+ template types
75 );
76 ```
77
78 Each implementation calls `render_template(self)`, which renders to HTML and returns a 200 response (or a 500 error page if rendering fails).
79
80 ## HTMX Dual-Response Pattern
81
82 See `CONTRIBUTING.md` § HTMX Responses for the full pattern (dual-format handlers, response table, helper functions).
83
84 ## `ListResponse<T>` Envelope
85
86 **Location:** `src/types/mod.rs`
87
88 All JSON list endpoints wrap results in a `{ "data": [...] }` envelope, making the response forward-compatible for pagination metadata.
89
90 ```rust
91 #[derive(Serialize)]
92 pub struct ListResponse<T: Serialize> {
93 pub data: Vec<T>,
94 }
95 ```
96
97 Usage:
98 ```rust
99 let chapters = db::chapters::get_chapters_by_item(&state.db, item_id).await?;
100 Ok(Json(ListResponse { data: chapters.into_iter().map(ChapterResponse::from).collect() }))
101 ```
102
103 ## Error Handling
104
105 See `CONTRIBUTING.md` § Error Handling for the `AppError` enum, HTTP status mapping, user-safe message rules, and JSON error middleware. Source: `src/error.rs`.
106
107 ## Rate Limiting
108
109 **Location:** `src/helpers.rs` (config builders), `src/routes/api/mod.rs` (applied to routes)
110
111 Uses `tower-governor` with `SmartIpKeyExtractor` (reads X-Forwarded-For or direct IP).
112
113 ```rust
114 let write_rate_limit = rate_limiter_ms(
115 constants::API_WRITE_RATE_LIMIT_MS,
116 constants::API_WRITE_RATE_LIMIT_BURST,
117 );
118
119 let write_routes = Router::new()
120 .route("/api/projects", post(projects::create_project))
121 // ...
122 .route_layer(GovernorLayer { config: write_rate_limit });
123 ```
124
125 Rate limits are grouped by tier:
126 - **Write routes** -- mutations (create, update, delete)
127 - **Export routes** -- data export endpoints (lower burst)
128 - **License key routes** -- activation/validation (tight limits)
129 - **Dashboard GET routes** -- read-heavy dashboard endpoints
130
131 ## Template View Models
132
133 **Location:** `src/templates/` (public.rs, dashboard.rs, partials.rs)
134
135 Each template is an Askama struct with all the data it needs pre-loaded. No database calls happen in templates.
136
137 ```rust
138 #[derive(Template)]
139 #[template(path = "pages/project.html")]
140 pub struct ProjectTemplate {
141 pub csrf_token: CsrfTokenOption,
142 pub session_user: Option<SessionUser>,
143 pub project: Project,
144 pub creator_username: String,
145 pub items: Vec<Item>,
146 pub subscription_tiers: Vec<SubscriptionTier>,
147 pub is_following: bool,
148 pub follower_count: i64,
149 // ...
150 }
151 ```
152
153 ### Organization
154
155 | Module | Contents |
156 |--------|----------|
157 | `templates/public.rs` | Public pages (landing, auth, profiles, content) |
158 | `templates/dashboard.rs` | Creator dashboards, admin views |
159 | `templates/partials.rs` | HTMX fragments, tab content, alerts, form status |
160
161 ### Physical template files
162
163 ```
164 templates/
165 base.html Base layout with head, nav, footer
166 pages/ Full public pages (30+)
167 dashboards/ Creator + admin dashboards
168 partials/ HTMX fragments
169 tabs/ Tab content for dashboards
170 wizards/ Multi-step flows (join wizard)
171 steps/
172 ```
173
174 ### Common partial types
175
176 - `AlertTemplate` -- feedback messages with optional link (builder pattern)
177 - `FormStatusTemplate` -- inline success/error for HTMX form submissions
178 - Tab templates -- one struct per dashboard tab, loaded via HTMX on tab click
179
180 ## Route Organization
181
182 Routes are split by audience:
183
184 ```
185 routes/
186 api/ JSON endpoints (grouped by domain: items, projects, subscriptions, etc.)
187 pages/
188 public/ Public-facing HTML pages
189 dashboard/ Authenticated creator dashboard pages
190 feeds.rs RSS/Atom feeds
191 blog.rs Blog pages
192 auth.rs Login, signup, logout, password reset
193 git/ Source browser (directory module)
194 git_issues/ Issue tracker (directory module)
195 oauth.rs OAuth provider (for Multithreaded)
196 postmark/ Inbound email webhook (directory module)
197 storage/ File upload/download (presigned URLs, directory module)
198 stripe/ Stripe webhooks and Connect callbacks
199 synckit.rs SyncKit cloud sync + OTA API
200 ```
201
202 The `api/mod.rs` file composes all API sub-routers, applies rate limiting layers, and adds the JSON error middleware.
203
204 ## Database Conventions
205
206 - All tables use UUID primary keys (except `sync_log` which uses BIGSERIAL)
207 - Text enums stored as VARCHAR/TEXT, mapped via `impl_str_enum!`
208 - Timestamps are `TIMESTAMPTZ NOT NULL DEFAULT NOW()` unless nullable
209 - Foreign keys use `ON DELETE CASCADE` for owned relationships
210 - Compile-time checked queries via sqlx -- migrations auto-run on boot
211 - Each integration test creates and drops its own PostgreSQL database
212
213 ## Key Paths
214
215 | Pattern | Location |
216 |---------|----------|
217 | Enum macro | `src/db/enums.rs` |
218 | ID macro | `src/db/id_types.rs` |
219 | Template macro | `src/templates/mod.rs` |
220 | Error type | `src/error.rs` |
221 | HTMX helpers | `src/helpers.rs` |
222 | Rate limiters | `src/helpers.rs`, `src/routes/api/mod.rs` |
223 | ListResponse | `src/types/mod.rs` |
224 | Constants | `src/constants.rs` |
225