22import type { Request , Response } from "express" ;
33import type Database from "better-sqlite3" ;
44import { randomUUID } from "node:crypto" ;
5+ import { marked } from "marked" ;
56import passport , { requireAdmin , isGithubOAuthEnabled } from "../auth.js" ;
67import { syncLabNotesFromFs } from "../services/syncLabNotesFromFs.js" ;
78import { normalizeLocale , sha256Hex } from "../lib/helpers.js" ;
89
10+ marked . setOptions ( {
11+ gfm : true ,
12+ breaks : false , // ✅ strict
13+ } ) ;
14+
15+
916export function registerAdminRoutes ( app : any , db : Database . Database ) {
1017 // Must match your UI origin exactly (no trailing slash)
1118 const UI_BASE_URL = process . env . UI_BASE_URL ?? "http://localhost:8001" ;
@@ -121,13 +128,32 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
121128
122129 // ✅ Resolve canonical noteId by (slug, locale) to make upserts stable
123130 const existing = db
124- . prepare ( "SELECT id FROM lab_notes WHERE slug = ? AND locale = ?" )
125- . get ( slug , noteLocale ) as { id : string } | undefined ;
131+ . prepare ( "SELECT id, department_id, dept, type FROM lab_notes WHERE slug = ? AND locale = ?" )
132+ . get ( slug , noteLocale ) as
133+ | { id : string ; department_id : string | null ; dept : string | null ; type : string | null }
134+ | undefined ;
126135
127136 // If the row already exists, prefer its id over any incoming id.
128137 // This prevents “identity drift” where (slug, locale) updates a different id.
129138 const noteId = existing ?. id ?? id ?? randomUUID ( ) ;
130139
140+ const incomingDepartment =
141+ typeof department_id === "string" && department_id . trim ( ) ? department_id . trim ( ) : null ;
142+
143+ const incomingDept =
144+ typeof dept === "string" && dept . trim ( ) ? dept . trim ( ) : null ;
145+
146+ // Preserve existing if not provided, else default for brand-new notes
147+ const resolvedDepartment =
148+ incomingDepartment ?? existing ?. department_id ?? "SCMS" ;
149+
150+ const resolvedDept =
151+ incomingDept ?? existing ?. dept ?? null ;
152+
153+ // Type is identity-ish too; preserve if missing
154+ const resolvedType =
155+ ( typeof type === "string" && type . trim ( ) ? type . trim ( ) : null ) ?? existing ?. type ?? "labnote" ;
156+
131157 const tx = db . transaction ( ( ) => {
132158 // 1) Upsert metadata row (NO content_html writes, NO content_markdown column)
133159 db . prepare ( `
@@ -151,11 +177,11 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
151177 title=excluded.title,
152178 type=excluded.type,
153179 status=excluded.status,
154- dept= excluded.dept,
180+ dept = COALESCE( excluded.dept, lab_notes.dept) ,
155181 category=excluded.category,
156182 excerpt=excluded.excerpt,
157183 summary=excluded.summary,
158- department_id= excluded.department_id,
184+ department_id = COALESCE( excluded.department_id, lab_notes.department_id) ,
159185 shadow_density=excluded.shadow_density,
160186 coherence_score=excluded.coherence_score,
161187 safer_landing=excluded.safer_landing,
@@ -176,7 +202,7 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
176202 excerpt || "" ,
177203 summary || "" ,
178204
179- department_id || "SCMS" ,
205+ incomingDepartment ,
180206 shadow_density ?? 0 ,
181207 coherence_score ?? 1.0 ,
182208 safer_landing ? 1 : 0 ,
@@ -200,7 +226,7 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
200226 const nextRev = ( revRow ?. maxRev ?? 0 ) + 1 ;
201227
202228 // 4) Create revision row (ledger truth)
203- const revisionId = crypto . randomUUID ( ) ;
229+ const revisionId = randomUUID ( ) ;
204230
205231 const prevPointer = db
206232 . prepare ( `
@@ -219,7 +245,7 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
219245 status : noteStatus ,
220246 published : normalizedPublishedAt ?? undefined ,
221247 dept : dept ?? null ,
222- department_id : department_id || "SCMS" ,
248+ department_id : resolvedDepartment ,
223249 shadow_density : shadow_density ?? 0 ,
224250 coherence_score : coherence_score ?? 1.0 ,
225251 safer_landing : Boolean ( safer_landing ) ,
@@ -342,57 +368,63 @@ export function registerAdminRoutes(app: any, db: Database.Database) {
342368 // ---------------------------------------------------------------------------
343369 // Admin: Publish Lab Note (protected)
344370 // ---------------------------------------------------------------------------
345- app . post ( "/admin/notes/:id/publish" , requireAdmin , ( req : Request , res : Response ) => {
371+ // Admin: Publish by slug + locale
372+ app . post ( "/admin/notes/:slug/publish" , requireAdmin , ( req : Request , res : Response ) => {
346373 try {
347- const id = String ( req . params . id ?? "" ) . trim ( ) ;
348- if ( ! id ) return res . status ( 400 ) . json ( { error : "id is required" } ) ;
374+ const slug = String ( req . params . slug ?? "" ) . trim ( ) ;
375+ const locale = normalizeLocale ( String ( req . query . locale ?? "en" ) ) ;
376+ if ( ! slug ) return res . status ( 400 ) . json ( { error : "slug is required" } ) ;
349377
350378 const nowDate = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
351379
352- const result = db
353- . prepare (
354- `
355- UPDATE lab_notes
356- SET
357- status = 'published',
358- published_at = COALESCE(published_at, ?),
359- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
360- WHERE id = ?
361- `
362- )
363- . run ( nowDate , id ) ;
380+ const row = db
381+ . prepare ( `SELECT id FROM lab_notes WHERE slug = ? AND locale = ? LIMIT 1` )
382+ . get ( slug , locale ) as { id : string } | undefined ;
364383
365- if ( result . changes === 0 ) return res . status ( 404 ) . json ( { error : "Not found" } ) ;
366- return res . json ( { ok : true , id, status : "published" } ) ;
384+ if ( ! row ) return res . status ( 404 ) . json ( { error : "Not found" } ) ;
385+
386+ db . prepare ( `
387+ UPDATE lab_notes
388+ SET
389+ status = 'published',
390+ published_revision_id = current_revision_id,
391+ published_at = COALESCE(published_at, ?),
392+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
393+ WHERE id = ?
394+ ` ) . run ( nowDate , row . id ) ;
395+
396+ return res . json ( { ok : true , slug, locale, id : row . id , status : "published" } ) ;
367397 } catch ( e : any ) {
368398 return res . status ( 500 ) . json ( { error : "Database error" , details : String ( e ?. message ?? e ) } ) ;
369399 }
370400 } ) ;
371401
372-
373402 // ---------------------------------------------------------------------------
374403 // Admin: Un-publish Lab Note (protected)
375404 // ---------------------------------------------------------------------------
376- app . post ( "/admin/notes/:id /unpublish" , requireAdmin , ( req : Request , res : Response ) => {
405+ app . post ( "/admin/notes/:slug /unpublish" , requireAdmin , ( req : Request , res : Response ) => {
377406 try {
378- const id = String ( req . params . id ?? "" ) . trim ( ) ;
379- if ( ! id ) return res . status ( 400 ) . json ( { error : "id is required" } ) ;
407+ const slug = String ( req . params . slug ?? "" ) . trim ( ) ;
408+ const locale = normalizeLocale ( String ( req . query . locale ?? "en" ) ) ;
409+ if ( ! slug ) return res . status ( 400 ) . json ( { error : "slug is required" } ) ;
380410
381- const result = db
382- . prepare (
383- `
384- UPDATE lab_notes
385- SET
386- status = 'draft',
387- published_at = NULL,
388- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
389- WHERE id = ?
390- `
391- )
392- . run ( id ) ;
411+ const row = db
412+ . prepare ( `SELECT id FROM lab_notes WHERE slug = ? AND locale = ? LIMIT 1` )
413+ . get ( slug , locale ) as { id : string } | undefined ;
414+
415+ if ( ! row ) return res . status ( 404 ) . json ( { error : "Not found" } ) ;
393416
394- if ( result . changes === 0 ) return res . status ( 404 ) . json ( { error : "Not found" } ) ;
395- return res . json ( { ok : true , id, status : "draft" } ) ;
417+ db . prepare ( `
418+ UPDATE lab_notes
419+ SET
420+ status = 'draft',
421+ published_revision_id = NULL,
422+ published_at = NULL,
423+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
424+ WHERE id = ?
425+ ` ) . run ( row . id ) ;
426+
427+ return res . json ( { ok : true , slug, locale, id : row . id , status : "draft" } ) ;
396428 } catch ( e : any ) {
397429 return res . status ( 500 ) . json ( { error : "Database error" , details : String ( e ?. message ?? e ) } ) ;
398430 }
0 commit comments