Skip to main content

max / makenotwork

Upload trust tiers, git repos, expanded test suite, and CI scripts Add upload trust tier system (Phase 10D): untrusted creators' uploads are held for admin review, trusted creators auto-publish. Includes admin review queue, trust/untrust toggles, and HeldForReview scan status. Add git_repos DB module and migration. Expand integration test coverage with adversarial, media upload, promo code, waitlist, appeal, broadcast, category, chapter, content insertion, preferences, project management, session revocation, and Stripe disconnect tests. Add CI runner and git SSH setup scripts. Remove sourcehut .build.yml. Various route and template refinements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-03-10 02:15 UTC
Commit: b8175647779656cb7a44c087dfdf20fc8b3c305a
Parent: 1d1f998
102 files changed, +7547 insertions, -979 deletions
@@ -3453,7 +3453,7 @@
3453 3453
3454 3454 [[package]]
3455 3455 name = "makenotwork"
3456 - version = "0.1.5"
3456 + version = "0.1.6"
3457 3457 dependencies = [
3458 3458 "ammonia",
3459 3459 "anyhow",
@@ -4601,9 +4601,9 @@
4601 4601
4602 4602 [[package]]
4603 4603 name = "quinn-proto"
4604 - version = "0.11.13"
4604 + version = "0.11.14"
4605 4605 source = "registry+https://github.com/rust-lang/crates.io-index"
4606 - checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
4606 + checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
4607 4607 dependencies = [
4608 4608 "bytes",
4609 4609 "getrandom 0.3.4",
@@ -23,8 +23,7 @@
23 23 - **Pay-what-you-want**: Buyer chooses the amount, optional minimum price
24 24 - **Subscriptions**: Monthly recurring tiers per project with Stripe billing (multiple tiers, active/inactive toggle)
25 25 - **License keys**: Auto-generated on purchase, configurable activation limits, machine tracking, public validation endpoint for software phone-home
26 - - **Discount codes**: Percentage or fixed-amount, item-scoped or seller-wide, usage limits, expiration dates, auto-apply via URL parameter
27 - - **Download codes**: Single-use codes for free access, optional max uses and expiration
26 + - **Promo codes**: Unified code system supporting percentage/fixed discounts, free access grants, and free trial periods for subscriptions. Item-scoped or project-wide, usage limits, expiration dates, auto-apply via URL parameter
28 27
29 28 ### Discovery & Organization
30 29
@@ -39,14 +38,17 @@
39 38 - **Library**: All purchased content in one place, download files, view license keys
40 39 - **Audio streaming**: In-browser player with chapter navigation
41 40 - **Text reader**: Clean typography, reading time estimate
42 - - **Contact sharing**: Opt-in email sharing with creators at purchase time
41 + - **Contact sharing**: Opt-in email sharing with creators at purchase time, revocable by the buyer
43 42 - **Free accounts**: Fans never pay for the platform, only for content they choose to buy
43 + - **Email notifications**: Sale alerts, follower alerts, new release announcements, new device login warnings (each individually toggleable)
44 44
45 45 ### Creator Dashboard
46 46
47 47 - **Project management**: Overview with revenue and sales stats, content tab, blog tab, settings, subscriptions
48 - - **Item management**: Settings, content editor, versions, tags, license keys, download codes, chapters
48 + - **Item management**: Settings, content editor, versions, tags, license keys, promo codes, chapters
49 49 - **Transactions**: Full purchase and sales history, filterable
50 + - **Contacts**: View fans who shared their email at purchase, with purchase count and total spent
51 + - **Broadcasts**: Send plain-text email updates to all your followers (rate-limited to one per 24 hours)
50 52 - **Data export**: All projects, items, blog posts, sales (CSV), and purchases (CSV) downloadable anytime
51 53 - **Custom links**: Add external links to your profile
52 54
@@ -74,10 +76,12 @@
74 76 - **Creator waitlist**: Invite-only launch with lottery waves and hand-picked approvals
75 77 - **Admin CLI** (`mnw-admin`): Command-line tool for waitlist management, creator approval, spam flagging, wave execution, stats, user suspension/unsuspension, appeal processing, revenue reports, transaction history, CSV data export, and S3 storage audits -- connects directly to the database, no web UI needed
76 78 - **Documentation**: Server-rendered from markdown, auto-linked cross-references
79 + - **Transactional email**: Password reset, email verification, purchase receipts, subscription lifecycle, sale and follower notifications via Postmark with bounce/complaint suppression
80 + - **Git source browser**: Browse server-hosted bare repositories with syntax highlighting
77 81 - **Health monitoring**: Real uptime tracking, database status, service connectivity checks
78 - - **Malware scanning**: ClamAV + VirusTotal hash lookup on file uploads
82 + - **Malware scanning**: ClamAV + YARA rules + MalwareBazaar hash lookup on file uploads
79 83 - **Creator guide**: 12-page documentation covering the full UX surface area
80 - - **619 automated tests**: Unit, integration, workflow, and health tests
84 + - **621 automated tests**: Unit, integration, workflow, and health tests
81 85
82 86 ### Developer Infrastructure (SyncKit)
83 87
@@ -95,12 +99,9 @@
95 99
96 100 Near-term work. No timelines because we ship when it's ready.
97 101
98 - - **Deploy to production** (server setup complete, final manual backup test remaining)
99 - - **Postmark email setup** (SPF/DKIM, bounce handling for transactional email)
100 - - **Free trial support** for subscription tiers
101 - - **Sale and follower notifications** (email alerts for creators)
102 - - **Contacts dashboard** (view fans who shared their email at purchase)
103 - - **Admin CLI expansion**: Broadcast sending (deferred until Postmark integration)
102 + - **Beta launch**: Final testing pass, onboard first creators
103 + - **Notification preferences UI**: Dashboard settings page for managing email notification toggles
104 + - **Admin CLI expansion**: Broadcast sending to all users for platform announcements
104 105
105 106 ---
106 107
@@ -130,7 +131,7 @@
130 131
131 132 ### Open source creator tools
132 133
133 - Integration with Sourcehut and GitHub for software creators. Sponsor tiers, license display, release hosting, build status badges.
134 + Built-in git hosting with source browser, plus GitHub integration for software creators. Sponsor tiers, license display, release hosting, build status badges.
134 135
135 136 ### Analytics
136 137
@@ -13,7 +13,7 @@
13 13 ## Repository
14 14
15 15 ```
16 - sr.ht/~maxmj/
16 + makenot.work/git/maxmj/
17 17 ```
18 18
19 19 Available for review:
@@ -170,7 +170,7 @@
170 170 - Check for vulnerabilities
171 171 - Report issues responsibly
172 172
173 - Repository: [sr.ht/~maxmj/](https://sr.ht/~maxmj/) (Sourcehut)
173 + Repository: [makenot.work/git/maxmj/](https://makenot.work/git/maxmj/)
174 174
175 175 ---
176 176
@@ -8,7 +8,7 @@
8 8 use crate::error::{AppError, Result};
9 9
10 10 /// Email service configuration
11 - #[derive(Clone, Debug)]
11 + #[derive(Clone)]
12 12 pub struct EmailConfig {
13 13 /// Postmark API token (optional, logs if not set)
14 14 pub postmark_token: Option<String>,
@@ -18,6 +18,16 @@
18 18 pub from_name: String,
19 19 }
20 20
21 + impl std::fmt::Debug for EmailConfig {
22 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 + f.debug_struct("EmailConfig")
24 + .field("postmark_token", &self.postmark_token.as_ref().map(|_| "[REDACTED]"))
25 + .field("from_address", &self.from_address)
26 + .field("from_name", &self.from_name)
27 + .finish()
28 + }
29 + }
30 +
21 31 impl EmailConfig {
22 32 /// Load email configuration from environment
23 33 pub fn from_env() -> Self {
@@ -36,11 +46,12 @@
36 46 pub struct EmailClient {
37 47 config: EmailConfig,
38 48 http_client: reqwest::Client,
49 + pool: Option<sqlx::PgPool>,
39 50 }
40 51
41 52 impl EmailClient {
42 53 /// Create a new email client
43 - pub fn new(config: EmailConfig) -> Self {
54 + pub fn new(config: EmailConfig, pool: Option<sqlx::PgPool>) -> Self {
44 55 let http_client = reqwest::Client::builder()
45 56 .timeout(std::time::Duration::from_secs(10))
46 57 .build()
@@ -49,6 +60,7 @@
49 60 EmailClient {
50 61 config,
51 62 http_client,
63 + pool,
52 64 }
53 65 }
54 66
@@ -590,6 +602,21 @@
590 602 body: &str,
591 603 extra_headers: &[(&str, String)],
592 604 ) -> Result<()> {
605 + // Check suppression list before sending
606 + if let Some(ref pool) = self.pool {
607 + match crate::db::email_suppressions::is_suppressed(pool, to).await {
608 + Ok(true) => {
609 + tracing::info!(recipient = %to, subject = %subject, "email skipped (suppressed)");
610 + return Ok(());
611 + }
612 + Ok(false) => {}
613 + Err(e) => {
614 + // Log but don't block sending on suppression check failure
615 + tracing::warn!(recipient = %to, error = %e, "suppression check failed, sending anyway");
616 + }
617 + }
618 + }
619 +
593 620 if let Some(ref token) = self.config.postmark_token {
594 621 self.send_via_postmark(token, to, subject, body, extra_headers).await
595 622 } else {
@@ -826,7 +853,7 @@
826 853 ///
827 854 /// * `action` — one of: `broadcast`, `release`, `sale`, `follower`, `login`
828 855 /// * `target` — for `broadcast`: the creator's user ID to unfollow;
829 - /// for preferences: same as `user_id`
856 + /// for preferences: same as `user_id`
830 857 pub fn generate_unsubscribe_url(
831 858 host_url: &str,
832 859 user_id: UserId,
D .build.yml -40
A CLAUDE.md +126
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file