code wiki / _hdl_build / nx_adnet_submit.nx
nx_adnet_submit.nx source
↩ module page · 123 lines · 8797 B
1// nx_adnet_submit.nx -- LIB: the SELF-SERVE advertiser intake SURFACE (server-rendered, ZERO JS) for the
2// sovereign ad network. Completes the "businesses sign up to advertise" census axis with a real, frontend-
3// testable page: GET renders the form; POST parses the urlencoded body (the DRY nx_http_form_get_field the
4// login route uses), builds a full inventory row, VALIDATES it with the exact same fail-closed validators
5// the serving slot enforces (ass_valid_row: first-party img only, http(s)/path click only, clean slug), and
6// STAGES it to an operator-review journal. STAGED IS NOT LIVE: nothing an advertiser submits ever serves
7// until the operator approves it (ass_approve, a separate operator lane). B2B only; the schema has NO visitor
8// field -> no visitor identity can transit this path by construction. All reflected input is HTML-escaped.
9// license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_http_form.nx"
12import "_hdl_build/nx_adnet_selfserve.nx"
13
14// HTML-escape append (mirrors aslot_cat_esc semantics; local copy keeps this lib self-contained on its imports).
15func asf_cat_esc(dst: *u8, off: i64, s: *u8, cap: i64) -> i64 {
16 var o: i64 = off
17 var i: i64 = 0
18 while s[i] != (0 as u8) {
19 if o + 8 >= cap { return o }
20 let c: u8 = s[i]
21 var lit: i64 = 1
22 if c == (60 as u8) { o = ad_cat(dst, o, "<" as *u8); lit = 0 }
23 if c == (62 as u8) { if lit == 1 { o = ad_cat(dst, o, ">" as *u8); lit = 0 } }
24 if c == (38 as u8) { if lit == 1 { o = ad_cat(dst, o, "&" as *u8); lit = 0 } }
25 if c == (34 as u8) { if lit == 1 { o = ad_cat(dst, o, """ as *u8); lit = 0 } }
26 if c == (39 as u8) { if lit == 1 { dst[o] = 32 as u8; o = o + 1; lit = 0 } }
27 if lit == 1 { dst[o] = c; o = o + 1 }
28 i = i + 1
29 }
30 return o
31}
32
33// the shared page shell + form. `msg` (may be 0) is a status banner already-escaped by the caller;
34// slug/adv/img/click/section repopulate the fields (escaped) after a rejected POST.
35func asf_render(out: *u8, cap: i64, msg: *u8, slug: *u8, adv: *u8, img: *u8, click: *u8, section: *u8) -> i64 {
36 var o: i64 = 0
37 o = ad_cat(out, o, "<html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Advertise on Nishi - Self-Serve Intake</title>" as *u8)
38 o = ad_cat(out, o, "<style>body{font-family:system-ui,sans-serif;max-width:640px;margin:5vh auto;padding:0 20px;line-height:1.55;color:var(--fg,black)}label{display:block;margin:14px 0 4px;font-weight:600;font-size:.9rem}input{width:100%;padding:8px 10px;font-size:1rem;box-sizing:border-box}small{color:gray}.msg{padding:10px 14px;border-radius:6px;margin:12px 0;font-size:.95rem}.ok{background:honeydew;border-left:4px solid seagreen}.err{background:mistyrose;border-left:4px solid firebrick}button{margin-top:18px;padding:10px 22px;font-size:1rem;font-weight:600;cursor:pointer}.note{background:aliceblue;border-left:4px solid steelblue;padding:10px 14px;border-radius:6px;font-size:.9rem;margin:14px 0}</style></head><body class=\"nx-ad-optout\">" as *u8)
39 o = ad_cat(out, o, "<h1>Advertise on Nishi</h1><p>One labeled first-party ad per page, across the Nishi properties. No cross-site tracking, no auctions, no middlemen. Submit a campaign below - the operator reviews every submission before it serves.</p>" as *u8)
40 if (msg as i64) != 0 { o = ad_cat(out, o, msg) }
41 o = ad_cat(out, o, "<div class=\"note\">Your creative must be a <strong>first-party image</strong> hosted on Nishi (a path starting with <code>/</code>, e.g. <code>/synth/yourbanner.png</code>). Build one with the banner builder, then reference its path here. External image URLs are refused by design (no off-site beacons).</div>" as *u8)
42 o = ad_cat(out, o, "<form method=\"POST\" action=\"/ad/submit\">" as *u8)
43 o = ad_cat(out, o, "<label>Campaign slug <small>(lowercase letters, digits, - or _)</small></label><input name=\"slug\" value=\"" as *u8)
44 o = asf_cat_esc(out, o, slug, cap)
45 o = ad_cat(out, o, "\" maxlength=\"48\" required>" as *u8)
46 o = ad_cat(out, o, "<label>Business / advertiser name</label><input name=\"business\" value=\"" as *u8)
47 o = asf_cat_esc(out, o, adv, cap)
48 o = ad_cat(out, o, "\" maxlength=\"120\" required>" as *u8)
49 o = ad_cat(out, o, "<label>Creative image path <small>(first-party, starts with /)</small></label><input name=\"img\" value=\"" as *u8)
50 o = asf_cat_esc(out, o, img, cap)
51 o = ad_cat(out, o, "\" maxlength=\"200\" placeholder=\"/synth/yourbanner.png\" required>" as *u8)
52 o = ad_cat(out, o, "<label>Click destination <small>(https:// or a Nishi path)</small></label><input name=\"click\" value=\"" as *u8)
53 o = asf_cat_esc(out, o, click, cap)
54 o = ad_cat(out, o, "\" maxlength=\"200\" placeholder=\"https://your-site.example\" required>" as *u8)
55 o = ad_cat(out, o, "<label>Page section <small>(lowercase word: home, video, compare, or 'any')</small></label><input name=\"section\" value=\"" as *u8)
56 o = asf_cat_esc(out, o, section, cap)
57 o = ad_cat(out, o, "\" maxlength=\"64\" placeholder=\"any\" required>" as *u8)
58 o = ad_cat(out, o, "<button type=\"submit\">Submit for review</button></form>" as *u8)
59 o = ad_cat(out, o, "<p style=\"margin-top:2rem\"><small>Submissions are staged for operator review and never serve automatically. Billing is pay-on-results (CPM/CPC). See the <a href=\"/compare/ads\">ad network comparison</a> and the <a href=\"/synth/adnet_catalog.html\">property catalogue</a>.</small></p>" as *u8)
60 o = ad_cat(out, o, "</body></html>" as *u8)
61 if o + 2 >= cap { return 0 }
62 out[o] = 0 as u8
63 return o
64}
65
66// GET: the empty form. NOTE: an empty "" string LITERAL aliases the constant pool (NishiLang gotcha ->
67// it rendered a stray pooled vhost string in the fields); use a real zero-length mmap'd buffer instead.
68func asf_form_page(out: *u8, cap: i64) -> i64 {
69 let e: *u8 = sys_mmap(1)
70 e[0] = 0 as u8
71 return asf_render(out, cap, 0 as *u8, e, e, e, e, e)
72}
73
74// POST handler: parse -> build row -> validate -> stage. Returns rendered page bytes (confirmation or
75// re-populated form + error). subs_path = the operator-review journal (NON-public, daemon cwd).
76func asf_post(body: *u8, body_n: i64, subs_path: *u8, out: *u8, cap: i64) -> i64 {
77 let slug: *u8 = sys_mmap(256)
78 let adv: *u8 = sys_mmap(256)
79 let img: *u8 = sys_mmap(512)
80 let click: *u8 = sys_mmap(512)
81 let section: *u8 = sys_mmap(256)
82 let vl: *i64 = sys_mmap(8) as *i64
83 slug[0] = 0 as u8; adv[0] = 0 as u8; img[0] = 0 as u8; click[0] = 0 as u8; section[0] = 0 as u8
84 if nx_http_form_get_field(body, body_n, "slug" as *u8, 4, slug, 256, vl) != NXF_OK { slug[0] = 0 as u8 }
85 if nx_http_form_get_field(body, body_n, "business" as *u8, 8, adv, 256, vl) != NXF_OK { adv[0] = 0 as u8 }
86 if nx_http_form_get_field(body, body_n, "img" as *u8, 3, img, 512, vl) != NXF_OK { img[0] = 0 as u8 }
87 if nx_http_form_get_field(body, body_n, "click" as *u8, 5, click, 512, vl) != NXF_OK { click[0] = 0 as u8 }
88 if nx_http_form_get_field(body, body_n, "section" as *u8, 7, section, 256, vl) != NXF_OK { section[0] = 0 as u8 }
89
90 // build the inventory row: slug<TAB>advertiser<TAB>img<TAB>click<TAB>section<TAB>1
91 let row: *u8 = sys_mmap(2048)
92 var r: i64 = 0
93 r = ad_cat(row, r, slug)
94 row[r] = 9 as u8; r = r + 1
95 r = ad_cat(row, r, adv)
96 row[r] = 9 as u8; r = r + 1
97 r = ad_cat(row, r, img)
98 row[r] = 9 as u8; r = r + 1
99 r = ad_cat(row, r, click)
100 row[r] = 9 as u8; r = r + 1
101 r = ad_cat(row, r, section)
102 row[r] = 9 as u8; r = r + 1
103 row[r] = 49 as u8; r = r + 1
104 row[r] = 0 as u8
105
106 if ass_valid_row(row, r) == 0 {
107 let em: *u8 = "<div class=\"msg err\">Rejected: check that the slug is lowercase [a-z0-9_-], the image is a first-party path starting with /, the click is https:// or a / path (no javascript:/data:), and the section is a lowercase word. Nothing was staged.</div>" as *u8
108 return asf_render(out, cap, em, slug, adv, img, click, section)
109 }
110 if ass_submit(subs_path, row, r) != 1 {
111 let em2: *u8 = "<div class=\"msg err\">Could not stage the submission (server write issue). Nothing was staged; please retry.</div>" as *u8
112 return asf_render(out, cap, em2, slug, adv, img, click, section)
113 }
114 let okm: *u8 = sys_mmap(512)
115 var om: i64 = 0
116 om = ad_cat(okm, om, "<div class=\"msg ok\">Submitted for review: <strong>" as *u8)
117 om = asf_cat_esc(okm, om, slug, 512)
118 om = ad_cat(okm, om, "</strong>. The operator will review it before it serves. You can submit another below.</div>" as *u8)
119 okm[om] = 0 as u8
120 let e: *u8 = sys_mmap(1)
121 e[0] = 0 as u8
122 return asf_render(out, cap, okm, e, e, e, e, e)
123}