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, "&lt;" as *u8); lit = 0 } 23 if c == (62 as u8) { if lit == 1 { o = ad_cat(dst, o, "&gt;" as *u8); lit = 0 } } 24 if c == (38 as u8) { if lit == 1 { o = ad_cat(dst, o, "&amp;" as *u8); lit = 0 } } 25 if c == (34 as u8) { if lit == 1 { o = ad_cat(dst, o, "&quot;" 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}