diff --git a/.gitignore b/.gitignore index 9a2697a..f866521 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,5 @@ ecosystem.config.cjs /passenger-start.cjs /cloudflared.yml /config.yml -/coverage/ +coverage/ diff --git a/coverage/clover.xml b/coverage/clover.xml deleted file mode 100644 index 914345b..0000000 --- a/coverage/clover.xml +++ /dev/null @@ -1,484 +0,0 @@ - -<<<<<<< HEAD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -======= - - - ->>>>>>> origin/main - - diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json deleted file mode 100644 index aeb948d..0000000 --- a/coverage/coverage-final.json +++ /dev/null @@ -1,20 +0,0 @@ -<<<<<<< HEAD -{"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\auth.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\auth.ts","statementMap":{"0":{"start":{"line":9,"column":4},"end":{"line":13,"column":6}},"1":{"start":{"line":17,"column":4},"end":{"line":17,"column":54}},"2":{"start":{"line":17,"column":41},"end":{"line":17,"column":54}},"3":{"start":{"line":18,"column":4},"end":{"line":22,"column":6}},"4":{"start":{"line":26,"column":27},"end":{"line":26,"column":73}},"5":{"start":{"line":27,"column":4},"end":{"line":27,"column":53}},"6":{"start":{"line":27,"column":30},"end":{"line":27,"column":51}},"7":{"start":{"line":31,"column":4},"end":{"line":31,"column":48}},"8":{"start":{"line":31,"column":41},"end":{"line":31,"column":48}},"9":{"start":{"line":33,"column":20},"end":{"line":33,"column":44}},"10":{"start":{"line":34,"column":4},"end":{"line":40,"column":5}},"11":{"start":{"line":35,"column":8},"end":{"line":38,"column":10}},"12":{"start":{"line":39,"column":8},"end":{"line":39,"column":15}},"13":{"start":{"line":42,"column":21},"end":{"line":42,"column":52}},"14":{"start":{"line":43,"column":25},"end":{"line":43,"column":60}},"15":{"start":{"line":44,"column":24},"end":{"line":44,"column":58}},"16":{"start":{"line":46,"column":4},"end":{"line":55,"column":6}},"17":{"start":{"line":50,"column":32},"end":{"line":50,"column":67}},"18":{"start":{"line":51,"column":16},"end":{"line":51,"column":87}},"19":{"start":{"line":51,"column":62},"end":{"line":51,"column":87}},"20":{"start":{"line":52,"column":16},"end":{"line":52,"column":43}},"21":{"start":{"line":57,"column":4},"end":{"line":57,"column":66}},"22":{"start":{"line":57,"column":48},"end":{"line":57,"column":64}},"23":{"start":{"line":58,"column":4},"end":{"line":58,"column":68}},"24":{"start":{"line":58,"column":50},"end":{"line":58,"column":66}},"25":{"start":{"line":61,"column":35},"end":{"line":68,"column":1}},"26":{"start":{"line":66,"column":4},"end":{"line":66,"column":47}},"27":{"start":{"line":66,"column":33},"end":{"line":66,"column":47}},"28":{"start":{"line":67,"column":4},"end":{"line":67,"column":59}},"29":{"start":{"line":71,"column":16},"end":{"line":71,"column":66}},"30":{"start":{"line":72,"column":4},"end":{"line":72,"column":42}},"31":{"start":{"line":76,"column":4},"end":{"line":79,"column":25}},"32":{"start":{"line":78,"column":20},"end":{"line":78,"column":42}},"33":{"start":{"line":84,"column":4},"end":{"line":88,"column":7}},"34":{"start":{"line":98,"column":28},"end":{"line":128,"column":1}},"35":{"start":{"line":100,"column":4},"end":{"line":105,"column":5}},"36":{"start":{"line":104,"column":8},"end":{"line":104,"column":22}},"37":{"start":{"line":107,"column":18},"end":{"line":107,"column":39}},"38":{"start":{"line":110,"column":4},"end":{"line":112,"column":5}},"39":{"start":{"line":111,"column":8},"end":{"line":111,"column":71}},"40":{"start":{"line":114,"column":4},"end":{"line":116,"column":5}},"41":{"start":{"line":115,"column":8},"end":{"line":115,"column":63}},"42":{"start":{"line":118,"column":18},"end":{"line":118,"column":51}},"43":{"start":{"line":119,"column":4},"end":{"line":121,"column":5}},"44":{"start":{"line":120,"column":8},"end":{"line":120,"column":63}},"45":{"start":{"line":123,"column":4},"end":{"line":125,"column":5}},"46":{"start":{"line":124,"column":8},"end":{"line":124,"column":60}},"47":{"start":{"line":127,"column":4},"end":{"line":127,"column":18}}},"fnMap":{"0":{"name":"getGithubOAuthEnv","decl":{"start":{"line":8,"column":9},"end":{"line":8,"column":26}},"loc":{"start":{"line":8,"column":38},"end":{"line":14,"column":1}}},"1":{"name":"isGithubOAuthEnabled","decl":{"start":{"line":16,"column":16},"end":{"line":16,"column":36}},"loc":{"start":{"line":16,"column":36},"end":{"line":23,"column":1}}},"2":{"name":"missingGithubOAuthKeys","decl":{"start":{"line":25,"column":9},"end":{"line":25,"column":31}},"loc":{"start":{"line":25,"column":31},"end":{"line":28,"column":1}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":27,"column":23},"end":{"line":27,"column":24}},"loc":{"start":{"line":27,"column":30},"end":{"line":27,"column":51}}},"4":{"name":"configurePassport","decl":{"start":{"line":30,"column":16},"end":{"line":30,"column":33}},"loc":{"start":{"line":30,"column":33},"end":{"line":59,"column":1}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":49,"column":12},"end":{"line":49,"column":13}},"loc":{"start":{"line":49,"column":79},"end":{"line":53,"column":13}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":57,"column":27},"end":{"line":57,"column":28}},"loc":{"start":{"line":57,"column":48},"end":{"line":57,"column":64}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":58,"column":29},"end":{"line":58,"column":30}},"loc":{"start":{"line":58,"column":50},"end":{"line":58,"column":66}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":61,"column":35},"end":{"line":61,"column":null}},"loc":{"start":{"line":65,"column":4},"end":{"line":68,"column":1}}},"9":{"name":"getGithubLogin","decl":{"start":{"line":70,"column":16},"end":{"line":70,"column":30}},"loc":{"start":{"line":70,"column":40},"end":{"line":73,"column":1}}},"10":{"name":"parseAllowlist","decl":{"start":{"line":75,"column":9},"end":{"line":75,"column":23}},"loc":{"start":{"line":75,"column":41},"end":{"line":80,"column":1}}},"11":{"name":"(anonymous_11)","decl":{"start":{"line":78,"column":13},"end":{"line":78,"column":14}},"loc":{"start":{"line":78,"column":20},"end":{"line":78,"column":42}}},"12":{"name":"buildAdminAllowlist","decl":{"start":{"line":83,"column":9},"end":{"line":83,"column":28}},"loc":{"start":{"line":83,"column":28},"end":{"line":89,"column":1}}},"13":{"name":"(anonymous_13)","decl":{"start":{"line":98,"column":28},"end":{"line":98,"column":29}},"loc":{"start":{"line":98,"column":80},"end":{"line":128,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":8},"end":{"line":12,"column":42}},"type":"binary-expr","locations":[{"start":{"line":10,"column":8},"end":{"line":10,"column":42}},{"start":{"line":11,"column":8},"end":{"line":11,"column":46}},{"start":{"line":12,"column":8},"end":{"line":12,"column":42}}]},"1":{"loc":{"start":{"line":17,"column":4},"end":{"line":17,"column":54}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":17,"column":54}},{"start":{},"end":{}}]},"2":{"loc":{"start":{"line":19,"column":8},"end":{"line":21,"column":41}},"type":"binary-expr","locations":[{"start":{"line":19,"column":8},"end":{"line":19,"column":38}},{"start":{"line":20,"column":8},"end":{"line":20,"column":42}},{"start":{"line":21,"column":8},"end":{"line":21,"column":41}}]},"3":{"loc":{"start":{"line":31,"column":4},"end":{"line":31,"column":48}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":31,"column":48}},{"start":{},"end":{}}]},"4":{"loc":{"start":{"line":34,"column":4},"end":{"line":40,"column":5}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":40,"column":5}},{"start":{},"end":{}}]},"5":{"loc":{"start":{"line":51,"column":16},"end":{"line":51,"column":87}},"type":"if","locations":[{"start":{"line":51,"column":16},"end":{"line":51,"column":87}},{"start":{},"end":{}}]},"6":{"loc":{"start":{"line":51,"column":20},"end":{"line":51,"column":60}},"type":"binary-expr","locations":[{"start":{"line":51,"column":20},"end":{"line":51,"column":27}},{"start":{"line":51,"column":31},"end":{"line":51,"column":60}}]},"7":{"loc":{"start":{"line":66,"column":4},"end":{"line":66,"column":47}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":66,"column":47}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":71,"column":23},"end":{"line":71,"column":58}},"type":"binary-expr","locations":[{"start":{"line":71,"column":23},"end":{"line":71,"column":37}},{"start":{"line":71,"column":41},"end":{"line":71,"column":52}},{"start":{"line":71,"column":56},"end":{"line":71,"column":58}}]},"9":{"loc":{"start":{"line":72,"column":11},"end":{"line":72,"column":41}},"type":"cond-expr","locations":[{"start":{"line":72,"column":17},"end":{"line":72,"column":34}},{"start":{"line":72,"column":37},"end":{"line":72,"column":41}}]},"10":{"loc":{"start":{"line":76,"column":18},"end":{"line":76,"column":32}},"type":"binary-expr","locations":[{"start":{"line":76,"column":18},"end":{"line":76,"column":26}},{"start":{"line":76,"column":30},"end":{"line":76,"column":32}}]},"11":{"loc":{"start":{"line":100,"column":4},"end":{"line":105,"column":5}},"type":"if","locations":[{"start":{"line":100,"column":4},"end":{"line":105,"column":5}},{"start":{},"end":{}}]},"12":{"loc":{"start":{"line":101,"column":8},"end":{"line":102,"column":47}},"type":"binary-expr","locations":[{"start":{"line":101,"column":8},"end":{"line":101,"column":45}},{"start":{"line":102,"column":8},"end":{"line":102,"column":47}}]},"13":{"loc":{"start":{"line":110,"column":4},"end":{"line":112,"column":5}},"type":"if","locations":[{"start":{"line":110,"column":4},"end":{"line":112,"column":5}},{"start":{},"end":{}}]},"14":{"loc":{"start":{"line":110,"column":8},"end":{"line":110,"column":65}},"type":"binary-expr","locations":[{"start":{"line":110,"column":8},"end":{"line":110,"column":24}},{"start":{"line":110,"column":28},"end":{"line":110,"column":65}}]},"15":{"loc":{"start":{"line":114,"column":4},"end":{"line":116,"column":5}},"type":"if","locations":[{"start":{"line":114,"column":4},"end":{"line":116,"column":5}},{"start":{},"end":{}}]},"16":{"loc":{"start":{"line":119,"column":4},"end":{"line":121,"column":5}},"type":"if","locations":[{"start":{"line":119,"column":4},"end":{"line":121,"column":5}},{"start":{},"end":{}}]},"17":{"loc":{"start":{"line":123,"column":4},"end":{"line":125,"column":5}},"type":"if","locations":[{"start":{"line":123,"column":4},"end":{"line":125,"column":5}},{"start":{},"end":{}}]},"18":{"loc":{"start":{"line":123,"column":8},"end":{"line":123,"column":43}},"type":"binary-expr","locations":[{"start":{"line":123,"column":8},"end":{"line":123,"column":22}},{"start":{"line":123,"column":26},"end":{"line":123,"column":43}}]}},"s":{"0":0,"1":20,"2":20,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":7,"26":0,"27":0,"28":0,"29":0,"30":0,"31":12,"32":12,"33":4,"34":7,"35":17,"36":13,"37":4,"38":4,"39":0,"40":4,"41":4,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0},"f":{"0":0,"1":20,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":12,"11":12,"12":4,"13":17},"b":{"0":[0,0,0],"1":[20,0],"2":[0,0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0,0],"9":[0,0],"10":[12,8],"11":[13,4],"12":[17,17],"13":[0,4],"14":[4,0],"15":[4,0],"16":[0,0],"17":[0,0],"18":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db.ts","statementMap":{"0":{"start":{"line":12,"column":23},"end":{"line":12,"column":53}},"1":{"start":{"line":13,"column":22},"end":{"line":13,"column":46}},"2":{"start":{"line":17,"column":8},"end":{"line":19,"column":52}},"3":{"start":{"line":22,"column":8},"end":{"line":26,"column":31}},"4":{"start":{"line":29,"column":4},"end":{"line":31,"column":5}},"5":{"start":{"line":30,"column":8},"end":{"line":30,"column":71}},"6":{"start":{"line":33,"column":4},"end":{"line":33,"column":18}},"7":{"start":{"line":37,"column":20},"end":{"line":37,"column":76}},"8":{"start":{"line":38,"column":4},"end":{"line":38,"column":45}},"9":{"start":{"line":43,"column":4},"end":{"line":48,"column":5}},"10":{"start":{"line":50,"column":16},"end":{"line":52,"column":48}},"11":{"start":{"line":54,"column":14},"end":{"line":54,"column":37}},"12":{"start":{"line":55,"column":4},"end":{"line":55,"column":38}},"13":{"start":{"line":59,"column":4},"end":{"line":64,"column":5}},"14":{"start":{"line":66,"column":4},"end":{"line":70,"column":26}},"15":{"start":{"line":74,"column":16},"end":{"line":74,"column":80}},"16":{"start":{"line":77,"column":24},"end":{"line":77,"column":52}},"17":{"start":{"line":79,"column":4},"end":{"line":79,"column":35}},"18":{"start":{"line":80,"column":4},"end":{"line":80,"column":36}},"19":{"start":{"line":81,"column":4},"end":{"line":83,"column":5}},"20":{"start":{"line":82,"column":8},"end":{"line":82,"column":37}},"21":{"start":{"line":87,"column":4},"end":{"line":95,"column":7}},"22":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"23":{"start":{"line":100,"column":4},"end":{"line":100,"column":75}},"24":{"start":{"line":109,"column":19},"end":{"line":109,"column":43}},"25":{"start":{"line":110,"column":19},"end":{"line":110,"column":31}},"26":{"start":{"line":111,"column":17},"end":{"line":111,"column":34}},"27":{"start":{"line":112,"column":19},"end":{"line":112,"column":23}},"28":{"start":{"line":115,"column":21},"end":{"line":119,"column":48}},"29":{"start":{"line":121,"column":4},"end":{"line":121,"column":30}},"30":{"start":{"line":121,"column":23},"end":{"line":121,"column":30}},"31":{"start":{"line":124,"column":4},"end":{"line":218,"column":6}},"32":{"start":{"line":221,"column":23},"end":{"line":221,"column":42}},"33":{"start":{"line":223,"column":24},"end":{"line":238,"column":6}},"34":{"start":{"line":241,"column":8},"end":{"line":258,"column":2}},"35":{"start":{"line":260,"column":22},"end":{"line":260,"column":79}},"36":{"start":{"line":261,"column":24},"end":{"line":261,"column":44}},"37":{"start":{"line":263,"column":4},"end":{"line":301,"column":6}},"38":{"start":{"line":304,"column":4},"end":{"line":310,"column":49}},"39":{"start":{"line":314,"column":16},"end":{"line":314,"column":96}},"40":{"start":{"line":315,"column":4},"end":{"line":315,"column":27}}},"fnMap":{"0":{"name":"resolveDbPath","decl":{"start":{"line":11,"column":16},"end":{"line":11,"column":29}},"loc":{"start":{"line":11,"column":29},"end":{"line":34,"column":1}}},"1":{"name":"openDb","decl":{"start":{"line":36,"column":16},"end":{"line":36,"column":22}},"loc":{"start":{"line":36,"column":37},"end":{"line":39,"column":1}}},"2":{"name":"getLabNotesSchemaVersion","decl":{"start":{"line":41,"column":16},"end":{"line":41,"column":40}},"loc":{"start":{"line":41,"column":62},"end":{"line":56,"column":1}}},"3":{"name":"setLabNotesSchemaVersion","decl":{"start":{"line":58,"column":16},"end":{"line":58,"column":40}},"loc":{"start":{"line":58,"column":79},"end":{"line":71,"column":1}}},"4":{"name":"bootstrapDb","decl":{"start":{"line":73,"column":16},"end":{"line":73,"column":27}},"loc":{"start":{"line":73,"column":49},"end":{"line":97,"column":1}}},"5":{"name":"sha256Hex","decl":{"start":{"line":99,"column":9},"end":{"line":99,"column":18}},"loc":{"start":{"line":99,"column":32},"end":{"line":101,"column":1}}},"6":{"name":"seedMarkerNote","decl":{"start":{"line":108,"column":16},"end":{"line":108,"column":30}},"loc":{"start":{"line":108,"column":52},"end":{"line":311,"column":1}}},"7":{"name":"isDbEmpty","decl":{"start":{"line":313,"column":16},"end":{"line":313,"column":25}},"loc":{"start":{"line":313,"column":47},"end":{"line":316,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":8},"end":{"line":19,"column":52}},"type":"cond-expr","locations":[{"start":{"line":18,"column":14},"end":{"line":18,"column":56}},{"start":{"line":19,"column":14},"end":{"line":19,"column":52}}]},"1":{"loc":{"start":{"line":22,"column":8},"end":{"line":26,"column":31}},"type":"cond-expr","locations":[{"start":{"line":23,"column":14},"end":{"line":23,"column":24}},{"start":{"line":24,"column":14},"end":{"line":26,"column":31}}]},"2":{"loc":{"start":{"line":24,"column":14},"end":{"line":26,"column":31}},"type":"cond-expr","locations":[{"start":{"line":25,"column":18},"end":{"line":25,"column":43}},{"start":{"line":26,"column":18},"end":{"line":26,"column":31}}]},"3":{"loc":{"start":{"line":29,"column":4},"end":{"line":31,"column":5}},"type":"if","locations":[{"start":{"line":29,"column":4},"end":{"line":31,"column":5}},{"start":{},"end":{}}]},"4":{"loc":{"start":{"line":29,"column":8},"end":{"line":29,"column":56}},"type":"binary-expr","locations":[{"start":{"line":29,"column":8},"end":{"line":29,"column":31}},{"start":{"line":29,"column":35},"end":{"line":29,"column":56}}]},"5":{"loc":{"start":{"line":37,"column":20},"end":{"line":37,"column":76}},"type":"cond-expr","locations":[{"start":{"line":37,"column":53},"end":{"line":37,"column":64}},{"start":{"line":37,"column":67},"end":{"line":37,"column":76}}]},"6":{"loc":{"start":{"line":54,"column":21},"end":{"line":54,"column":36}},"type":"binary-expr","locations":[{"start":{"line":54,"column":21},"end":{"line":54,"column":31}},{"start":{"line":54,"column":35},"end":{"line":54,"column":36}}]},"7":{"loc":{"start":{"line":55,"column":11},"end":{"line":55,"column":37}},"type":"cond-expr","locations":[{"start":{"line":55,"column":32},"end":{"line":55,"column":33}},{"start":{"line":55,"column":36},"end":{"line":55,"column":37}}]},"8":{"loc":{"start":{"line":74,"column":16},"end":{"line":74,"column":80}},"type":"cond-expr","locations":[{"start":{"line":74,"column":57},"end":{"line":74,"column":68}},{"start":{"line":74,"column":71},"end":{"line":74,"column":80}}]},"9":{"loc":{"start":{"line":81,"column":4},"end":{"line":83,"column":5}},"type":"if","locations":[{"start":{"line":81,"column":4},"end":{"line":83,"column":5}},{"start":{},"end":{}}]},"10":{"loc":{"start":{"line":121,"column":4},"end":{"line":121,"column":30}},"type":"if","locations":[{"start":{"line":121,"column":4},"end":{"line":121,"column":30}},{"start":{},"end":{}}]}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":19,"8":19,"9":19,"10":19,"11":19,"12":19,"13":19,"14":19,"15":19,"16":19,"17":19,"18":19,"19":19,"20":19,"21":19,"22":19,"23":19,"24":19,"25":19,"26":19,"27":19,"28":19,"29":19,"30":0,"31":19,"32":19,"33":19,"34":19,"35":19,"36":19,"37":19,"38":19,"39":0,"40":0},"f":{"0":0,"1":19,"2":19,"3":19,"4":19,"5":19,"6":19,"7":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,19],"6":[19,19],"7":[19,0],"8":[0,19],"9":[19,0],"10":[0,19]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\env.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\env.ts","statementMap":{"0":{"start":{"line":32,"column":8},"end":{"line":32,"column":23}},"1":{"start":{"line":33,"column":8},"end":{"line":33,"column":31}},"2":{"start":{"line":38,"column":19},"end":{"line":38,"column":56}},"3":{"start":{"line":41,"column":0},"end":{"line":58,"column":1}},"4":{"start":{"line":43,"column":4},"end":{"line":43,"column":62}},"5":{"start":{"line":44,"column":4},"end":{"line":48,"column":7}},"6":{"start":{"line":50,"column":20},"end":{"line":50,"column":40}},"7":{"start":{"line":51,"column":20},"end":{"line":51,"column":53}},"8":{"start":{"line":52,"column":4},"end":{"line":54,"column":5}},"9":{"start":{"line":53,"column":8},"end":{"line":53,"column":57}},"10":{"start":{"line":57,"column":4},"end":{"line":57,"column":42}},"11":{"start":{"line":62,"column":14},"end":{"line":62,"column":87}},"12":{"start":{"line":63,"column":4},"end":{"line":63,"column":76}},"13":{"start":{"line":63,"column":67},"end":{"line":63,"column":76}},"14":{"start":{"line":64,"column":4},"end":{"line":64,"column":104}},"15":{"start":{"line":68,"column":4},"end":{"line":68,"column":62}},"16":{"start":{"line":68,"column":46},"end":{"line":68,"column":62}},"17":{"start":{"line":69,"column":14},"end":{"line":69,"column":27}},"18":{"start":{"line":70,"column":4},"end":{"line":72,"column":5}},"19":{"start":{"line":71,"column":8},"end":{"line":71,"column":93}},"20":{"start":{"line":73,"column":4},"end":{"line":73,"column":13}},"21":{"start":{"line":78,"column":21},"end":{"line":78,"column":70}},"22":{"start":{"line":79,"column":17},"end":{"line":79,"column":44}},"23":{"start":{"line":81,"column":20},"end":{"line":81,"column":48}},"24":{"start":{"line":83,"column":4},"end":{"line":87,"column":5}},"25":{"start":{"line":84,"column":8},"end":{"line":86,"column":10}},"26":{"start":{"line":89,"column":27},"end":{"line":89,"column":55}},"27":{"start":{"line":90,"column":4},"end":{"line":92,"column":5}},"28":{"start":{"line":91,"column":8},"end":{"line":91,"column":95}},"29":{"start":{"line":94,"column":4},"end":{"line":109,"column":6}},"30":{"start":{"line":112,"column":19},"end":{"line":112,"column":43}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":31,"column":4},"end":{"line":31,"column":16}},"loc":{"start":{"line":31,"column":31},"end":{"line":34,"column":5}}},"1":{"name":"normalizeNodeEnv","decl":{"start":{"line":61,"column":9},"end":{"line":61,"column":25}},"loc":{"start":{"line":61,"column":39},"end":{"line":65,"column":1}}},"2":{"name":"parsePort","decl":{"start":{"line":67,"column":9},"end":{"line":67,"column":18}},"loc":{"start":{"line":67,"column":62},"end":{"line":74,"column":1}}},"3":{"name":"validateEnv","decl":{"start":{"line":77,"column":9},"end":{"line":77,"column":20}},"loc":{"start":{"line":77,"column":45},"end":{"line":110,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":38,"column":19},"end":{"line":38,"column":56}},"type":"binary-expr","locations":[{"start":{"line":38,"column":19},"end":{"line":38,"column":39}},{"start":{"line":38,"column":43},"end":{"line":38,"column":56}}]},"1":{"loc":{"start":{"line":41,"column":0},"end":{"line":58,"column":1}},"type":"if","locations":[{"start":{"line":41,"column":0},"end":{"line":58,"column":1}},{"start":{},"end":{}}]},"2":{"loc":{"start":{"line":41,"column":4},"end":{"line":41,"column":58}},"type":"binary-expr","locations":[{"start":{"line":41,"column":4},"end":{"line":41,"column":33}},{"start":{"line":41,"column":37},"end":{"line":41,"column":58}}]},"3":{"loc":{"start":{"line":45,"column":18},"end":{"line":45,"column":46}},"type":"binary-expr","locations":[{"start":{"line":45,"column":18},"end":{"line":45,"column":38}},{"start":{"line":45,"column":42},"end":{"line":45,"column":46}}]},"4":{"loc":{"start":{"line":52,"column":4},"end":{"line":54,"column":5}},"type":"if","locations":[{"start":{"line":52,"column":4},"end":{"line":54,"column":5}},{"start":{},"end":{}}]},"5":{"loc":{"start":{"line":62,"column":14},"end":{"line":62,"column":87}},"type":"cond-expr","locations":[{"start":{"line":62,"column":32},"end":{"line":62,"column":45}},{"start":{"line":62,"column":48},"end":{"line":62,"column":87}}]},"6":{"loc":{"start":{"line":62,"column":48},"end":{"line":62,"column":87}},"type":"cond-expr","locations":[{"start":{"line":62,"column":67},"end":{"line":62,"column":79}},{"start":{"line":62,"column":82},"end":{"line":62,"column":87}}]},"7":{"loc":{"start":{"line":63,"column":4},"end":{"line":63,"column":76}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":63,"column":76}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":63,"column":8},"end":{"line":63,"column":65}},"type":"binary-expr","locations":[{"start":{"line":63,"column":8},"end":{"line":63,"column":27}},{"start":{"line":63,"column":31},"end":{"line":63,"column":43}},{"start":{"line":63,"column":47},"end":{"line":63,"column":65}}]},"9":{"loc":{"start":{"line":68,"column":4},"end":{"line":68,"column":62}},"type":"if","locations":[{"start":{"line":68,"column":4},"end":{"line":68,"column":62}},{"start":{},"end":{}}]},"10":{"loc":{"start":{"line":68,"column":8},"end":{"line":68,"column":44}},"type":"binary-expr","locations":[{"start":{"line":68,"column":8},"end":{"line":68,"column":21}},{"start":{"line":68,"column":25},"end":{"line":68,"column":44}}]},"11":{"loc":{"start":{"line":70,"column":4},"end":{"line":72,"column":5}},"type":"if","locations":[{"start":{"line":70,"column":4},"end":{"line":72,"column":5}},{"start":{},"end":{}}]},"12":{"loc":{"start":{"line":70,"column":8},"end":{"line":70,"column":74}},"type":"binary-expr","locations":[{"start":{"line":70,"column":8},"end":{"line":70,"column":27}},{"start":{"line":70,"column":31},"end":{"line":70,"column":51}},{"start":{"line":70,"column":55},"end":{"line":70,"column":61}},{"start":{"line":70,"column":65},"end":{"line":70,"column":74}}]},"13":{"loc":{"start":{"line":78,"column":38},"end":{"line":78,"column":69}},"type":"binary-expr","locations":[{"start":{"line":78,"column":38},"end":{"line":78,"column":52}},{"start":{"line":78,"column":56},"end":{"line":78,"column":69}}]},"14":{"loc":{"start":{"line":81,"column":21},"end":{"line":81,"column":40}},"type":"binary-expr","locations":[{"start":{"line":81,"column":21},"end":{"line":81,"column":34}},{"start":{"line":81,"column":38},"end":{"line":81,"column":40}}]},"15":{"loc":{"start":{"line":83,"column":4},"end":{"line":87,"column":5}},"type":"if","locations":[{"start":{"line":83,"column":4},"end":{"line":87,"column":5}},{"start":{},"end":{}}]},"16":{"loc":{"start":{"line":83,"column":8},"end":{"line":83,"column":71}},"type":"binary-expr","locations":[{"start":{"line":83,"column":8},"end":{"line":83,"column":27}},{"start":{"line":83,"column":31},"end":{"line":83,"column":45}},{"start":{"line":83,"column":49},"end":{"line":83,"column":71}}]},"17":{"loc":{"start":{"line":90,"column":4},"end":{"line":92,"column":5}},"type":"if","locations":[{"start":{"line":90,"column":4},"end":{"line":92,"column":5}},{"start":{},"end":{}}]},"18":{"loc":{"start":{"line":90,"column":8},"end":{"line":90,"column":84}},"type":"binary-expr","locations":[{"start":{"line":90,"column":8},"end":{"line":90,"column":33}},{"start":{"line":90,"column":38},"end":{"line":90,"column":53}},{"start":{"line":90,"column":57},"end":{"line":90,"column":83}}]}},"s":{"0":0,"1":0,"2":7,"3":7,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":7,"12":7,"13":7,"14":0,"15":7,"16":7,"17":0,"18":0,"19":0,"20":0,"21":7,"22":7,"23":7,"24":7,"25":0,"26":7,"27":7,"28":0,"29":7,"30":7},"f":{"0":0,"1":7,"2":7,"3":7},"b":{"0":[7,0],"1":[0,7],"2":[7,7],"3":[0,0],"4":[0,0],"5":[0,7],"6":[0,7],"7":[7,0],"8":[7,7,0],"9":[7,0],"10":[7,0],"11":[0,0],"12":[0,0,0,0],"13":[7,0],"14":[7,7],"15":[0,7],"16":[7,7,0],"17":[0,7],"18":[7,0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\auth\\tokens.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\auth\\tokens.ts","statementMap":{"0":{"start":{"line":16,"column":4},"end":{"line":16,"column":36}},"1":{"start":{"line":21,"column":4},"end":{"line":25,"column":29}},"2":{"start":{"line":30,"column":14},"end":{"line":30,"column":38}},"3":{"start":{"line":31,"column":4},"end":{"line":37,"column":5}},"4":{"start":{"line":33,"column":8},"end":{"line":35,"column":9}},"5":{"start":{"line":34,"column":12},"end":{"line":34,"column":55}},"6":{"start":{"line":36,"column":8},"end":{"line":36,"column":28}},"7":{"start":{"line":38,"column":4},"end":{"line":38,"column":13}},"8":{"start":{"line":42,"column":19},"end":{"line":42,"column":32}},"9":{"start":{"line":43,"column":4},"end":{"line":43,"column":93}},"10":{"start":{"line":48,"column":17},"end":{"line":48,"column":50}},"11":{"start":{"line":49,"column":19},"end":{"line":49,"column":84}},"12":{"start":{"line":50,"column":4},"end":{"line":50,"column":30}},"13":{"start":{"line":62,"column":15},"end":{"line":62,"column":34}},"14":{"start":{"line":63,"column":18},"end":{"line":63,"column":36}},"15":{"start":{"line":64,"column":23},"end":{"line":64,"column":39}},"16":{"start":{"line":66,"column":4},"end":{"line":79,"column":6}},"17":{"start":{"line":82,"column":4},"end":{"line":82,"column":25}},"18":{"start":{"line":89,"column":23},"end":{"line":89,"column":42}},"19":{"start":{"line":91,"column":16},"end":{"line":96,"column":39}},"20":{"start":{"line":98,"column":4},"end":{"line":98,"column":26}},"21":{"start":{"line":98,"column":14},"end":{"line":98,"column":26}},"22":{"start":{"line":99,"column":4},"end":{"line":99,"column":41}},"23":{"start":{"line":99,"column":29},"end":{"line":99,"column":41}},"24":{"start":{"line":101,"column":4},"end":{"line":104,"column":5}},"25":{"start":{"line":102,"column":20},"end":{"line":102,"column":46}},"26":{"start":{"line":103,"column":8},"end":{"line":103,"column":67}},"27":{"start":{"line":103,"column":55},"end":{"line":103,"column":67}},"28":{"start":{"line":106,"column":19},"end":{"line":106,"column":54}},"29":{"start":{"line":109,"column":4},"end":{"line":109,"column":92}},"30":{"start":{"line":111,"column":4},"end":{"line":123,"column":6}},"31":{"start":{"line":127,"column":4},"end":{"line":127,"column":55}},"32":{"start":{"line":127,"column":45},"end":{"line":127,"column":55}},"33":{"start":{"line":128,"column":4},"end":{"line":134,"column":5}},"34":{"start":{"line":129,"column":18},"end":{"line":129,"column":35}},"35":{"start":{"line":130,"column":8},"end":{"line":130,"column":76}},"36":{"start":{"line":130,"column":30},"end":{"line":130,"column":76}},"37":{"start":{"line":130,"column":53},"end":{"line":130,"column":74}},"38":{"start":{"line":131,"column":8},"end":{"line":131,"column":18}},"39":{"start":{"line":133,"column":8},"end":{"line":133,"column":18}}},"fnMap":{"0":{"name":"nowIso","decl":{"start":{"line":15,"column":9},"end":{"line":15,"column":15}},"loc":{"start":{"line":15,"column":15},"end":{"line":17,"column":1}}},"1":{"name":"base64Url","decl":{"start":{"line":20,"column":9},"end":{"line":20,"column":18}},"loc":{"start":{"line":20,"column":30},"end":{"line":26,"column":1}}},"2":{"name":"tokenPepper","decl":{"start":{"line":29,"column":9},"end":{"line":29,"column":20}},"loc":{"start":{"line":29,"column":20},"end":{"line":39,"column":1}}},"3":{"name":"hashToken","decl":{"start":{"line":41,"column":16},"end":{"line":41,"column":25}},"loc":{"start":{"line":41,"column":42},"end":{"line":44,"column":1}}},"4":{"name":"generateRawToken","decl":{"start":{"line":46,"column":16},"end":{"line":46,"column":32}},"loc":{"start":{"line":46,"column":32},"end":{"line":51,"column":1}}},"5":{"name":"mintApiToken","decl":{"start":{"line":53,"column":16},"end":{"line":53,"column":28}},"loc":{"start":{"line":60,"column":5},"end":{"line":83,"column":1}}},"6":{"name":"verifyApiToken","decl":{"start":{"line":85,"column":16},"end":{"line":85,"column":30}},"loc":{"start":{"line":87,"column":20},"end":{"line":124,"column":1}}},"7":{"name":"safeParseJsonArray","decl":{"start":{"line":126,"column":9},"end":{"line":126,"column":27}},"loc":{"start":{"line":126,"column":38},"end":{"line":135,"column":1}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":130,"column":46},"end":{"line":130,"column":47}},"loc":{"start":{"line":130,"column":53},"end":{"line":130,"column":74}}}},"branchMap":{"0":{"loc":{"start":{"line":31,"column":4},"end":{"line":37,"column":5}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":37,"column":5}},{"start":{},"end":{}}]},"1":{"loc":{"start":{"line":31,"column":8},"end":{"line":31,"column":23}},"type":"binary-expr","locations":[{"start":{"line":31,"column":8},"end":{"line":31,"column":10}},{"start":{"line":31,"column":14},"end":{"line":31,"column":23}}]},"2":{"loc":{"start":{"line":33,"column":8},"end":{"line":35,"column":9}},"type":"if","locations":[{"start":{"line":33,"column":8},"end":{"line":35,"column":9}},{"start":{},"end":{}}]},"3":{"loc":{"start":{"line":49,"column":19},"end":{"line":49,"column":84}},"type":"cond-expr","locations":[{"start":{"line":49,"column":59},"end":{"line":49,"column":70}},{"start":{"line":49,"column":73},"end":{"line":49,"column":84}}]},"4":{"loc":{"start":{"line":75,"column":23},"end":{"line":75,"column":41}},"type":"binary-expr","locations":[{"start":{"line":75,"column":23},"end":{"line":75,"column":35}},{"start":{"line":75,"column":39},"end":{"line":75,"column":41}}]},"5":{"loc":{"start":{"line":76,"column":8},"end":{"line":76,"column":32}},"type":"binary-expr","locations":[{"start":{"line":76,"column":8},"end":{"line":76,"column":24}},{"start":{"line":76,"column":28},"end":{"line":76,"column":32}}]},"6":{"loc":{"start":{"line":77,"column":8},"end":{"line":77,"column":37}},"type":"binary-expr","locations":[{"start":{"line":77,"column":8},"end":{"line":77,"column":29}},{"start":{"line":77,"column":33},"end":{"line":77,"column":37}}]},"7":{"loc":{"start":{"line":98,"column":4},"end":{"line":98,"column":26}},"type":"if","locations":[{"start":{"line":98,"column":4},"end":{"line":98,"column":26}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":99,"column":4},"end":{"line":99,"column":41}},"type":"if","locations":[{"start":{"line":99,"column":4},"end":{"line":99,"column":41}},{"start":{},"end":{}}]},"9":{"loc":{"start":{"line":101,"column":4},"end":{"line":104,"column":5}},"type":"if","locations":[{"start":{"line":101,"column":4},"end":{"line":104,"column":5}},{"start":{},"end":{}}]},"10":{"loc":{"start":{"line":103,"column":8},"end":{"line":103,"column":67}},"type":"if","locations":[{"start":{"line":103,"column":8},"end":{"line":103,"column":67}},{"start":{},"end":{}}]},"11":{"loc":{"start":{"line":103,"column":12},"end":{"line":103,"column":53}},"type":"binary-expr","locations":[{"start":{"line":103,"column":12},"end":{"line":103,"column":32}},{"start":{"line":103,"column":36},"end":{"line":103,"column":53}}]},"12":{"loc":{"start":{"line":117,"column":24},"end":{"line":117,"column":46}},"type":"binary-expr","locations":[{"start":{"line":117,"column":24},"end":{"line":117,"column":38}},{"start":{"line":117,"column":42},"end":{"line":117,"column":46}}]},"13":{"loc":{"start":{"line":118,"column":29},"end":{"line":118,"column":56}},"type":"binary-expr","locations":[{"start":{"line":118,"column":29},"end":{"line":118,"column":48}},{"start":{"line":118,"column":52},"end":{"line":118,"column":56}}]},"14":{"loc":{"start":{"line":119,"column":26},"end":{"line":119,"column":50}},"type":"binary-expr","locations":[{"start":{"line":119,"column":26},"end":{"line":119,"column":42}},{"start":{"line":119,"column":46},"end":{"line":119,"column":50}}]},"15":{"loc":{"start":{"line":127,"column":4},"end":{"line":127,"column":55}},"type":"if","locations":[{"start":{"line":127,"column":4},"end":{"line":127,"column":55}},{"start":{},"end":{}}]},"16":{"loc":{"start":{"line":127,"column":8},"end":{"line":127,"column":43}},"type":"binary-expr","locations":[{"start":{"line":127,"column":8},"end":{"line":127,"column":14}},{"start":{"line":127,"column":18},"end":{"line":127,"column":43}}]},"17":{"loc":{"start":{"line":130,"column":8},"end":{"line":130,"column":76}},"type":"if","locations":[{"start":{"line":130,"column":8},"end":{"line":130,"column":76}},{"start":{},"end":{}}]}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrateApiTokens.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrateApiTokens.ts","statementMap":{"0":{"start":{"line":4,"column":41},"end":{"line":4,"column":42}},"1":{"start":{"line":7,"column":16},"end":{"line":9,"column":48}},"2":{"start":{"line":10,"column":4},"end":{"line":10,"column":46}},"3":{"start":{"line":14,"column":4},"end":{"line":18,"column":26}},"4":{"start":{"line":23,"column":4},"end":{"line":28,"column":5}},"5":{"start":{"line":30,"column":17},"end":{"line":30,"column":31}},"6":{"start":{"line":31,"column":4},"end":{"line":31,"column":50}},"7":{"start":{"line":31,"column":43},"end":{"line":31,"column":50}},"8":{"start":{"line":33,"column":4},"end":{"line":56,"column":5}},"9":{"start":{"line":58,"column":4},"end":{"line":58,"column":46}}},"fnMap":{"0":{"name":"getVersion","decl":{"start":{"line":6,"column":9},"end":{"line":6,"column":19}},"loc":{"start":{"line":6,"column":41},"end":{"line":11,"column":1}}},"1":{"name":"setVersion","decl":{"start":{"line":13,"column":9},"end":{"line":13,"column":19}},"loc":{"start":{"line":13,"column":58},"end":{"line":19,"column":1}}},"2":{"name":"migrateApiTokensSchema","decl":{"start":{"line":21,"column":16},"end":{"line":21,"column":38}},"loc":{"start":{"line":21,"column":105},"end":{"line":59,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":11},"end":{"line":10,"column":45}},"type":"cond-expr","locations":[{"start":{"line":10,"column":24},"end":{"line":10,"column":41}},{"start":{"line":10,"column":44},"end":{"line":10,"column":45}}]},"1":{"loc":{"start":{"line":31,"column":4},"end":{"line":31,"column":50}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":31,"column":50}},{"start":{},"end":{}}]}},"s":{"0":7,"1":19,"2":19,"3":19,"4":19,"5":19,"6":19,"7":0,"8":19,"9":19},"f":{"0":19,"1":19,"2":19},"b":{"0":[0,19],"1":[0,19]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrateLabNotes.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrateLabNotes.ts","statementMap":{"0":{"start":{"line":5,"column":40},"end":{"line":5,"column":41}},"1":{"start":{"line":8,"column":16},"end":{"line":10,"column":48}},"2":{"start":{"line":12,"column":4},"end":{"line":12,"column":47}},"3":{"start":{"line":12,"column":40},"end":{"line":12,"column":47}},"4":{"start":{"line":14,"column":4},"end":{"line":18,"column":28}},"5":{"start":{"line":32,"column":4},"end":{"line":32,"column":75}},"6":{"start":{"line":36,"column":4},"end":{"line":36,"column":36}},"7":{"start":{"line":40,"column":16},"end":{"line":42,"column":48}},"8":{"start":{"line":44,"column":4},"end":{"line":44,"column":46}},"9":{"start":{"line":69,"column":29},"end":{"line":71,"column":14}},"10":{"start":{"line":74,"column":4},"end":{"line":79,"column":7}},"11":{"start":{"line":82,"column":4},"end":{"line":86,"column":7}},"12":{"start":{"line":87,"column":24},"end":{"line":87,"column":52}},"13":{"start":{"line":91,"column":46},"end":{"line":142,"column":6}},"14":{"start":{"line":144,"column":25},"end":{"line":145,"column":null}},"15":{"start":{"line":145,"column":73},"end":{"line":145,"column":79}},"16":{"start":{"line":148,"column":35},"end":{"line":148,"column":37}},"17":{"start":{"line":149,"column":4},"end":{"line":154,"column":5}},"18":{"start":{"line":150,"column":8},"end":{"line":153,"column":9}},"19":{"start":{"line":151,"column":12},"end":{"line":151,"column":80}},"20":{"start":{"line":152,"column":12},"end":{"line":152,"column":40}},"21":{"start":{"line":157,"column":4},"end":{"line":172,"column":7}},"22":{"start":{"line":175,"column":4},"end":{"line":182,"column":7}},"23":{"start":{"line":185,"column":4},"end":{"line":193,"column":7}},"24":{"start":{"line":196,"column":4},"end":{"line":295,"column":5}},"25":{"start":{"line":297,"column":29},"end":{"line":297,"column":34}},"26":{"start":{"line":300,"column":4},"end":{"line":326,"column":5}},"27":{"start":{"line":301,"column":26},"end":{"line":301,"column":27}},"28":{"start":{"line":303,"column":19},"end":{"line":322,"column":10}},"29":{"start":{"line":304,"column":25},"end":{"line":308,"column":21}},"30":{"start":{"line":312,"column":12},"end":{"line":321,"column":13}},"31":{"start":{"line":313,"column":32},"end":{"line":315,"column":78}},"32":{"start":{"line":317,"column":16},"end":{"line":317,"column":59}},"33":{"start":{"line":317,"column":50},"end":{"line":317,"column":59}},"34":{"start":{"line":320,"column":16},"end":{"line":320,"column":30}},"35":{"start":{"line":324,"column":8},"end":{"line":324,"column":13}},"36":{"start":{"line":325,"column":8},"end":{"line":325,"column":45}},"37":{"start":{"line":330,"column":4},"end":{"line":425,"column":5}},"38":{"start":{"line":331,"column":8},"end":{"line":424,"column":7}},"39":{"start":{"line":428,"column":4},"end":{"line":569,"column":5}},"40":{"start":{"line":429,"column":8},"end":{"line":568,"column":5}},"41":{"start":{"line":573,"column":4},"end":{"line":650,"column":5}},"42":{"start":{"line":653,"column":4},"end":{"line":653,"column":59}},"43":{"start":{"line":655,"column":36},"end":{"line":659,"column":6}},"44":{"start":{"line":661,"column":4},"end":{"line":678,"column":5}},"45":{"start":{"line":669,"column":12},"end":{"line":671,"column":37}},"46":{"start":{"line":673,"column":24},"end":{"line":673,"column":95}},"47":{"start":{"line":675,"column":8},"end":{"line":677,"column":10}},"48":{"start":{"line":680,"column":4},"end":{"line":680,"column":18}},"49":{"start":{"line":684,"column":4},"end":{"line":684,"column":55}},"50":{"start":{"line":684,"column":45},"end":{"line":684,"column":55}},"51":{"start":{"line":685,"column":4},"end":{"line":691,"column":5}},"52":{"start":{"line":686,"column":18},"end":{"line":686,"column":35}},"53":{"start":{"line":687,"column":8},"end":{"line":687,"column":76}},"54":{"start":{"line":687,"column":30},"end":{"line":687,"column":76}},"55":{"start":{"line":687,"column":53},"end":{"line":687,"column":74}},"56":{"start":{"line":688,"column":8},"end":{"line":688,"column":18}},"57":{"start":{"line":690,"column":8},"end":{"line":690,"column":18}}},"fnMap":{"0":{"name":"setLabNotesSchemaVersion","decl":{"start":{"line":7,"column":9},"end":{"line":7,"column":33}},"loc":{"start":{"line":7,"column":72},"end":{"line":19,"column":1}}},"1":{"name":"sha256Hex","decl":{"start":{"line":31,"column":9},"end":{"line":31,"column":18}},"loc":{"start":{"line":31,"column":32},"end":{"line":33,"column":1}}},"2":{"name":"nowIso","decl":{"start":{"line":35,"column":9},"end":{"line":35,"column":15}},"loc":{"start":{"line":35,"column":15},"end":{"line":37,"column":1}}},"3":{"name":"getLabNotesSchemaVersion","decl":{"start":{"line":39,"column":9},"end":{"line":39,"column":33}},"loc":{"start":{"line":39,"column":55},"end":{"line":45,"column":1}}},"4":{"name":"migrateLabNotesSchema","decl":{"start":{"line":63,"column":16},"end":{"line":63,"column":37}},"loc":{"start":{"line":66,"column":24},"end":{"line":681,"column":1}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":145,"column":61},"end":{"line":145,"column":62}},"loc":{"start":{"line":145,"column":73},"end":{"line":145,"column":79}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":303,"column":34},"end":{"line":303,"column":37}},"loc":{"start":{"line":303,"column":39},"end":{"line":322,"column":9}}},"7":{"name":"safeParseJsonArray","decl":{"start":{"line":683,"column":9},"end":{"line":683,"column":27}},"loc":{"start":{"line":683,"column":38},"end":{"line":692,"column":1}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":687,"column":46},"end":{"line":687,"column":47}},"loc":{"start":{"line":687,"column":53},"end":{"line":687,"column":74}}}},"branchMap":{"0":{"loc":{"start":{"line":12,"column":4},"end":{"line":12,"column":47}},"type":"if","locations":[{"start":{"line":12,"column":4},"end":{"line":12,"column":47}},{"start":{},"end":{}}]},"1":{"loc":{"start":{"line":44,"column":11},"end":{"line":44,"column":45}},"type":"cond-expr","locations":[{"start":{"line":44,"column":24},"end":{"line":44,"column":41}},{"start":{"line":44,"column":44},"end":{"line":44,"column":45}}]},"2":{"loc":{"start":{"line":150,"column":8},"end":{"line":153,"column":9}},"type":"if","locations":[{"start":{"line":150,"column":8},"end":{"line":153,"column":9}},{"start":{},"end":{}}]},"3":{"loc":{"start":{"line":300,"column":4},"end":{"line":326,"column":5}},"type":"if","locations":[{"start":{"line":300,"column":4},"end":{"line":326,"column":5}},{"start":{},"end":{}}]},"4":{"loc":{"start":{"line":317,"column":16},"end":{"line":317,"column":59}},"type":"if","locations":[{"start":{"line":317,"column":16},"end":{"line":317,"column":59}},{"start":{},"end":{}}]},"5":{"loc":{"start":{"line":330,"column":4},"end":{"line":425,"column":5}},"type":"if","locations":[{"start":{"line":330,"column":4},"end":{"line":425,"column":5}},{"start":{},"end":{}}]},"6":{"loc":{"start":{"line":428,"column":4},"end":{"line":569,"column":5}},"type":"if","locations":[{"start":{"line":428,"column":4},"end":{"line":569,"column":5}},{"start":{},"end":{}}]},"7":{"loc":{"start":{"line":661,"column":4},"end":{"line":678,"column":5}},"type":"if","locations":[{"start":{"line":661,"column":4},"end":{"line":678,"column":5}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":662,"column":8},"end":{"line":666,"column":31}},"type":"binary-expr","locations":[{"start":{"line":662,"column":8},"end":{"line":662,"column":11}},{"start":{"line":663,"column":9},"end":{"line":663,"column":33}},{"start":{"line":664,"column":12},"end":{"line":664,"column":42}},{"start":{"line":665,"column":12},"end":{"line":665,"column":52}},{"start":{"line":666,"column":12},"end":{"line":666,"column":30}}]},"9":{"loc":{"start":{"line":669,"column":12},"end":{"line":671,"column":37}},"type":"cond-expr","locations":[{"start":{"line":670,"column":18},"end":{"line":670,"column":100}},{"start":{"line":671,"column":18},"end":{"line":671,"column":37}}]},"10":{"loc":{"start":{"line":673,"column":24},"end":{"line":673,"column":95}},"type":"cond-expr","locations":[{"start":{"line":673,"column":45},"end":{"line":673,"column":70}},{"start":{"line":673,"column":73},"end":{"line":673,"column":95}}]},"11":{"loc":{"start":{"line":684,"column":4},"end":{"line":684,"column":55}},"type":"if","locations":[{"start":{"line":684,"column":4},"end":{"line":684,"column":55}},{"start":{},"end":{}}]},"12":{"loc":{"start":{"line":684,"column":8},"end":{"line":684,"column":43}},"type":"binary-expr","locations":[{"start":{"line":684,"column":8},"end":{"line":684,"column":14}},{"start":{"line":684,"column":18},"end":{"line":684,"column":43}}]},"13":{"loc":{"start":{"line":687,"column":8},"end":{"line":687,"column":76}},"type":"if","locations":[{"start":{"line":687,"column":8},"end":{"line":687,"column":76}},{"start":{},"end":{}}]}},"s":{"0":8,"1":25,"2":25,"3":2,"4":23,"5":0,"6":0,"7":25,"8":25,"9":25,"10":25,"11":25,"12":25,"13":25,"14":25,"15":87,"16":25,"17":25,"18":775,"19":713,"20":713,"21":25,"22":25,"23":25,"24":25,"25":25,"26":25,"27":23,"28":23,"29":23,"30":23,"31":1,"32":1,"33":0,"34":1,"35":23,"36":23,"37":25,"38":23,"39":25,"40":23,"41":25,"42":25,"43":25,"44":25,"45":2,"46":2,"47":2,"48":25,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0},"f":{"0":25,"1":0,"2":0,"3":25,"4":25,"5":87,"6":23,"7":0,"8":0},"b":{"0":[2,23],"1":[2,23],"2":[713,62],"3":[23,2],"4":[0,1],"5":[23,2],"6":[23,2],"7":[2,23],"8":[25,3,1,1,1],"9":[2,0],"10":[0,2],"11":[0,0],"12":[0,0],"13":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrations\\2025-01-dedupe-lab-notes-slugs.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\db\\migrations\\2025-01-dedupe-lab-notes-slugs.ts","statementMap":{"0":{"start":{"line":5,"column":4},"end":{"line":5,"column":69}},"1":{"start":{"line":5,"column":41},"end":{"line":5,"column":69}},"2":{"start":{"line":7,"column":4},"end":{"line":21,"column":5}},"3":{"start":{"line":23,"column":4},"end":{"line":26,"column":5}}},"fnMap":{"0":{"name":"dedupeLabNotesSlugs","decl":{"start":{"line":4,"column":16},"end":{"line":4,"column":35}},"loc":{"start":{"line":4,"column":76},"end":{"line":27,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":59},"end":{"line":4,"column":76}},"type":"default-arg","locations":[{"start":{"line":4,"column":65},"end":{"line":4,"column":76}}]},"1":{"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":69}},"type":"if","locations":[{"start":{"line":5,"column":4},"end":{"line":5,"column":69}},{"start":{},"end":{}}]}},"s":{"0":19,"1":0,"2":19,"3":19},"f":{"0":19},"b":{"0":[19],"1":[0,19]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\lib\\helpers.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\lib\\helpers.ts","statementMap":{"0":{"start":{"line":4,"column":16},"end":{"line":4,"column":58}},"1":{"start":{"line":5,"column":4},"end":{"line":5,"column":26}},"2":{"start":{"line":5,"column":14},"end":{"line":5,"column":26}},"3":{"start":{"line":8,"column":16},"end":{"line":8,"column":36}},"4":{"start":{"line":9,"column":4},"end":{"line":9,"column":49}},"5":{"start":{"line":9,"column":38},"end":{"line":9,"column":49}},"6":{"start":{"line":12,"column":4},"end":{"line":12,"column":48}},"7":{"start":{"line":12,"column":25},"end":{"line":12,"column":48}},"8":{"start":{"line":14,"column":4},"end":{"line":14,"column":16}}},"fnMap":{"0":{"name":"normalizeLocale","decl":{"start":{"line":3,"column":16},"end":{"line":3,"column":31}},"loc":{"start":{"line":3,"column":46},"end":{"line":15,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":23},"end":{"line":4,"column":36}},"type":"binary-expr","locations":[{"start":{"line":4,"column":23},"end":{"line":4,"column":28}},{"start":{"line":4,"column":32},"end":{"line":4,"column":36}}]},"1":{"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":26}},"type":"if","locations":[{"start":{"line":5,"column":4},"end":{"line":5,"column":26}},{"start":{},"end":{}}]},"2":{"loc":{"start":{"line":9,"column":4},"end":{"line":9,"column":49}},"type":"if","locations":[{"start":{"line":9,"column":4},"end":{"line":9,"column":49}},{"start":{},"end":{}}]},"3":{"loc":{"start":{"line":9,"column":8},"end":{"line":9,"column":36}},"type":"binary-expr","locations":[{"start":{"line":9,"column":8},"end":{"line":9,"column":20}},{"start":{"line":9,"column":24},"end":{"line":9,"column":36}}]},"4":{"loc":{"start":{"line":12,"column":4},"end":{"line":12,"column":48}},"type":"if","locations":[{"start":{"line":12,"column":4},"end":{"line":12,"column":48}},{"start":{},"end":{}}]}},"s":{"0":7,"1":7,"2":0,"3":7,"4":7,"5":7,"6":0,"7":0,"8":0},"f":{"0":7},"b":{"0":[7,2],"1":[0,7],"2":[7,0],"3":[7,0],"4":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\mappers\\labNotesMapper.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\mappers\\labNotesMapper.ts","statementMap":{"0":{"start":{"line":4,"column":4},"end":{"line":4,"column":45}},"1":{"start":{"line":8,"column":14},"end":{"line":8,"column":37}},"2":{"start":{"line":9,"column":4},"end":{"line":9,"column":73}},"3":{"start":{"line":9,"column":64},"end":{"line":9,"column":73}},"4":{"start":{"line":10,"column":4},"end":{"line":10,"column":41}},"5":{"start":{"line":13,"column":53},"end":{"line":19,"column":2}},"6":{"start":{"line":23,"column":16},"end":{"line":23,"column":62}},"7":{"start":{"line":25,"column":4},"end":{"line":25,"column":55}},"8":{"start":{"line":25,"column":44},"end":{"line":25,"column":55}},"9":{"start":{"line":28,"column":4},"end":{"line":28,"column":50}},"10":{"start":{"line":28,"column":35},"end":{"line":28,"column":50}},"11":{"start":{"line":29,"column":4},"end":{"line":29,"column":48}},"12":{"start":{"line":29,"column":34},"end":{"line":29,"column":48}},"13":{"start":{"line":30,"column":4},"end":{"line":30,"column":48}},"14":{"start":{"line":30,"column":34},"end":{"line":30,"column":48}},"15":{"start":{"line":31,"column":4},"end":{"line":31,"column":54}},"16":{"start":{"line":31,"column":37},"end":{"line":31,"column":54}},"17":{"start":{"line":33,"column":4},"end":{"line":33,"column":21}},"18":{"start":{"line":56,"column":22},"end":{"line":56,"column":45}},"19":{"start":{"line":58,"column":4},"end":{"line":92,"column":6}},"20":{"start":{"line":116,"column":22},"end":{"line":116,"column":45}},"21":{"start":{"line":118,"column":4},"end":{"line":152,"column":6}}},"fnMap":{"0":{"name":"deriveStatus","decl":{"start":{"line":3,"column":9},"end":{"line":3,"column":21}},"loc":{"start":{"line":3,"column":39},"end":{"line":5,"column":1}}},"1":{"name":"normalizeStatus","decl":{"start":{"line":7,"column":9},"end":{"line":7,"column":24}},"loc":{"start":{"line":7,"column":102},"end":{"line":11,"column":1}}},"2":{"name":"deriveType","decl":{"start":{"line":21,"column":9},"end":{"line":21,"column":19}},"loc":{"start":{"line":21,"column":39},"end":{"line":34,"column":1}}},"3":{"name":"mapToLabNoteView","decl":{"start":{"line":35,"column":16},"end":{"line":35,"column":32}},"loc":{"start":{"line":35,"column":68},"end":{"line":93,"column":1}}},"4":{"name":"mapToLabNotePreview","decl":{"start":{"line":95,"column":16},"end":{"line":95,"column":35}},"loc":{"start":{"line":95,"column":71},"end":{"line":153,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":11},"end":{"line":4,"column":44}},"type":"cond-expr","locations":[{"start":{"line":4,"column":23},"end":{"line":4,"column":34}},{"start":{"line":4,"column":37},"end":{"line":4,"column":44}}]},"1":{"loc":{"start":{"line":8,"column":15},"end":{"line":8,"column":22}},"type":"binary-expr","locations":[{"start":{"line":8,"column":15},"end":{"line":8,"column":16}},{"start":{"line":8,"column":20},"end":{"line":8,"column":22}}]},"2":{"loc":{"start":{"line":9,"column":4},"end":{"line":9,"column":73}},"type":"if","locations":[{"start":{"line":9,"column":4},"end":{"line":9,"column":73}},{"start":{},"end":{}}]},"3":{"loc":{"start":{"line":9,"column":8},"end":{"line":9,"column":62}},"type":"binary-expr","locations":[{"start":{"line":9,"column":8},"end":{"line":9,"column":25}},{"start":{"line":9,"column":29},"end":{"line":9,"column":42}},{"start":{"line":9,"column":46},"end":{"line":9,"column":62}}]},"4":{"loc":{"start":{"line":10,"column":24},"end":{"line":10,"column":39}},"type":"binary-expr","locations":[{"start":{"line":10,"column":24},"end":{"line":10,"column":33}},{"start":{"line":10,"column":37},"end":{"line":10,"column":39}}]},"5":{"loc":{"start":{"line":23,"column":17},"end":{"line":23,"column":32}},"type":"binary-expr","locations":[{"start":{"line":23,"column":17},"end":{"line":23,"column":26}},{"start":{"line":23,"column":30},"end":{"line":23,"column":32}}]},"6":{"loc":{"start":{"line":25,"column":4},"end":{"line":25,"column":55}},"type":"if","locations":[{"start":{"line":25,"column":4},"end":{"line":25,"column":55}},{"start":{},"end":{}}]},"7":{"loc":{"start":{"line":25,"column":8},"end":{"line":25,"column":42}},"type":"binary-expr","locations":[{"start":{"line":25,"column":8},"end":{"line":25,"column":11}},{"start":{"line":25,"column":15},"end":{"line":25,"column":42}}]},"8":{"loc":{"start":{"line":28,"column":4},"end":{"line":28,"column":50}},"type":"if","locations":[{"start":{"line":28,"column":4},"end":{"line":28,"column":50}},{"start":{},"end":{}}]},"9":{"loc":{"start":{"line":29,"column":4},"end":{"line":29,"column":48}},"type":"if","locations":[{"start":{"line":29,"column":4},"end":{"line":29,"column":48}},{"start":{},"end":{}}]},"10":{"loc":{"start":{"line":30,"column":4},"end":{"line":30,"column":48}},"type":"if","locations":[{"start":{"line":30,"column":4},"end":{"line":30,"column":48}},{"start":{},"end":{}}]},"11":{"loc":{"start":{"line":31,"column":4},"end":{"line":31,"column":54}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":31,"column":54}},{"start":{},"end":{}}]},"12":{"loc":{"start":{"line":56,"column":22},"end":{"line":56,"column":45}},"type":"binary-expr","locations":[{"start":{"line":56,"column":22},"end":{"line":56,"column":39}},{"start":{"line":56,"column":43},"end":{"line":56,"column":45}}]},"13":{"loc":{"start":{"line":63,"column":18},"end":{"line":63,"column":44}},"type":"binary-expr","locations":[{"start":{"line":63,"column":18},"end":{"line":63,"column":31}},{"start":{"line":63,"column":35},"end":{"line":63,"column":44}}]},"14":{"loc":{"start":{"line":64,"column":17},"end":{"line":64,"column":35}},"type":"binary-expr","locations":[{"start":{"line":64,"column":17},"end":{"line":64,"column":29}},{"start":{"line":64,"column":33},"end":{"line":64,"column":35}}]},"15":{"loc":{"start":{"line":66,"column":21},"end":{"line":66,"column":85}},"type":"binary-expr","locations":[{"start":{"line":66,"column":21},"end":{"line":66,"column":46}},{"start":{"line":66,"column":50},"end":{"line":66,"column":85}}]},"16":{"loc":{"start":{"line":72,"column":14},"end":{"line":72,"column":36}},"type":"binary-expr","locations":[{"start":{"line":72,"column":14},"end":{"line":72,"column":23}},{"start":{"line":72,"column":27},"end":{"line":72,"column":36}}]},"17":{"loc":{"start":{"line":73,"column":16},"end":{"line":73,"column":35}},"type":"binary-expr","locations":[{"start":{"line":73,"column":16},"end":{"line":73,"column":27}},{"start":{"line":73,"column":31},"end":{"line":73,"column":35}}]},"18":{"loc":{"start":{"line":75,"column":16},"end":{"line":81,"column":23}},"type":"cond-expr","locations":[{"start":{"line":76,"column":14},"end":{"line":80,"column":null}},{"start":{"line":81,"column":14},"end":{"line":81,"column":23}}]},"19":{"loc":{"start":{"line":78,"column":20},"end":{"line":78,"column":70}},"type":"cond-expr","locations":[{"start":{"line":78,"column":39},"end":{"line":78,"column":65}},{"start":{"line":78,"column":68},"end":{"line":78,"column":70}}]},"20":{"loc":{"start":{"line":79,"column":20},"end":{"line":79,"column":64}},"type":"cond-expr","locations":[{"start":{"line":79,"column":37},"end":{"line":79,"column":59}},{"start":{"line":79,"column":62},"end":{"line":79,"column":64}}]},"21":{"loc":{"start":{"line":83,"column":23},"end":{"line":83,"column":51}},"type":"binary-expr","locations":[{"start":{"line":83,"column":23},"end":{"line":83,"column":41}},{"start":{"line":83,"column":45},"end":{"line":83,"column":51}}]},"22":{"loc":{"start":{"line":84,"column":24},"end":{"line":84,"column":48}},"type":"binary-expr","locations":[{"start":{"line":84,"column":24},"end":{"line":84,"column":43}},{"start":{"line":84,"column":47},"end":{"line":84,"column":48}}]},"23":{"loc":{"start":{"line":88,"column":21},"end":{"line":88,"column":48}},"type":"binary-expr","locations":[{"start":{"line":88,"column":21},"end":{"line":88,"column":43}},{"start":{"line":88,"column":47},"end":{"line":88,"column":48}}]},"24":{"loc":{"start":{"line":116,"column":22},"end":{"line":116,"column":45}},"type":"binary-expr","locations":[{"start":{"line":116,"column":22},"end":{"line":116,"column":39}},{"start":{"line":116,"column":43},"end":{"line":116,"column":45}}]},"25":{"loc":{"start":{"line":123,"column":18},"end":{"line":123,"column":44}},"type":"binary-expr","locations":[{"start":{"line":123,"column":18},"end":{"line":123,"column":31}},{"start":{"line":123,"column":35},"end":{"line":123,"column":44}}]},"26":{"loc":{"start":{"line":124,"column":17},"end":{"line":124,"column":35}},"type":"binary-expr","locations":[{"start":{"line":124,"column":17},"end":{"line":124,"column":29}},{"start":{"line":124,"column":33},"end":{"line":124,"column":35}}]},"27":{"loc":{"start":{"line":132,"column":14},"end":{"line":132,"column":36}},"type":"binary-expr","locations":[{"start":{"line":132,"column":14},"end":{"line":132,"column":23}},{"start":{"line":132,"column":27},"end":{"line":132,"column":36}}]},"28":{"loc":{"start":{"line":133,"column":16},"end":{"line":133,"column":35}},"type":"binary-expr","locations":[{"start":{"line":133,"column":16},"end":{"line":133,"column":27}},{"start":{"line":133,"column":31},"end":{"line":133,"column":35}}]},"29":{"loc":{"start":{"line":135,"column":16},"end":{"line":141,"column":23}},"type":"cond-expr","locations":[{"start":{"line":136,"column":14},"end":{"line":140,"column":null}},{"start":{"line":141,"column":14},"end":{"line":141,"column":23}}]},"30":{"loc":{"start":{"line":138,"column":20},"end":{"line":138,"column":70}},"type":"cond-expr","locations":[{"start":{"line":138,"column":39},"end":{"line":138,"column":65}},{"start":{"line":138,"column":68},"end":{"line":138,"column":70}}]},"31":{"loc":{"start":{"line":139,"column":20},"end":{"line":139,"column":64}},"type":"cond-expr","locations":[{"start":{"line":139,"column":37},"end":{"line":139,"column":59}},{"start":{"line":139,"column":62},"end":{"line":139,"column":64}}]},"32":{"loc":{"start":{"line":143,"column":23},"end":{"line":143,"column":51}},"type":"binary-expr","locations":[{"start":{"line":143,"column":23},"end":{"line":143,"column":41}},{"start":{"line":143,"column":45},"end":{"line":143,"column":51}}]},"33":{"loc":{"start":{"line":144,"column":24},"end":{"line":144,"column":48}},"type":"binary-expr","locations":[{"start":{"line":144,"column":24},"end":{"line":144,"column":43}},{"start":{"line":144,"column":47},"end":{"line":144,"column":48}}]},"34":{"loc":{"start":{"line":148,"column":21},"end":{"line":148,"column":48}},"type":"binary-expr","locations":[{"start":{"line":148,"column":21},"end":{"line":148,"column":43}},{"start":{"line":148,"column":47},"end":{"line":148,"column":48}}]}},"s":{"0":1,"1":3,"2":3,"3":2,"4":1,"5":7,"6":3,"7":3,"8":2,"9":1,"10":0,"11":1,"12":0,"13":1,"14":0,"15":1,"16":0,"17":1,"18":1,"19":1,"20":2,"21":2},"f":{"0":1,"1":3,"2":3,"3":1,"4":2},"b":{"0":[1,0],"1":[3,1],"2":[2,1],"3":[3,1,1],"4":[1,0],"5":[3,1],"6":[2,1],"7":[3,2],"8":[0,1],"9":[0,1],"10":[0,1],"11":[0,1],"12":[1,0],"13":[1,1],"14":[1,0],"15":[1,1],"16":[1,1],"17":[1,1],"18":[0,1],"19":[0,0],"20":[0,0],"21":[1,0],"22":[1,0],"23":[1,0],"24":[2,0],"25":[2,2],"26":[2,0],"27":[2,0],"28":[2,0],"29":[0,2],"30":[0,0],"31":[0,0],"32":[2,0],"33":[2,0],"34":[2,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\middleware\\requireAdmin.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\middleware\\requireAdmin.ts","statementMap":{"0":{"start":{"line":5,"column":14},"end":{"line":5,"column":41}},"1":{"start":{"line":6,"column":4},"end":{"line":6,"column":24}},"2":{"start":{"line":6,"column":12},"end":{"line":6,"column":24}},"3":{"start":{"line":7,"column":14},"end":{"line":7,"column":55}},"4":{"start":{"line":8,"column":4},"end":{"line":8,"column":26}},"5":{"start":{"line":12,"column":4},"end":{"line":15,"column":25}},"6":{"start":{"line":14,"column":18},"end":{"line":14,"column":40}},"7":{"start":{"line":20,"column":20},"end":{"line":20,"column":36}},"8":{"start":{"line":21,"column":18},"end":{"line":21,"column":67}},"9":{"start":{"line":22,"column":4},"end":{"line":22,"column":46}},"10":{"start":{"line":26,"column":19},"end":{"line":26,"column":74}},"11":{"start":{"line":27,"column":17},"end":{"line":27,"column":23}},"12":{"start":{"line":29,"column":28},"end":{"line":65,"column":1}},"13":{"start":{"line":31,"column":4},"end":{"line":36,"column":5}},"14":{"start":{"line":35,"column":8},"end":{"line":35,"column":22}},"15":{"start":{"line":38,"column":4},"end":{"line":40,"column":5}},"16":{"start":{"line":39,"column":8},"end":{"line":39,"column":63}},"17":{"start":{"line":42,"column":18},"end":{"line":42,"column":51}},"18":{"start":{"line":43,"column":4},"end":{"line":45,"column":5}},"19":{"start":{"line":44,"column":8},"end":{"line":44,"column":63}},"20":{"start":{"line":48,"column":18},"end":{"line":52,"column":6}},"21":{"start":{"line":55,"column":4},"end":{"line":57,"column":5}},"22":{"start":{"line":56,"column":8},"end":{"line":56,"column":71}},"23":{"start":{"line":60,"column":4},"end":{"line":62,"column":5}},"24":{"start":{"line":61,"column":8},"end":{"line":61,"column":60}},"25":{"start":{"line":64,"column":4},"end":{"line":64,"column":18}}},"fnMap":{"0":{"name":"getAuthToken","decl":{"start":{"line":4,"column":9},"end":{"line":4,"column":21}},"loc":{"start":{"line":4,"column":34},"end":{"line":9,"column":1}}},"1":{"name":"parseAllowlist","decl":{"start":{"line":11,"column":9},"end":{"line":11,"column":23}},"loc":{"start":{"line":11,"column":41},"end":{"line":16,"column":1}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":14,"column":13},"end":{"line":14,"column":14}},"loc":{"start":{"line":14,"column":18},"end":{"line":14,"column":40}}},"3":{"name":"getSessionLogin","decl":{"start":{"line":18,"column":9},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":37},"end":{"line":23,"column":1}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":29,"column":28},"end":{"line":29,"column":29}},"loc":{"start":{"line":29,"column":80},"end":{"line":65,"column":1}}}},"branchMap":{"0":{"loc":{"start":{"line":6,"column":4},"end":{"line":6,"column":24}},"type":"if","locations":[{"start":{"line":6,"column":4},"end":{"line":6,"column":24}},{"start":{},"end":{}}]},"1":{"loc":{"start":{"line":8,"column":11},"end":{"line":8,"column":25}},"type":"binary-expr","locations":[{"start":{"line":8,"column":11},"end":{"line":8,"column":17}},{"start":{"line":8,"column":21},"end":{"line":8,"column":25}}]},"2":{"loc":{"start":{"line":12,"column":18},"end":{"line":12,"column":32}},"type":"binary-expr","locations":[{"start":{"line":12,"column":18},"end":{"line":12,"column":26}},{"start":{"line":12,"column":30},"end":{"line":12,"column":32}}]},"3":{"loc":{"start":{"line":21,"column":19},"end":{"line":21,"column":48}},"type":"binary-expr","locations":[{"start":{"line":21,"column":19},"end":{"line":21,"column":30}},{"start":{"line":21,"column":34},"end":{"line":21,"column":42}},{"start":{"line":21,"column":46},"end":{"line":21,"column":48}}]},"4":{"loc":{"start":{"line":22,"column":11},"end":{"line":22,"column":45}},"type":"cond-expr","locations":[{"start":{"line":22,"column":19},"end":{"line":22,"column":38}},{"start":{"line":22,"column":41},"end":{"line":22,"column":45}}]},"5":{"loc":{"start":{"line":31,"column":4},"end":{"line":36,"column":5}},"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":36,"column":5}},{"start":{},"end":{}}]},"6":{"loc":{"start":{"line":32,"column":8},"end":{"line":33,"column":47}},"type":"binary-expr","locations":[{"start":{"line":32,"column":8},"end":{"line":32,"column":45}},{"start":{"line":33,"column":8},"end":{"line":33,"column":47}}]},"7":{"loc":{"start":{"line":38,"column":4},"end":{"line":40,"column":5}},"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":40,"column":5}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":43,"column":4},"end":{"line":45,"column":5}},"type":"if","locations":[{"start":{"line":43,"column":4},"end":{"line":45,"column":5}},{"start":{},"end":{}}]},"9":{"loc":{"start":{"line":55,"column":4},"end":{"line":57,"column":5}},"type":"if","locations":[{"start":{"line":55,"column":4},"end":{"line":57,"column":5}},{"start":{},"end":{}}]},"10":{"loc":{"start":{"line":60,"column":4},"end":{"line":62,"column":5}},"type":"if","locations":[{"start":{"line":60,"column":4},"end":{"line":62,"column":5}},{"start":{},"end":{}}]}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":7,"11":7,"12":7,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\adminRoutes.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\adminRoutes.ts","statementMap":{"0":{"start":{"line":10,"column":24},"end":{"line":10,"column":74}},"1":{"start":{"line":15,"column":4},"end":{"line":40,"column":7}},"2":{"start":{"line":16,"column":8},"end":{"line":39,"column":9}},"3":{"start":{"line":17,"column":25},"end":{"line":32,"column":22}},"4":{"start":{"line":34,"column":12},"end":{"line":34,"column":34}},"5":{"start":{"line":36,"column":12},"end":{"line":38,"column":85}},"6":{"start":{"line":45,"column":4},"end":{"line":156,"column":7}},"7":{"start":{"line":46,"column":8},"end":{"line":155,"column":9}},"8":{"start":{"line":66,"column":16},"end":{"line":66,"column":30}},"9":{"start":{"line":68,"column":12},"end":{"line":68,"column":84}},"10":{"start":{"line":68,"column":24},"end":{"line":68,"column":84}},"11":{"start":{"line":69,"column":12},"end":{"line":69,"column":82}},"12":{"start":{"line":69,"column":23},"end":{"line":69,"column":82}},"13":{"start":{"line":71,"column":27},"end":{"line":71,"column":45}},"14":{"start":{"line":72,"column":31},"end":{"line":72,"column":54}},"15":{"start":{"line":74,"column":29},"end":{"line":74,"column":54}},"16":{"start":{"line":75,"column":31},"end":{"line":75,"column":87}},"17":{"start":{"line":78,"column":16},"end":{"line":80,"column":42}},"18":{"start":{"line":82,"column":25},"end":{"line":115,"column":8}},"19":{"start":{"line":117,"column":12},"end":{"line":140,"column":14}},"20":{"start":{"line":142,"column":12},"end":{"line":149,"column":15}},"21":{"start":{"line":151,"column":12},"end":{"line":154,"column":15}},"22":{"start":{"line":161,"column":4},"end":{"line":165,"column":7}},"23":{"start":{"line":162,"column":22},"end":{"line":162,"column":46}},"24":{"start":{"line":163,"column":8},"end":{"line":163,"column":63}},"25":{"start":{"line":163,"column":19},"end":{"line":163,"column":63}},"26":{"start":{"line":164,"column":8},"end":{"line":164,"column":34}},"27":{"start":{"line":167,"column":4},"end":{"line":180,"column":7}},"28":{"start":{"line":168,"column":21},"end":{"line":168,"column":49}},"29":{"start":{"line":168,"column":27},"end":{"line":168,"column":49}},"30":{"start":{"line":170,"column":8},"end":{"line":179,"column":9}},"31":{"start":{"line":171,"column":27},"end":{"line":171,"column":37}},"32":{"start":{"line":172,"column":12},"end":{"line":175,"column":13}},"33":{"start":{"line":173,"column":16},"end":{"line":173,"column":73}},"34":{"start":{"line":173,"column":46},"end":{"line":173,"column":73}},"35":{"start":{"line":174,"column":16},"end":{"line":174,"column":32}},"36":{"start":{"line":176,"column":12},"end":{"line":176,"column":26}},"37":{"start":{"line":178,"column":12},"end":{"line":178,"column":26}},"38":{"start":{"line":183,"column":4},"end":{"line":185,"column":7}},"39":{"start":{"line":184,"column":8},"end":{"line":184,"column":54}},"40":{"start":{"line":190,"column":4},"end":{"line":213,"column":5}},"41":{"start":{"line":191,"column":8},"end":{"line":191,"column":92}},"42":{"start":{"line":193,"column":8},"end":{"line":203,"column":10}},"43":{"start":{"line":201,"column":16},"end":{"line":201,"column":63}},"44":{"start":{"line":206,"column":8},"end":{"line":208,"column":11}},"45":{"start":{"line":207,"column":12},"end":{"line":207,"column":69}},"46":{"start":{"line":210,"column":8},"end":{"line":212,"column":11}},"47":{"start":{"line":211,"column":12},"end":{"line":211,"column":69}}},"fnMap":{"0":{"name":"registerAdminRoutes","decl":{"start":{"line":8,"column":16},"end":{"line":8,"column":35}},"loc":{"start":{"line":8,"column":67},"end":{"line":214,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":15,"column":42},"end":{"line":15,"column":43}},"loc":{"start":{"line":15,"column":75},"end":{"line":40,"column":5}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":45,"column":43},"end":{"line":45,"column":44}},"loc":{"start":{"line":45,"column":75},"end":{"line":156,"column":5}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":161,"column":24},"end":{"line":161,"column":25}},"loc":{"start":{"line":161,"column":56},"end":{"line":165,"column":5}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":167,"column":29},"end":{"line":167,"column":30}},"loc":{"start":{"line":167,"column":61},"end":{"line":180,"column":5}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":168,"column":21},"end":{"line":168,"column":24}},"loc":{"start":{"line":168,"column":27},"end":{"line":168,"column":49}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":183,"column":30},"end":{"line":183,"column":31}},"loc":{"start":{"line":183,"column":63},"end":{"line":185,"column":5}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":199,"column":12},"end":{"line":199,"column":13}},"loc":{"start":{"line":199,"column":45},"end":{"line":202,"column":13}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":206,"column":32},"end":{"line":206,"column":33}},"loc":{"start":{"line":206,"column":65},"end":{"line":208,"column":9}}},"9":{"name":"(anonymous_9)","decl":{"start":{"line":210,"column":41},"end":{"line":210,"column":42}},"loc":{"start":{"line":210,"column":74},"end":{"line":212,"column":9}}}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":24},"end":{"line":10,"column":74}},"type":"binary-expr","locations":[{"start":{"line":10,"column":24},"end":{"line":10,"column":47}},{"start":{"line":10,"column":51},"end":{"line":10,"column":74}}]},"1":{"loc":{"start":{"line":38,"column":65},"end":{"line":38,"column":80}},"type":"binary-expr","locations":[{"start":{"line":38,"column":65},"end":{"line":38,"column":75}},{"start":{"line":38,"column":79},"end":{"line":38,"column":80}}]},"2":{"loc":{"start":{"line":66,"column":16},"end":{"line":66,"column":30}},"type":"binary-expr","locations":[{"start":{"line":66,"column":16},"end":{"line":66,"column":24}},{"start":{"line":66,"column":28},"end":{"line":66,"column":30}}]},"3":{"loc":{"start":{"line":68,"column":12},"end":{"line":68,"column":84}},"type":"if","locations":[{"start":{"line":68,"column":12},"end":{"line":68,"column":84}},{"start":{},"end":{}}]},"4":{"loc":{"start":{"line":69,"column":12},"end":{"line":69,"column":82}},"type":"if","locations":[{"start":{"line":69,"column":12},"end":{"line":69,"column":82}},{"start":{},"end":{}}]},"5":{"loc":{"start":{"line":71,"column":27},"end":{"line":71,"column":45}},"type":"binary-expr","locations":[{"start":{"line":71,"column":27},"end":{"line":71,"column":29}},{"start":{"line":71,"column":33},"end":{"line":71,"column":45}}]},"6":{"loc":{"start":{"line":74,"column":36},"end":{"line":74,"column":53}},"type":"binary-expr","locations":[{"start":{"line":74,"column":36},"end":{"line":74,"column":40}},{"start":{"line":74,"column":44},"end":{"line":74,"column":53}}]},"7":{"loc":{"start":{"line":75,"column":38},"end":{"line":75,"column":86}},"type":"binary-expr","locations":[{"start":{"line":75,"column":38},"end":{"line":75,"column":44}},{"start":{"line":75,"column":49},"end":{"line":75,"column":85}}]},"8":{"loc":{"start":{"line":75,"column":49},"end":{"line":75,"column":85}},"type":"cond-expr","locations":[{"start":{"line":75,"column":64},"end":{"line":75,"column":75}},{"start":{"line":75,"column":78},"end":{"line":75,"column":85}}]},"9":{"loc":{"start":{"line":78,"column":16},"end":{"line":80,"column":42}},"type":"cond-expr","locations":[{"start":{"line":79,"column":22},"end":{"line":79,"column":75}},{"start":{"line":80,"column":22},"end":{"line":80,"column":42}}]},"10":{"loc":{"start":{"line":79,"column":22},"end":{"line":79,"column":75}},"type":"binary-expr","locations":[{"start":{"line":79,"column":22},"end":{"line":79,"column":34}},{"start":{"line":79,"column":38},"end":{"line":79,"column":75}}]},"11":{"loc":{"start":{"line":80,"column":22},"end":{"line":80,"column":42}},"type":"binary-expr","locations":[{"start":{"line":80,"column":22},"end":{"line":80,"column":34}},{"start":{"line":80,"column":38},"end":{"line":80,"column":42}}]},"12":{"loc":{"start":{"line":125,"column":16},"end":{"line":125,"column":28}},"type":"binary-expr","locations":[{"start":{"line":125,"column":16},"end":{"line":125,"column":20}},{"start":{"line":125,"column":24},"end":{"line":125,"column":28}}]},"13":{"loc":{"start":{"line":127,"column":16},"end":{"line":127,"column":43}},"type":"binary-expr","locations":[{"start":{"line":127,"column":16},"end":{"line":127,"column":24}},{"start":{"line":127,"column":28},"end":{"line":127,"column":43}}]},"14":{"loc":{"start":{"line":128,"column":16},"end":{"line":128,"column":29}},"type":"binary-expr","locations":[{"start":{"line":128,"column":16},"end":{"line":128,"column":23}},{"start":{"line":128,"column":27},"end":{"line":128,"column":29}}]},"15":{"loc":{"start":{"line":129,"column":16},"end":{"line":129,"column":29}},"type":"binary-expr","locations":[{"start":{"line":129,"column":16},"end":{"line":129,"column":23}},{"start":{"line":129,"column":27},"end":{"line":129,"column":29}}]},"16":{"loc":{"start":{"line":131,"column":16},"end":{"line":131,"column":36}},"type":"binary-expr","locations":[{"start":{"line":131,"column":16},"end":{"line":131,"column":28}},{"start":{"line":131,"column":32},"end":{"line":131,"column":36}}]},"17":{"loc":{"start":{"line":133,"column":16},"end":{"line":133,"column":39}},"type":"binary-expr","locations":[{"start":{"line":133,"column":16},"end":{"line":133,"column":29}},{"start":{"line":133,"column":33},"end":{"line":133,"column":39}}]},"18":{"loc":{"start":{"line":134,"column":16},"end":{"line":134,"column":35}},"type":"binary-expr","locations":[{"start":{"line":134,"column":16},"end":{"line":134,"column":30}},{"start":{"line":134,"column":34},"end":{"line":134,"column":35}}]},"19":{"loc":{"start":{"line":135,"column":16},"end":{"line":135,"column":38}},"type":"binary-expr","locations":[{"start":{"line":135,"column":16},"end":{"line":135,"column":31}},{"start":{"line":135,"column":35},"end":{"line":135,"column":38}}]},"20":{"loc":{"start":{"line":136,"column":16},"end":{"line":136,"column":37}},"type":"cond-expr","locations":[{"start":{"line":136,"column":32},"end":{"line":136,"column":33}},{"start":{"line":136,"column":36},"end":{"line":136,"column":37}}]},"21":{"loc":{"start":{"line":137,"column":16},"end":{"line":137,"column":38}},"type":"binary-expr","locations":[{"start":{"line":137,"column":16},"end":{"line":137,"column":33}},{"start":{"line":137,"column":37},"end":{"line":137,"column":38}}]},"22":{"loc":{"start":{"line":153,"column":32},"end":{"line":153,"column":47}},"type":"binary-expr","locations":[{"start":{"line":153,"column":32},"end":{"line":153,"column":42}},{"start":{"line":153,"column":46},"end":{"line":153,"column":47}}]},"23":{"loc":{"start":{"line":162,"column":22},"end":{"line":162,"column":46}},"type":"binary-expr","locations":[{"start":{"line":162,"column":22},"end":{"line":162,"column":38}},{"start":{"line":162,"column":42},"end":{"line":162,"column":46}}]},"24":{"loc":{"start":{"line":163,"column":8},"end":{"line":163,"column":63}},"type":"if","locations":[{"start":{"line":163,"column":8},"end":{"line":163,"column":63}},{"start":{},"end":{}}]},"25":{"loc":{"start":{"line":172,"column":12},"end":{"line":175,"column":13}},"type":"if","locations":[{"start":{"line":172,"column":12},"end":{"line":175,"column":13}},{"start":{},"end":{}}]},"26":{"loc":{"start":{"line":173,"column":16},"end":{"line":173,"column":73}},"type":"if","locations":[{"start":{"line":173,"column":16},"end":{"line":173,"column":73}},{"start":{},"end":{}}]},"27":{"loc":{"start":{"line":190,"column":4},"end":{"line":213,"column":5}},"type":"if","locations":[{"start":{"line":190,"column":4},"end":{"line":213,"column":5}},{"start":{"line":204,"column":11},"end":{"line":213,"column":5}}]}},"s":{"0":19,"1":19,"2":4,"3":4,"4":4,"5":0,"6":19,"7":9,"8":9,"9":9,"10":2,"11":7,"12":2,"13":5,"14":5,"15":5,"16":5,"17":5,"18":5,"19":5,"20":5,"21":0,"22":19,"23":1,"24":1,"25":1,"26":0,"27":19,"28":1,"29":1,"30":1,"31":1,"32":1,"33":0,"34":0,"35":0,"36":1,"37":0,"38":19,"39":1,"40":19,"41":0,"42":0,"43":0,"44":19,"45":0,"46":19,"47":0},"f":{"0":19,"1":4,"2":9,"3":1,"4":1,"5":1,"6":1,"7":0,"8":0,"9":0},"b":{"0":[19,19],"1":[0,0],"2":[9,0],"3":[2,7],"4":[2,5],"5":[5,5],"6":[5,5],"7":[5,0],"8":[0,0],"9":[1,4],"10":[1,1],"11":[4,4],"12":[5,5],"13":[5,5],"14":[5,5],"15":[5,3],"16":[5,5],"17":[5,5],"18":[5,5],"19":[5,5],"20":[0,5],"21":[5,5],"22":[0,0],"23":[1,1],"24":[1,0],"25":[0,1],"26":[0,0],"27":[0,19]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\adminTokensRoutes.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\adminTokensRoutes.ts","statementMap":{"0":{"start":{"line":8,"column":4},"end":{"line":21,"column":7}},"1":{"start":{"line":9,"column":21},"end":{"line":13,"column":21}},"2":{"start":{"line":15,"column":21},"end":{"line":18,"column":11}},"3":{"start":{"line":15,"column":38},"end":{"line":18,"column":10}},"4":{"start":{"line":20,"column":8},"end":{"line":20,"column":37}},"5":{"start":{"line":24,"column":4},"end":{"line":43,"column":7}},"6":{"start":{"line":25,"column":46},"end":{"line":25,"column":60}},"7":{"start":{"line":26,"column":8},"end":{"line":28,"column":9}},"8":{"start":{"line":27,"column":12},"end":{"line":27,"column":117}},"9":{"start":{"line":29,"column":8},"end":{"line":31,"column":9}},"10":{"start":{"line":29,"column":57},"end":{"line":29,"column":78}},"11":{"start":{"line":30,"column":12},"end":{"line":30,"column":123}},"12":{"start":{"line":33,"column":33},"end":{"line":33,"column":95}},"13":{"start":{"line":35,"column":23},"end":{"line":40,"column":10}},"14":{"start":{"line":42,"column":8},"end":{"line":42,"column":45}},"15":{"start":{"line":46,"column":4},"end":{"line":50,"column":7}},"16":{"start":{"line":47,"column":23},"end":{"line":47,"column":33}},"17":{"start":{"line":48,"column":8},"end":{"line":48,"column":79}},"18":{"start":{"line":49,"column":8},"end":{"line":49,"column":31}},"19":{"start":{"line":54,"column":4},"end":{"line":54,"column":55}},"20":{"start":{"line":54,"column":45},"end":{"line":54,"column":55}},"21":{"start":{"line":55,"column":4},"end":{"line":61,"column":5}},"22":{"start":{"line":56,"column":18},"end":{"line":56,"column":35}},"23":{"start":{"line":57,"column":8},"end":{"line":57,"column":76}},"24":{"start":{"line":57,"column":30},"end":{"line":57,"column":76}},"25":{"start":{"line":57,"column":53},"end":{"line":57,"column":74}},"26":{"start":{"line":58,"column":8},"end":{"line":58,"column":18}},"27":{"start":{"line":60,"column":8},"end":{"line":60,"column":18}}},"fnMap":{"0":{"name":"registerAdminTokensRoutes","decl":{"start":{"line":6,"column":16},"end":{"line":6,"column":41}},"loc":{"start":{"line":6,"column":73},"end":{"line":51,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":8,"column":43},"end":{"line":8,"column":44}},"loc":{"start":{"line":8,"column":76},"end":{"line":21,"column":5}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":15,"column":30},"end":{"line":15,"column":31}},"loc":{"start":{"line":15,"column":38},"end":{"line":18,"column":10}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":24,"column":44},"end":{"line":24,"column":45}},"loc":{"start":{"line":24,"column":76},"end":{"line":43,"column":5}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":29,"column":50},"end":{"line":29,"column":51}},"loc":{"start":{"line":29,"column":57},"end":{"line":29,"column":78}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":46,"column":55},"end":{"line":46,"column":56}},"loc":{"start":{"line":46,"column":87},"end":{"line":50,"column":5}}},"6":{"name":"safeParseJsonArray","decl":{"start":{"line":53,"column":9},"end":{"line":53,"column":27}},"loc":{"start":{"line":53,"column":38},"end":{"line":62,"column":1}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":57,"column":46},"end":{"line":57,"column":47}},"loc":{"start":{"line":57,"column":53},"end":{"line":57,"column":74}}}},"branchMap":{"0":{"loc":{"start":{"line":25,"column":46},"end":{"line":25,"column":60}},"type":"binary-expr","locations":[{"start":{"line":25,"column":46},"end":{"line":25,"column":54}},{"start":{"line":25,"column":58},"end":{"line":25,"column":60}}]},"1":{"loc":{"start":{"line":26,"column":8},"end":{"line":28,"column":9}},"type":"if","locations":[{"start":{"line":26,"column":8},"end":{"line":28,"column":9}},{"start":{},"end":{}}]},"2":{"loc":{"start":{"line":26,"column":12},"end":{"line":26,"column":47}},"type":"binary-expr","locations":[{"start":{"line":26,"column":12},"end":{"line":26,"column":18}},{"start":{"line":26,"column":22},"end":{"line":26,"column":47}}]},"3":{"loc":{"start":{"line":29,"column":8},"end":{"line":31,"column":9}},"type":"if","locations":[{"start":{"line":29,"column":8},"end":{"line":31,"column":9}},{"start":{},"end":{}}]},"4":{"loc":{"start":{"line":29,"column":12},"end":{"line":29,"column":79}},"type":"binary-expr","locations":[{"start":{"line":29,"column":12},"end":{"line":29,"column":34}},{"start":{"line":29,"column":38},"end":{"line":29,"column":79}}]},"5":{"loc":{"start":{"line":33,"column":33},"end":{"line":33,"column":95}},"type":"binary-expr","locations":[{"start":{"line":33,"column":33},"end":{"line":33,"column":59}},{"start":{"line":33,"column":64},"end":{"line":33,"column":87}},{"start":{"line":33,"column":91},"end":{"line":33,"column":95}}]},"6":{"loc":{"start":{"line":38,"column":24},"end":{"line":38,"column":74}},"type":"cond-expr","locations":[{"start":{"line":38,"column":57},"end":{"line":38,"column":67}},{"start":{"line":38,"column":70},"end":{"line":38,"column":74}}]},"7":{"loc":{"start":{"line":54,"column":4},"end":{"line":54,"column":55}},"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":54,"column":55}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":54,"column":8},"end":{"line":54,"column":43}},"type":"binary-expr","locations":[{"start":{"line":54,"column":8},"end":{"line":54,"column":14}},{"start":{"line":54,"column":18},"end":{"line":54,"column":43}}]},"9":{"loc":{"start":{"line":57,"column":8},"end":{"line":57,"column":76}},"type":"if","locations":[{"start":{"line":57,"column":8},"end":{"line":57,"column":76}},{"start":{},"end":{}}]}},"s":{"0":19,"1":0,"2":0,"3":0,"4":0,"5":19,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":19,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0},"f":{"0":19,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\healthRoutes.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\healthRoutes.ts","statementMap":{"0":{"start":{"line":5,"column":4},"end":{"line":7,"column":7}},"1":{"start":{"line":6,"column":8},"end":{"line":6,"column":43}}},"fnMap":{"0":{"name":"registerHealthRoutes","decl":{"start":{"line":4,"column":16},"end":{"line":4,"column":36}},"loc":{"start":{"line":4,"column":61},"end":{"line":8,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":5,"column":23},"end":{"line":5,"column":24}},"loc":{"start":{"line":5,"column":56},"end":{"line":7,"column":5}}}},"branchMap":{},"s":{"0":19,"1":1},"f":{"0":19,"1":1},"b":{}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\labNotesRoutes.ts": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\src\\routes\\labNotesRoutes.ts","statementMap":{"0":{"start":{"line":15,"column":4},"end":{"line":73,"column":7}},"1":{"start":{"line":16,"column":8},"end":{"line":72,"column":9}},"2":{"start":{"line":17,"column":31},"end":{"line":22,"column":13}},"3":{"start":{"line":18,"column":28},"end":{"line":18,"column":70}},"4":{"start":{"line":19,"column":16},"end":{"line":19,"column":38}},"5":{"start":{"line":19,"column":26},"end":{"line":19,"column":38}},"6":{"start":{"line":20,"column":28},"end":{"line":20,"column":48}},"7":{"start":{"line":21,"column":16},"end":{"line":21,"column":61}},"8":{"start":{"line":24,"column":27},"end":{"line":24,"column":60}},"9":{"start":{"line":26,"column":28},"end":{"line":31,"column":6}},"10":{"start":{"line":33,"column":27},"end":{"line":41,"column":6}},"11":{"start":{"line":43,"column":32},"end":{"line":52,"column":6}},"12":{"start":{"line":54,"column":27},"end":{"line":56,"column":57}},"13":{"start":{"line":59,"column":27},"end":{"line":65,"column":14}},"14":{"start":{"line":60,"column":32},"end":{"line":62,"column":48}},"15":{"start":{"line":64,"column":16},"end":{"line":64,"column":76}},"16":{"start":{"line":64,"column":68},"end":{"line":64,"column":73}},"17":{"start":{"line":67,"column":12},"end":{"line":67,"column":36}},"18":{"start":{"line":69,"column":12},"end":{"line":69,"column":64}},"19":{"start":{"line":70,"column":12},"end":{"line":70,"column":40}},"20":{"start":{"line":70,"column":33},"end":{"line":70,"column":40}},"21":{"start":{"line":71,"column":12},"end":{"line":71,"column":76}},"22":{"start":{"line":76,"column":4},"end":{"line":96,"column":7}},"23":{"start":{"line":77,"column":25},"end":{"line":77,"column":35}},"24":{"start":{"line":79,"column":21},"end":{"line":86,"column":49}},"25":{"start":{"line":89,"column":8},"end":{"line":89,"column":71}},"26":{"start":{"line":89,"column":19},"end":{"line":89,"column":71}},"27":{"start":{"line":91,"column":24},"end":{"line":93,"column":40}},"28":{"start":{"line":95,"column":8},"end":{"line":95,"column":68}},"29":{"start":{"line":95,"column":59},"end":{"line":95,"column":64}},"30":{"start":{"line":99,"column":4},"end":{"line":256,"column":7}},"31":{"start":{"line":101,"column":21},"end":{"line":101,"column":52}},"32":{"start":{"line":103,"column":8},"end":{"line":105,"column":9}},"33":{"start":{"line":104,"column":12},"end":{"line":104,"column":89}},"34":{"start":{"line":107,"column":21},"end":{"line":107,"column":37}},"35":{"start":{"line":108,"column":22},"end":{"line":108,"column":39}},"36":{"start":{"line":109,"column":25},"end":{"line":109,"column":46}},"37":{"start":{"line":110,"column":21},"end":{"line":110,"column":53}},"38":{"start":{"line":113,"column":23},"end":{"line":113,"column":60}},"39":{"start":{"line":116,"column":21},"end":{"line":116,"column":36}},"40":{"start":{"line":117,"column":30},"end":{"line":117,"column":58}},"41":{"start":{"line":118,"column":22},"end":{"line":118,"column":47}},"42":{"start":{"line":120,"column":31},"end":{"line":120,"column":55}},"43":{"start":{"line":121,"column":30},"end":{"line":121,"column":56}},"44":{"start":{"line":122,"column":34},"end":{"line":122,"column":61}},"45":{"start":{"line":125,"column":23},"end":{"line":125,"column":53}},"46":{"start":{"line":127,"column":14},"end":{"line":127,"column":95}},"47":{"start":{"line":131,"column":12},"end":{"line":133,"column":22}},"48":{"start":{"line":136,"column":25},"end":{"line":136,"column":53}},"49":{"start":{"line":139,"column":12},"end":{"line":144,"column":30}},"50":{"start":{"line":147,"column":25},"end":{"line":147,"column":46}},"51":{"start":{"line":150,"column":25},"end":{"line":152,"column":60}},"52":{"start":{"line":154,"column":23},"end":{"line":154,"column":58}},"53":{"start":{"line":156,"column":19},"end":{"line":242,"column":10}},"54":{"start":{"line":157,"column":12},"end":{"line":232,"column":13}},"55":{"start":{"line":158,"column":16},"end":{"line":192,"column":18}},"56":{"start":{"line":194,"column":16},"end":{"line":194,"column":86}},"57":{"start":{"line":196,"column":16},"end":{"line":231,"column":18}},"58":{"start":{"line":234,"column":30},"end":{"line":235,"column":null}},"59":{"start":{"line":238,"column":12},"end":{"line":241,"column":13}},"60":{"start":{"line":239,"column":28},"end":{"line":239,"column":44}},"61":{"start":{"line":240,"column":16},"end":{"line":240,"column":52}},"62":{"start":{"line":240,"column":25},"end":{"line":240,"column":52}},"63":{"start":{"line":244,"column":8},"end":{"line":255,"column":9}},"64":{"start":{"line":245,"column":12},"end":{"line":245,"column":17}},"65":{"start":{"line":246,"column":12},"end":{"line":252,"column":15}},"66":{"start":{"line":254,"column":12},"end":{"line":254,"column":93}}},"fnMap":{"0":{"name":"registerLabNotesRoutes","decl":{"start":{"line":13,"column":16},"end":{"line":13,"column":38}},"loc":{"start":{"line":13,"column":70},"end":{"line":259,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":15,"column":26},"end":{"line":15,"column":27}},"loc":{"start":{"line":15,"column":58},"end":{"line":73,"column":5}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":17,"column":31},"end":{"line":17,"column":32}},"loc":{"start":{"line":17,"column":50},"end":{"line":22,"column":13}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":59,"column":37},"end":{"line":59,"column":38}},"loc":{"start":{"line":59,"column":46},"end":{"line":65,"column":13}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":64,"column":61},"end":{"line":64,"column":62}},"loc":{"start":{"line":64,"column":68},"end":{"line":64,"column":73}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":76,"column":32},"end":{"line":76,"column":33}},"loc":{"start":{"line":76,"column":64},"end":{"line":96,"column":5}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":95,"column":52},"end":{"line":95,"column":53}},"loc":{"start":{"line":95,"column":59},"end":{"line":95,"column":64}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":99,"column":34},"end":{"line":99,"column":35}},"loc":{"start":{"line":99,"column":66},"end":{"line":256,"column":5}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":156,"column":34},"end":{"line":156,"column":37}},"loc":{"start":{"line":156,"column":39},"end":{"line":242,"column":9}}}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":35},"end":{"line":18,"column":48}},"type":"binary-expr","locations":[{"start":{"line":18,"column":35},"end":{"line":18,"column":40}},{"start":{"line":18,"column":44},"end":{"line":18,"column":48}}]},"1":{"loc":{"start":{"line":19,"column":16},"end":{"line":19,"column":38}},"type":"if","locations":[{"start":{"line":19,"column":16},"end":{"line":19,"column":38}},{"start":{},"end":{}}]},"2":{"loc":{"start":{"line":21,"column":23},"end":{"line":21,"column":60}},"type":"cond-expr","locations":[{"start":{"line":21,"column":39},"end":{"line":21,"column":44}},{"start":{"line":21,"column":48},"end":{"line":21,"column":59}}]},"3":{"loc":{"start":{"line":21,"column":48},"end":{"line":21,"column":59}},"type":"binary-expr","locations":[{"start":{"line":21,"column":48},"end":{"line":21,"column":51}},{"start":{"line":21,"column":55},"end":{"line":21,"column":59}}]},"4":{"loc":{"start":{"line":54,"column":27},"end":{"line":56,"column":57}},"type":"cond-expr","locations":[{"start":{"line":55,"column":22},"end":{"line":55,"column":46}},{"start":{"line":56,"column":22},"end":{"line":56,"column":57}}]},"5":{"loc":{"start":{"line":70,"column":12},"end":{"line":70,"column":40}},"type":"if","locations":[{"start":{"line":70,"column":12},"end":{"line":70,"column":40}},{"start":{},"end":{}}]},"6":{"loc":{"start":{"line":71,"column":49},"end":{"line":71,"column":72}},"type":"binary-expr","locations":[{"start":{"line":71,"column":49},"end":{"line":71,"column":59}},{"start":{"line":71,"column":63},"end":{"line":71,"column":72}}]},"7":{"loc":{"start":{"line":89,"column":8},"end":{"line":89,"column":71}},"type":"if","locations":[{"start":{"line":89,"column":8},"end":{"line":89,"column":71}},{"start":{},"end":{}}]},"8":{"loc":{"start":{"line":103,"column":8},"end":{"line":105,"column":9}},"type":"if","locations":[{"start":{"line":103,"column":8},"end":{"line":105,"column":9}},{"start":{},"end":{}}]},"9":{"loc":{"start":{"line":103,"column":12},"end":{"line":103,"column":55}},"type":"binary-expr","locations":[{"start":{"line":103,"column":12},"end":{"line":103,"column":22}},{"start":{"line":103,"column":26},"end":{"line":103,"column":37}},{"start":{"line":103,"column":41},"end":{"line":103,"column":55}}]},"10":{"loc":{"start":{"line":116,"column":21},"end":{"line":116,"column":36}},"type":"binary-expr","locations":[{"start":{"line":116,"column":21},"end":{"line":116,"column":30}},{"start":{"line":116,"column":34},"end":{"line":116,"column":36}}]},"11":{"loc":{"start":{"line":117,"column":30},"end":{"line":117,"column":58}},"type":"binary-expr","locations":[{"start":{"line":117,"column":30},"end":{"line":117,"column":48}},{"start":{"line":117,"column":52},"end":{"line":117,"column":58}}]},"12":{"loc":{"start":{"line":118,"column":22},"end":{"line":118,"column":47}},"type":"binary-expr","locations":[{"start":{"line":118,"column":22},"end":{"line":118,"column":39}},{"start":{"line":118,"column":43},"end":{"line":118,"column":47}}]},"13":{"loc":{"start":{"line":120,"column":31},"end":{"line":120,"column":55}},"type":"binary-expr","locations":[{"start":{"line":120,"column":31},"end":{"line":120,"column":50}},{"start":{"line":120,"column":54},"end":{"line":120,"column":55}}]},"14":{"loc":{"start":{"line":121,"column":30},"end":{"line":121,"column":56}},"type":"cond-expr","locations":[{"start":{"line":121,"column":51},"end":{"line":121,"column":52}},{"start":{"line":121,"column":55},"end":{"line":121,"column":56}}]},"15":{"loc":{"start":{"line":122,"column":34},"end":{"line":122,"column":61}},"type":"binary-expr","locations":[{"start":{"line":122,"column":34},"end":{"line":122,"column":56}},{"start":{"line":122,"column":60},"end":{"line":122,"column":61}}]},"16":{"loc":{"start":{"line":125,"column":23},"end":{"line":125,"column":53}},"type":"binary-expr","locations":[{"start":{"line":125,"column":23},"end":{"line":125,"column":40}},{"start":{"line":125,"column":44},"end":{"line":125,"column":53}}]},"17":{"loc":{"start":{"line":127,"column":14},"end":{"line":127,"column":95}},"type":"binary-expr","locations":[{"start":{"line":127,"column":14},"end":{"line":127,"column":33}},{"start":{"line":127,"column":38},"end":{"line":127,"column":94}}]},"18":{"loc":{"start":{"line":127,"column":38},"end":{"line":127,"column":94}},"type":"cond-expr","locations":[{"start":{"line":127,"column":73},"end":{"line":127,"column":84}},{"start":{"line":127,"column":87},"end":{"line":127,"column":94}}]},"19":{"loc":{"start":{"line":127,"column":39},"end":{"line":127,"column":62}},"type":"binary-expr","locations":[{"start":{"line":127,"column":39},"end":{"line":127,"column":56}},{"start":{"line":127,"column":60},"end":{"line":127,"column":62}}]},"20":{"loc":{"start":{"line":131,"column":12},"end":{"line":133,"column":22}},"type":"cond-expr","locations":[{"start":{"line":132,"column":19},"end":{"line":132,"column":92}},{"start":{"line":133,"column":18},"end":{"line":133,"column":22}}]},"21":{"loc":{"start":{"line":132,"column":19},"end":{"line":132,"column":92}},"type":"binary-expr","locations":[{"start":{"line":132,"column":19},"end":{"line":132,"column":51}},{"start":{"line":132,"column":55},"end":{"line":132,"column":92}}]},"22":{"loc":{"start":{"line":132,"column":20},"end":{"line":132,"column":43}},"type":"binary-expr","locations":[{"start":{"line":132,"column":20},"end":{"line":132,"column":37}},{"start":{"line":132,"column":41},"end":{"line":132,"column":43}}]},"23":{"loc":{"start":{"line":136,"column":25},"end":{"line":136,"column":53}},"type":"binary-expr","locations":[{"start":{"line":136,"column":25},"end":{"line":136,"column":45}},{"start":{"line":136,"column":49},"end":{"line":136,"column":53}}]},"24":{"loc":{"start":{"line":139,"column":12},"end":{"line":144,"column":30}},"type":"binary-expr","locations":[{"start":{"line":139,"column":12},"end":{"line":139,"column":24}},{"start":{"line":140,"column":12},"end":{"line":144,"column":30}}]},"25":{"loc":{"start":{"line":147,"column":25},"end":{"line":147,"column":46}},"type":"binary-expr","locations":[{"start":{"line":147,"column":25},"end":{"line":147,"column":38}},{"start":{"line":147,"column":42},"end":{"line":147,"column":46}}]},"26":{"loc":{"start":{"line":154,"column":23},"end":{"line":154,"column":58}},"type":"binary-expr","locations":[{"start":{"line":154,"column":23},"end":{"line":154,"column":35}},{"start":{"line":154,"column":39},"end":{"line":154,"column":58}}]},"27":{"loc":{"start":{"line":157,"column":12},"end":{"line":232,"column":13}},"type":"if","locations":[{"start":{"line":157,"column":12},"end":{"line":232,"column":13}},{"start":{"line":195,"column":19},"end":{"line":232,"column":13}}]},"28":{"loc":{"start":{"line":240,"column":16},"end":{"line":240,"column":52}},"type":"if","locations":[{"start":{"line":240,"column":16},"end":{"line":240,"column":52}},{"start":{},"end":{}}]},"29":{"loc":{"start":{"line":251,"column":24},"end":{"line":251,"column":56}},"type":"cond-expr","locations":[{"start":{"line":251,"column":35},"end":{"line":251,"column":44}},{"start":{"line":251,"column":47},"end":{"line":251,"column":56}}]},"30":{"loc":{"start":{"line":254,"column":60},"end":{"line":254,"column":89}},"type":"binary-expr","locations":[{"start":{"line":254,"column":60},"end":{"line":254,"column":70}},{"start":{"line":254,"column":74},"end":{"line":254,"column":89}}]}},"s":{"0":19,"1":2,"2":2,"3":0,"4":0,"5":0,"6":0,"7":0,"8":2,"9":2,"10":2,"11":2,"12":2,"13":2,"14":2,"15":2,"16":0,"17":2,"18":0,"19":0,"20":0,"21":0,"22":19,"23":2,"24":2,"25":2,"26":1,"27":1,"28":1,"29":0,"30":19,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0},"f":{"0":19,"1":2,"2":0,"3":2,"4":0,"5":2,"6":0,"7":0,"8":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,2],"5":[0,0],"6":[0,0],"7":[1,1],"8":[0,0],"9":[0,0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0]}} -,"C:\\Users\\darab\\WebstormProjects\\lab-api\\tests\\helpers\\createTestApp.js": {"path":"C:\\Users\\darab\\WebstormProjects\\lab-api\\tests\\helpers\\createTestApp.js","statementMap":{"0":{"start":{"line":10,"column":19},"end":{"line":10,"column":47}},"1":{"start":{"line":11,"column":14},"end":{"line":11,"column":54}},"2":{"start":{"line":12,"column":4},"end":{"line":12,"column":27}},"3":{"start":{"line":18,"column":4},"end":{"line":20,"column":5}},"4":{"start":{"line":19,"column":8},"end":{"line":19,"column":47}},"5":{"start":{"line":22,"column":16},"end":{"line":22,"column":25}},"6":{"start":{"line":23,"column":4},"end":{"line":23,"column":28}},"7":{"start":{"line":25,"column":15},"end":{"line":25,"column":33}},"8":{"start":{"line":27,"column":4},"end":{"line":27,"column":34}},"9":{"start":{"line":28,"column":4},"end":{"line":28,"column":36}},"10":{"start":{"line":29,"column":4},"end":{"line":29,"column":33}},"11":{"start":{"line":30,"column":4},"end":{"line":30,"column":39}},"12":{"start":{"line":32,"column":4},"end":{"line":32,"column":20}},"13":{"start":{"line":33,"column":4},"end":{"line":33,"column":23}},"14":{"start":{"line":35,"column":4},"end":{"line":35,"column":23}}},"fnMap":{"0":{"name":"api","decl":{"start":{"line":9,"column":16},"end":{"line":9,"column":19}},"loc":{"start":{"line":9,"column":26},"end":{"line":13,"column":1}},"line":9},"1":{"name":"createTestApp","decl":{"start":{"line":15,"column":16},"end":{"line":15,"column":29}},"loc":{"start":{"line":15,"column":32},"end":{"line":36,"column":1}},"line":15}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":19},"end":{"line":10,"column":47}},"type":"binary-expr","locations":[{"start":{"line":10,"column":19},"end":{"line":10,"column":41}},{"start":{"line":10,"column":45},"end":{"line":10,"column":47}}],"line":10},"1":{"loc":{"start":{"line":11,"column":14},"end":{"line":11,"column":54}},"type":"cond-expr","locations":[{"start":{"line":11,"column":37},"end":{"line":11,"column":41}},{"start":{"line":11,"column":44},"end":{"line":11,"column":54}}],"line":11},"2":{"loc":{"start":{"line":18,"column":4},"end":{"line":20,"column":5}},"type":"if","locations":[{"start":{"line":18,"column":4},"end":{"line":20,"column":5}},{"start":{},"end":{}}],"line":18},"3":{"loc":{"start":{"line":18,"column":10},"end":{"line":18,"column":46}},"type":"binary-expr","locations":[{"start":{"line":18,"column":10},"end":{"line":18,"column":40}},{"start":{"line":18,"column":44},"end":{"line":18,"column":46}}],"line":18}},"s":{"0":24,"1":24,"2":24,"3":19,"4":5,"5":19,"6":19,"7":19,"8":19,"9":19,"10":19,"11":19,"12":19,"13":19,"14":19},"f":{"0":24,"1":19},"b":{"0":[24,24],"1":[24,0],"2":[5,14],"3":[19,5]},"inputSourceMap":null,"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c48379347b9e62084670e617d8b509ef07d64a76"} -} -======= -{} ->>>>>>> origin/main diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css deleted file mode 100644 index f418035..0000000 --- a/coverage/lcov-report/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/lcov-report/block-navigation.js b/coverage/lcov-report/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/coverage/lcov-report/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/coverage/lcov-report/favicon.png b/coverage/lcov-report/favicon.png deleted file mode 100644 index c1525b8..0000000 Binary files a/coverage/lcov-report/favicon.png and /dev/null differ diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html deleted file mode 100644 index 067958c..0000000 --- a/coverage/lcov-report/index.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
-<<<<<<< HEAD - 48.77% - Statements - 219/449 -======= - Unknown% - Statements - 0/0 ->>>>>>> origin/main -
- - -
-<<<<<<< HEAD - 37.5% - Branches - 162/432 -======= - Unknown% - Branches - 0/0 ->>>>>>> origin/main -
- - -
-<<<<<<< HEAD - 50% - Functions - 45/90 -======= - Unknown% - Functions - 0/0 ->>>>>>> origin/main -
- - -
-<<<<<<< HEAD - 51.75% - Lines - 207/400 -======= - Unknown% - Lines - 0/0 ->>>>>>> origin/main -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-<<<<<<< HEAD -
-======= -
->>>>>>> origin/main -
- - - - - - - - - - - - - - - -<<<<<<< HEAD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -======= - ->>>>>>> origin/main -
FileStatementsBranchesFunctionsLines
src -
-
49.16%59/12033.96%36/10653.84%14/2650.9%56/110
src/auth -
-
0%0/400%0/360%0/90%0/34
src/db -
-
80.88%55/6868.57%24/3566.66%8/1287.09%54/62
src/db/migrations -
-
75%3/466.66%2/3100%1/1100%3/3
src/lib -
-
55.55%5/950%5/10100%1/166.66%4/6
src/mappers -
-
81.81%18/2260.56%43/71100%5/5100%16/16
src/middleware -
-
11.53%3/260%0/230%0/512%3/25
src/routes -
-
42.06%61/14532.14%45/14048.27%14/2943.41%56/129
tests/helpers -
-
100%15/1587.5%7/8100%2/2100%15/15
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/migrateLabNotes.ts.html b/coverage/lcov-report/migrateLabNotes.ts.html deleted file mode 100644 index f784e03..0000000 --- a/coverage/lcov-report/migrateLabNotes.ts.html +++ /dev/null @@ -1,2161 +0,0 @@ - - - - - - Code coverage report for migrateLabNotes.ts - - - - - - - - - -
-
-

All files migrateLabNotes.ts

-
- -
- 79.31% - Statements - 46/58 -
- - -
- 70.96% - Branches - 22/31 -
- - -
- 55.55% - Functions - 5/9 -
- - -
- 84.9% - Lines - 45/53 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 -492 -493 -494 -495 -496 -497 -498 -499 -500 -501 -502 -503 -504 -505 -506 -507 -508 -509 -510 -511 -512 -513 -514 -515 -516 -517 -518 -519 -520 -521 -522 -523 -524 -525 -526 -527 -528 -529 -530 -531 -532 -533 -534 -535 -536 -537 -538 -539 -540 -541 -542 -543 -544 -545 -546 -547 -548 -549 -550 -551 -552 -553 -554 -555 -556 -557 -558 -559 -560 -561 -562 -563 -564 -565 -566 -567 -568 -569 -570 -571 -572 -573 -574 -575 -576 -577 -578 -579 -580 -581 -582 -583 -584 -585 -586 -587 -588 -589 -590 -591 -592 -593 -594 -595 -596 -597 -598 -599 -600 -601 -602 -603 -604 -605 -606 -607 -608 -609 -610 -611 -612 -613 -614 -615 -616 -617 -618 -619 -620 -621 -622 -623 -624 -625 -626 -627 -628 -629 -630 -631 -632 -633 -634 -635 -636 -637 -638 -639 -640 -641 -642 -643 -644 -645 -646 -647 -648 -649 -650 -651 -652 -653 -654 -655 -656 -657 -658 -659 -660 -661 -662 -663 -664 -665 -666 -667 -668 -669 -670 -671 -672 -673 -674 -675 -676 -677 -678 -679 -680 -681 -682 -683 -684 -685 -686 -687 -688 -689 -690 -691 -692 -693  -  -  -  -1x -  -  -6x -  -  -  -6x -  -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -6x -  -  -  -  -  -  -  -6x -  -  -  -  -6x -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -68x -  -  -6x -6x -186x -124x -124x -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -6x -4x -  -4x -4x -  -  -  -  -  -  -  -4x -1x -  -  -  -1x -  -  -1x -  -  -  -4x -4x -  -  -  -  -6x -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -6x -  -  -  -  -  -6x -  -  -  -  -  -  -  -2x -  -  -  -2x -  -2x -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  - 
// src/db/migrateLabNotes.ts
-import Database from "better-sqlite3";
-import crypto from "crypto";
- 
-export const LAB_NOTES_SCHEMA_VERSION = 8;
- 
-function setLabNotesSchemaVersion(db: Database.Database, version: number) {
-    const cur = db
-        .prepare(`SELECT value FROM schema_meta WHERE key='lab_notes_schema_version'`)
-        .get() as { value?: string } | undefined;
- 
-    if (cur?.value === String(version)) return;
- 
-    db.prepare(`
-        INSERT INTO schema_meta (key, value)
-        VALUES ('lab_notes_schema_version', ?)
-        ON CONFLICT(key) DO UPDATE SET value = excluded.value
-    `).run(String(version));
-}
- 
- 
-type ColDef = { name: string; ddl: string };
-type MigrationLogFn = (msg: string) => void;
- 
-export type MigrationResult = {
-    addedColumns: string[];
-    createdFreshTable: boolean;
-    ranV2DataMigration: boolean;
-};
- 
-function sha256Hex(input: string): string {
-    return crypto.createHash("sha256").update(input, "utf8").digest("hex");
-}
- 
-function nowIso(): string {
-    return new Date().toISOString();
-}
- 
-function getLabNotesSchemaVersion(db: Database.Database): number {
-    const row = db
-        .prepare(`SELECT value FROM schema_meta WHERE key='lab_notes_schema_version'`)
-        .get() as { value?: string } | undefined;
- 
-    return row?.value ? Number(row.value) : 0;
-}
- 
- 
-/**
- * v2 model: lab_notes (identity + pointers) + lab_note_revisions (append-only truth)
- *
- * This migrator is intentionally idempotent:
- * - CREATE TABLE IF NOT EXISTS
- * - CREATE INDEX IF NOT EXISTS
- * - ALTER TABLE ADD COLUMN only when missing
- *
- * For installs that had v1's wide `lab_notes` table, we:
- * - keep the existing columns (non-destructive)
- * - add v2 pointer columns
- * - create v2 tables
- * - perform a one-time migration to create revision_num=1 records and set pointers
- */
- 
-export function migrateLabNotesSchema(
- 
-    db: Database.Database,
-    log?: MigrationLogFn
-): MigrationResult {
-    // Did the table exist before?
-    const hadLabNotesTable = !!db
-        .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='lab_notes'`)
-        .get();
- 
-    // Ensure schema_meta exists early (we read it before doing versioned work)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS schema_meta (
-                                                   key TEXT PRIMARY KEY,
-                                                   value TEXT NOT NULL
-        );
-    `);
- 
-    // Ensure lab_notes exists (seed)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS lab_notes (
-                                                 id TEXT PRIMARY KEY
-        );
-    `);
-    const prevVersion = getLabNotesSchemaVersion(db);
-    // --- v1 compatibility columns (existing wide model) ---
-    // Keep your original columns so older code / existing rows remain valid.
-    // We also add the v2 pointer columns and some core identity fields needed for 1.0.
-    const LAB_NOTES_REQUIRED_COLS: ColDef[] = [
-        // Core identity (v1 already had these; keep enforcing)
-        { name: "group_id", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "slug", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "locale", ddl: "TEXT NOT NULL DEFAULT 'en'" },
- 
-        // Basics
-        { name: "type", ddl: "TEXT NOT NULL DEFAULT 'labnote'" },
-        { name: "title", ddl: "TEXT NOT NULL DEFAULT ''" },
- 
-        // Kept attributes
-        { name: "category", ddl: "TEXT" },
-        { name: "excerpt", ddl: "TEXT" },
-        { name: "department_id", ddl: "TEXT" },
-        { name: "shadow_density", ddl: "REAL" },
-        { name: "safer_landing", ddl: "INTEGER" },
-        { name: "read_time_minutes", ddl: "INTEGER" },
- 
-        { name: "coherence_score", ddl: "REAL" },
-        { name: "subtitle", ddl: "TEXT" },
-        { name: "summary", ddl: "TEXT" },
- 
-        { name: "tags_json", ddl: "TEXT" },
-        { name: "dept", ddl: "TEXT" },
- 
-        // Publishing
-        { name: "status", ddl: "TEXT NOT NULL DEFAULT 'draft'" },
-        { name: "published_at", ddl: "TEXT" },
- 
-        // Authors
-        { name: "author", ddl: "TEXT" },
-        { name: "ai_author", ddl: "TEXT" },
- 
-        // Translation metadata
-        { name: "source_locale", ddl: "TEXT" },
-        { name: "translation_status", ddl: "TEXT NOT NULL DEFAULT 'original'" },
-        { name: "translation_provider", ddl: "TEXT" },
-        { name: "translation_version", ddl: "INTEGER NOT NULL DEFAULT 1" },
-        { name: "source_updated_at", ddl: "TEXT" },
-        { name: "translation_meta_json", ddl: "TEXT" },
- 
-        // Content artifacts (no legacy markdown column anymore)
-        { name: "content_html", ddl: "TEXT" },
- 
-        // Timestamps
-        { name: "created_at", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "updated_at", ddl: "TEXT NOT NULL DEFAULT ''" },
- 
-        // ---- v2 pointer fields (new) ----
-        { name: "current_revision_id", ddl: "TEXT" },
-        { name: "published_revision_id", ddl: "TEXT" },
-    ];
- 
-    const existingCols = new Set<string>(
-        db.prepare(`PRAGMA table_info(lab_notes)`).all().map((r: any) => r.name)
-    );
- 
-    const addedColumns: string[] = [];
-    for (const col of LAB_NOTES_REQUIRED_COLS) {
-        if (!existingCols.has(col.name)) {
-            db.exec(`ALTER TABLE lab_notes ADD COLUMN ${col.name} ${col.ddl};`);
-            addedColumns.push(col.name);
-        }
-    }
- 
-    // Backfills for legacy rows
-    db.exec(`
-        UPDATE lab_notes
-        SET group_id = id
-        WHERE group_id IS NULL OR group_id = '';
- 
-        UPDATE lab_notes SET slug = id WHERE slug IS NULL OR slug = '';
-        UPDATE lab_notes SET title = slug WHERE title IS NULL OR title = '';
- 
-        UPDATE lab_notes
-        SET created_at = COALESCE(created_at, updated_at, datetime('now'))
-        WHERE created_at IS NULL OR created_at = '';
- 
-        UPDATE lab_notes
-        SET updated_at = COALESCE(updated_at, created_at, datetime('now'))
-        WHERE updated_at IS NULL OR updated_at = '';
-    `);
- 
-    // Indexes (idempotent)
-    db.exec(`
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_locale ON lab_notes(locale);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_status ON lab_notes(status);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_published_at ON lab_notes(published_at);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_group_id ON lab_notes(group_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_department_id ON lab_notes(department_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_slug_locale ON lab_notes(slug, locale);
-    `);
- 
-    // Optional tag table (normalized tags)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS lab_note_tags (
-                                                     note_id TEXT NOT NULL,
-                                                     tag TEXT NOT NULL,
-                                                     UNIQUE(note_id, tag)
-        );
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_note_id ON lab_note_tags(note_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_tag ON lab_note_tags(tag);
-    `);
- 
-    // ---- v2 tables: append-only revisions + proposals + events ----
-    db.exec(`
-    PRAGMA foreign_keys = ON;
- 
-    CREATE TABLE IF NOT EXISTS lab_note_revisions (
-      id TEXT PRIMARY KEY,
-      note_id TEXT NOT NULL,
- 
-      revision_num INTEGER NOT NULL,
-      supersedes_revision_id TEXT NULL,
- 
-      frontmatter_json TEXT NOT NULL,
-      content_markdown TEXT NOT NULL,
-      content_hash TEXT NOT NULL,
- 
-      schema_version TEXT NOT NULL,
-      source TEXT NOT NULL CHECK (source IN ('cli','web','api','import')),
- 
-      intent TEXT NOT NULL,
-      intent_version TEXT NOT NULL DEFAULT '1',
- 
-      scope_json TEXT NOT NULL DEFAULT '[]',
-      side_effects_json TEXT NOT NULL DEFAULT '[]',
-      reversible INTEGER NOT NULL DEFAULT 1 CHECK (reversible IN (0,1)),
- 
-      auth_type TEXT NOT NULL CHECK (auth_type IN ('human_session','lab_token')),
-      scopes_json TEXT NOT NULL DEFAULT '[]',
- 
-      reasoning_json TEXT NULL,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      UNIQUE (note_id, revision_num),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE CASCADE,
-      FOREIGN KEY (supersedes_revision_id) REFERENCES lab_note_revisions(id)
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_revisions_note ON lab_note_revisions(note_id);
-    CREATE INDEX IF NOT EXISTS idx_revisions_intent ON lab_note_revisions(intent);
-    CREATE INDEX IF NOT EXISTS idx_revisions_hash ON lab_note_revisions(content_hash);
- 
-    CREATE TABLE IF NOT EXISTS lab_note_proposals (
-      id TEXT PRIMARY KEY,
-      note_id TEXT NOT NULL,
- 
-      base_revision_id TEXT NOT NULL,
-      proposed_revision_id TEXT NOT NULL,
- 
-      status TEXT NOT NULL DEFAULT 'pending'
-        CHECK (status IN ('pending','accepted','rejected','withdrawn')),
- 
-      created_by TEXT NOT NULL,
-      created_by_type TEXT NOT NULL CHECK (created_by_type IN ('human','ai','system')),
- 
-      reviewed_by TEXT NULL,
-      reviewed_at TEXT NULL,
-      review_comment TEXT NULL,
- 
-      diff_patch TEXT NULL,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE CASCADE,
-      FOREIGN KEY (base_revision_id) REFERENCES lab_note_revisions(id),
-      FOREIGN KEY (proposed_revision_id) REFERENCES lab_note_revisions(id)
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_proposals_note ON lab_note_proposals(note_id);
-    CREATE INDEX IF NOT EXISTS idx_proposals_status ON lab_note_proposals(status);
- 
-    CREATE TABLE IF NOT EXISTS lab_events (
-      id TEXT PRIMARY KEY,
- 
-      event_type TEXT NOT NULL,
-      note_id TEXT NULL,
-      revision_id TEXT NULL,
-      proposal_id TEXT NULL,
- 
-      intent TEXT NULL,
-      intent_version TEXT NULL,
- 
-      actor_type TEXT NOT NULL CHECK (actor_type IN ('human','ai','system')),
-      actor_id TEXT NOT NULL,
- 
-      auth_type TEXT NULL CHECK (auth_type IN ('human_session','lab_token')),
-      scopes_json TEXT NULL,
- 
-      payload_json TEXT NOT NULL DEFAULT '{}',
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE SET NULL,
-      FOREIGN KEY (revision_id) REFERENCES lab_note_revisions(id) ON DELETE SET NULL,
-      FOREIGN KEY (proposal_id) REFERENCES lab_note_proposals(id) ON DELETE SET NULL
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_events_type ON lab_events(event_type);
-    CREATE INDEX IF NOT EXISTS idx_events_note ON lab_events(note_id);
-    CREATE INDEX IF NOT EXISTS idx_events_created_at ON lab_events(created_at);
-  `);
- 
-    let ranV2DataMigration = false;
- 
-// ---- v2 data migration (one-time) ----
-    if (prevVersion < 2) {
-        let seededCount = 0;
- 
-        const tx = db.transaction(() => {
-            const rows = db.prepare(`
-      SELECT id, slug, locale, type, title, subtitle, summary, tags_json, dept,
-             status, published_at, author, ai_author, created_at, updated_at
-      FROM lab_notes
-    `).all() as any[];
- 
-            // ...prepare statements...
- 
-            for (const n of rows) {
-                const pointer = db
-                    .prepare(`SELECT current_revision_id FROM lab_notes WHERE id = ?`)
-                    .get(n.id) as { current_revision_id?: string } | undefined;
- 
-                Iif (pointer?.current_revision_id) continue;
- 
-                // ...insert revision, update pointers, insert event...
-                seededCount++;
-            }
-        });
- 
-        tx();
-        ranV2DataMigration = seededCount > 0;
-    }
- 
- 
-    // ---- drop legacy markdown column (table rebuild) ----
-    if (prevVersion < 7) {
-        db.exec(`
-      PRAGMA foreign_keys = OFF;
- 
-      BEGIN;
- 
-      DROP VIEW IF EXISTS v_lab_notes;
-      DROP VIEW IF EXISTS v_lab_notes_current;
- 
-      DROP TABLE IF EXISTS lab_notes_new;
- 
-      CREATE TABLE lab_notes_new (
-        id TEXT PRIMARY KEY,
- 
-        group_id TEXT NOT NULL,
-        slug TEXT NOT NULL,
-        locale TEXT NOT NULL,
- 
-        type TEXT NOT NULL,
-        title TEXT NOT NULL,
- 
-        category TEXT,
-        excerpt TEXT,
-        department_id TEXT,
-        shadow_density REAL,
-        safer_landing INTEGER,
-        read_time_minutes INTEGER,
- 
-        coherence_score REAL,
-        subtitle TEXT,
-        summary TEXT,
- 
-        tags_json TEXT,
-        dept TEXT,
- 
-        status TEXT NOT NULL,
-        published_at TEXT,
- 
-        author TEXT,
-        ai_author TEXT,
- 
-        source_locale TEXT,
-        translation_status TEXT NOT NULL,
-        translation_provider TEXT,
-        translation_version INTEGER NOT NULL,
-        source_updated_at TEXT,
-        translation_meta_json TEXT,
- 
-        content_html TEXT,
- 
-        current_revision_id TEXT,
-        published_revision_id TEXT,
- 
-        created_at TEXT NOT NULL,
-        updated_at TEXT NOT NULL
-      );
- 
-      INSERT INTO lab_notes_new (
-        id, group_id, slug, locale,
-        type, title,
-        category, excerpt, department_id,
-        shadow_density, safer_landing, read_time_minutes,
-        coherence_score, subtitle, summary,
-        tags_json, dept,
-        status, published_at,
-        author, ai_author,
-        source_locale, translation_status, translation_provider,
-        translation_version, source_updated_at, translation_meta_json,
-        content_html,
-        current_revision_id, published_revision_id,
-        created_at, updated_at
-      )
-      SELECT
-        id, group_id, slug, locale,
-        type, title,
-        category, excerpt, department_id,
-        shadow_density, safer_landing, read_time_minutes,
-        coherence_score, subtitle, summary,
-        tags_json, dept,
-        status, published_at,
-        author, ai_author,
-        source_locale, translation_status, translation_provider,
-        translation_version, source_updated_at, translation_meta_json,
-        content_html,
-        current_revision_id, published_revision_id,
-        created_at, updated_at
-      FROM lab_notes;
- 
-      DROP TABLE lab_notes;
-      ALTER TABLE lab_notes_new RENAME TO lab_notes;
- 
-      COMMIT;
- 
-      PRAGMA foreign_keys = ON;
-    `);
-    }
- 
-    // ---- v8: add real defaults + enforce unique (slug, locale) ----
-    if (prevVersion < 8) {
-        db.exec(`
-    PRAGMA foreign_keys = OFF;
-    BEGIN;
- 
-    DROP VIEW IF EXISTS v_lab_notes;
-    DROP VIEW IF EXISTS v_lab_notes_current;
- 
-    -- 1) DEDUPE by (slug, locale), keep most recently updated
-    DELETE FROM lab_notes
-    WHERE id IN (
-      SELECT id FROM (
-        SELECT
-          id,
-          ROW_NUMBER() OVER (
-            PARTITION BY slug, locale
-            ORDER BY updated_at DESC, created_at DESC
-          ) AS rn
-        FROM lab_notes
-      )
-      WHERE rn > 1
-    );
- 
-    -- 2) Rebuild lab_notes with DEFAULTs
-    DROP TABLE IF EXISTS lab_notes_new;
- 
-    CREATE TABLE lab_notes_new (
-      id TEXT PRIMARY KEY,
- 
-      group_id TEXT NOT NULL DEFAULT 'core',
-      slug TEXT NOT NULL,
-      locale TEXT NOT NULL DEFAULT 'en',
- 
-      type TEXT NOT NULL DEFAULT 'labnote',
-      title TEXT NOT NULL DEFAULT '',
- 
-      category TEXT,
-      excerpt TEXT,
-      department_id TEXT DEFAULT 'SCMS',
-      shadow_density REAL DEFAULT 0,
-      safer_landing INTEGER DEFAULT 0,
-      read_time_minutes INTEGER DEFAULT 5,
- 
-      coherence_score REAL DEFAULT 1.0,
-      subtitle TEXT,
-      summary TEXT,
- 
-      tags_json TEXT,
-      dept TEXT,
- 
-      status TEXT NOT NULL DEFAULT 'draft',
-      published_at TEXT,
- 
-      author TEXT,
-      ai_author TEXT,
- 
-      source_locale TEXT,
-      translation_status TEXT NOT NULL DEFAULT 'original',
-      translation_provider TEXT,
-      translation_version INTEGER NOT NULL DEFAULT 1,
-      source_updated_at TEXT,
-      translation_meta_json TEXT,
- 
-      content_html TEXT,
- 
-      current_revision_id TEXT,
-      published_revision_id TEXT,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
-      updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
-    );
- 
-    INSERT INTO lab_notes_new (
-      id, group_id, slug, locale,
-      type, title,
-      category, excerpt, department_id,
-      shadow_density, safer_landing, read_time_minutes,
-      coherence_score, subtitle, summary,
-      tags_json, dept,
-      status, published_at,
-      author, ai_author,
-      source_locale, translation_status, translation_provider,
-      translation_version, source_updated_at, translation_meta_json,
-      content_html,
-      current_revision_id, published_revision_id,
-      created_at, updated_at
-    )
-    SELECT
-      id,
-      COALESCE(NULLIF(group_id,''), id, 'core'),
-      slug,
-      LOWER(COALESCE(NULLIF(locale,''), 'en')),
- 
-      COALESCE(NULLIF(type,''), 'labnote'),
-      COALESCE(NULLIF(title,''), ''),
- 
-      category,
-      excerpt,
-      COALESCE(NULLIF(department_id,''), 'SCMS'),
-      COALESCE(shadow_density, 0),
-      COALESCE(safer_landing, 0),
-      COALESCE(read_time_minutes, 5),
- 
-      COALESCE(coherence_score, 1.0),
-      subtitle,
-      summary,
- 
-      tags_json,
-      dept,
- 
-      COALESCE(NULLIF(status,''), CASE WHEN published_at IS NOT NULL THEN 'published' ELSE 'draft' END),
-      published_at,
- 
-      author,
-      ai_author,
- 
-      source_locale,
-      COALESCE(NULLIF(translation_status,''), 'original'),
-      translation_provider,
-      COALESCE(translation_version, 1),
-      source_updated_at,
-      translation_meta_json,
- 
-      content_html,
- 
-      current_revision_id,
-      published_revision_id,
- 
-      COALESCE(NULLIF(created_at,''), strftime('%Y-%m-%dT%H:%M:%fZ','now')),
-      COALESCE(NULLIF(updated_at,''), COALESCE(NULLIF(created_at,''), strftime('%Y-%m-%dT%H:%M:%fZ','now')))
-    FROM lab_notes;
- 
-    DROP TABLE lab_notes;
-    ALTER TABLE lab_notes_new RENAME TO lab_notes;
- 
-    -- 3) Now enforce uniqueness correctly for localized slugs
-    CREATE UNIQUE INDEX IF NOT EXISTS uq_lab_notes_slug_locale ON lab_notes(slug, locale);
- 
-    COMMIT;
-    PRAGMA foreign_keys = ON;
-  `);
-    }
- 
- 
-    // ✅ Views created LAST (after any rebuild)
-    db.exec(`
-    DROP VIEW IF EXISTS v_lab_notes;
- 
-    CREATE VIEW v_lab_notes AS
-    SELECT
-      id,
-      group_id,
-      slug,
-      locale,
-      type,
-      title,
- 
-      category,
-      excerpt,
-      department_id,
-      shadow_density,
-      safer_landing,
-      read_time_minutes,
-      coherence_score,
-      subtitle,
-      summary,
-      tags_json,
-      dept,
- 
-      status,
-      published_at,
-      author,
-      ai_author,
- 
-      source_locale,
-      translation_status,
-      translation_provider,
-      translation_version,
-      source_updated_at,
-      translation_meta_json,
- 
-      content_html,
- 
-      current_revision_id,
-      published_revision_id,
- 
-      created_at,
-      updated_at
-    FROM lab_notes;
- 
-    DROP VIEW IF EXISTS v_lab_notes_current;
- 
-    CREATE VIEW v_lab_notes_current AS
-    SELECT
-      n.id AS note_id,
-      n.slug,
-      n.locale,
-      n.title,
-      n.status,
-      n.author,
-      n.ai_author,
-      n.current_revision_id,
-      n.published_revision_id,
-      r.revision_num,
-      r.schema_version,
-      r.source,
-      r.intent,
-      r.intent_version,
-      r.scope_json,
-      r.side_effects_json,
-      r.reversible,
-      r.auth_type,
-      r.scopes_json,
-      r.frontmatter_json,
-      r.content_markdown,
-      r.content_hash,
-      r.created_at AS revision_created_at,
-      n.created_at,
-      n.updated_at
-    FROM lab_notes n
-    LEFT JOIN lab_note_revisions r
-      ON r.id = n.current_revision_id;
-  `);
- 
-    // Set DB version last (after successful schema + data migration)
-    setLabNotesSchemaVersion(db, LAB_NOTES_SCHEMA_VERSION);
- 
-    const result: MigrationResult = {
-        addedColumns,
-        createdFreshTable: !hadLabNotesTable,
-        ranV2DataMigration,
-    };
- 
-    if (
-        log &&
-        (result.createdFreshTable ||
-            result.addedColumns.length > 0 ||
-            prevVersion !== LAB_NOTES_SCHEMA_VERSION ||
-            ranV2DataMigration)
-    ) {
-        const colsPart =
-            result.addedColumns.length > 0
-                ? `added ${result.addedColumns.length} column(s): ${result.addedColumns.join(", ")}`
-                : "no column changes";
- 
-        const migPart = ranV2DataMigration ? "ran v2 revision seeding" : "no v2 data migration";
- 
-        log(
-            `[db] lab_notes migration: v${prevVersion} → v${LAB_NOTES_SCHEMA_VERSION}; ${colsPart}; ${migPart}`
-        );
-    }
- 
-    return result;
-}
- 
-function safeParseJsonArray(input: any): string[] {
-    if (!input || typeof input !== "string") return [];
-    try {
-        const v = JSON.parse(input);
-        if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
-        return [];
-    } catch {
-        return [];
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/coverage/lcov-report/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js deleted file mode 100644 index b322523..0000000 --- a/coverage/lcov-report/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/lcov-report/sort-arrow-sprite.png b/coverage/lcov-report/sort-arrow-sprite.png deleted file mode 100644 index 6ed6831..0000000 Binary files a/coverage/lcov-report/sort-arrow-sprite.png and /dev/null differ diff --git a/coverage/lcov-report/sorter.js b/coverage/lcov-report/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/coverage/lcov-report/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/coverage/lcov-report/src/auth.ts.html b/coverage/lcov-report/src/auth.ts.html deleted file mode 100644 index d07da61..0000000 --- a/coverage/lcov-report/src/auth.ts.html +++ /dev/null @@ -1,479 +0,0 @@ - - - - - - Code coverage report for src/auth.ts - - - - - - - - - -
-
-

All files / src auth.ts

-
- -
- 27.08% - Statements - 13/48 -
- - -
- 24.39% - Branches - 10/41 -
- - -
- 35.71% - Functions - 5/14 -
- - -
- 29.26% - Lines - 12/41 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -20x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -12x -  -12x -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -7x -  -17x -  -  -  -13x -  -  -4x -  -  -4x -  -  -  -4x -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
// src/auth.ts
-import type { Request, Response, NextFunction } from "express";
-import passport from "passport";
-import { Strategy as GitHubStrategy } from "passport-github2";
- 
-type EnvKey = "CLIENT_ID" | "CLIENT_SECRET" | "CALLBACK_URL";
- 
-function getGithubOAuthEnv(key: EnvKey): string | undefined {
-    return (
-        process.env[`OAUTH_GITHUB_${key}`] ||
-        process.env[`HPL_OAUTH_GITHUB_${key}`] ||
-        process.env[`GITHUB_OAUTH_${key}`]
-    );
-}
- 
-export function isGithubOAuthEnabled(): boolean {
-    Eif (process.env.NODE_ENV === "test") return false;
-    return Boolean(
-        getGithubOAuthEnv("CLIENT_ID") &&
-        getGithubOAuthEnv("CLIENT_SECRET") &&
-        getGithubOAuthEnv("CALLBACK_URL")
-    );
-}
- 
-function missingGithubOAuthKeys(): EnvKey[] {
-    const keys: EnvKey[] = ["CLIENT_ID", "CLIENT_SECRET", "CALLBACK_URL"];
-    return keys.filter((k) => !getGithubOAuthEnv(k));
-}
- 
-export function configurePassport() {
-    if (process.env.NODE_ENV === "test") return;
- 
-    const missing = missingGithubOAuthKeys();
-    if (missing.length > 0) {
-        console.warn(
-            `⚠️ GitHub OAuth disabled (missing: ${missing.join(", ")}). ` +
-            `Provide one of: OAUTH_GITHUB_*, HPL_OAUTH_GITHUB_*, or GITHUB_OAUTH_* variants.`
-        );
-        return;
-    }
- 
-    const clientID = getGithubOAuthEnv("CLIENT_ID")!;
-    const clientSecret = getGithubOAuthEnv("CLIENT_SECRET")!;
-    const callbackURL = getGithubOAuthEnv("CALLBACK_URL")!;
- 
-    passport.use(
-        new GitHubStrategy(
-            { clientID, clientSecret, callbackURL },
-            (_accessToken: any, _refreshToken: any, profile: any, done: any) => {
-                const allowed = process.env.ALLOWED_GITHUB_USERNAME;
-                if (allowed && profile?.username !== allowed) return done(null, false);
-                return done(null, profile);
-            }
-        )
-    );
- 
-    passport.serializeUser((user: any, done) => done(null, user));
-    passport.deserializeUser((user: any, done) => done(null, user));
-}
- 
-export const ensureAuthenticated = (
-    req: Request,
-    res: Response,
-    next: NextFunction
-) => {
-    if (req.isAuthenticated?.()) return next();
-    return res.status(401).json({ error: "Unauthorized" });
-};
- 
-export function getGithubLogin(user: any): string | null {
-    const raw = String(user?.username ?? user?.login ?? "").trim();
-    return raw ? raw.toLowerCase() : null;
-}
- 
-function parseAllowlist(envValue?: string): string[] {
-    return String(envValue ?? "")
-        .split(",")
-        .map((s) => s.trim().toLowerCase())
-        .filter(Boolean);
-}
- 
-// ✅ Single source of truth for admin allowlist
-function buildAdminAllowlist(): Set<string> {
-    return new Set<string>([
-        ...parseAllowlist(process.env.ADMIN_GITHUB_USERS),
-        ...parseAllowlist(process.env.ADMIN_GITHUB_LOGINS),
-        ...parseAllowlist(process.env.ALLOWED_GITHUB_USERNAME),
-    ]);
-}
- 
-/**
- * Admin gate (API-side). Use this on /admin/* routes.
- *
- * Semantics:
- * - 401: not authenticated (no session)
- * - 403: authenticated but not authorized OR allowlist not configured (prod)
- */
-export const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
-    // ✅ DEV BYPASS (explicit opt-in, never works in production)
-    if (
-        process.env.NODE_ENV !== "production" &&
-        process.env.ADMIN_DEV_BYPASS === "true"
-    ) {
-        return next();
-    }
- 
-    const allow = buildAdminAllowlist();
- 
-    // Fail-closed when misconfigured (production only).
-    Iif (allow.size === 0 && process.env.NODE_ENV === "production") {
-        return res.status(403).json({ error: "Admin not configured" });
-    }
- 
-    Eif (!req.isAuthenticated?.()) {
-        return res.status(401).json({ error: "Unauthorized" });
-    }
- 
-    const login = getGithubLogin((req as any).user);
-    if (!login) {
-        return res.status(401).json({ error: "Unauthorized" });
-    }
- 
-    if (allow.size > 0 && !allow.has(login)) {
-        return res.status(403).json({ error: "Forbidden" });
-    }
- 
-    return next();
-};
- 
-export default passport;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/auth/index.html b/coverage/lcov-report/src/auth/index.html deleted file mode 100644 index fb9286f..0000000 --- a/coverage/lcov-report/src/auth/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for src/auth - - - - - - - - - -
-
-

All files src/auth

-
- -
- 0% - Statements - 0/40 -
- - -
- 0% - Branches - 0/36 -
- - -
- 0% - Functions - 0/9 -
- - -
- 0% - Lines - 0/34 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
tokens.ts -
-
0%0/400%0/360%0/90%0/34
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/auth/tokens.ts.html b/coverage/lcov-report/src/auth/tokens.ts.html deleted file mode 100644 index 3cf98b8..0000000 --- a/coverage/lcov-report/src/auth/tokens.ts.html +++ /dev/null @@ -1,494 +0,0 @@ - - - - - - Code coverage report for src/auth/tokens.ts - - - - - - - - - -
-
-

All files / src/auth tokens.ts

-
- -
- 0% - Statements - 0/40 -
- - -
- 0% - Branches - 0/36 -
- - -
- 0% - Functions - 0/9 -
- - -
- 0% - Lines - 0/34 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import crypto from "crypto";
-import type Database from "better-sqlite3";
- 
-export type ApiTokenRecord = {
-    id: string;
-    label: string;
-    scopes: string[];
-    is_active: 0 | 1;
-    expires_at: string | null;
-    created_by_user: string | null;
-    last_used_at: string | null;
-    created_at: string;
-};
- 
-function nowIso(): string {
-    return new Date().toISOString();
-}
- 
-// base64url without padding
-function base64Url(buf: Buffer): string {
-    return buf
-        .toString("base64")
-        .replace(/\+/g, "-")
-        .replace(/\//g, "_")
-        .replace(/=+$/g, "");
-}
- 
-// ⚠️ Pepper must be set in production
-function tokenPepper(): string {
-    const p = process.env.TOKEN_PEPPER;
-    if (!p || !p.trim()) {
-        // Fail closed in production. In dev you can allow missing if you want.
-        if (process.env.NODE_ENV === "production") {
-            throw new Error("TOKEN_PEPPER is not set");
-        }
-        return "dev-pepper";
-    }
-    return p;
-}
- 
-export function hashToken(rawToken: string): string {
-    const pepper = tokenPepper();
-    return crypto.createHash("sha256").update(`${pepper}:${rawToken}`, "utf8").digest("hex");
-}
- 
-export function generateRawToken(): string {
-    // 32 bytes = 256 bits of entropy
-    const rand = base64Url(crypto.randomBytes(32));
-    const prefix = process.env.NODE_ENV === "production" ? "hpl_live_" : "hpl_test_";
-    return `${prefix}${rand}`;
-}
- 
-export function mintApiToken(
-    db: Database.Database,
-    input: {
-        label: string;
-        scopes: string[];
-        expires_at?: string | null;
-        created_by_user?: string | null;
-    }
-): { token: string; id: string } {
-    const id = crypto.randomUUID();
-    const token = generateRawToken();
-    const token_hash = hashToken(token);
- 
-    db.prepare(`
-    INSERT INTO api_tokens (
-      id, label, token_hash, scopes_json, is_active, expires_at, created_by_user, created_at
-    )
-    VALUES (?, ?, ?, ?, 1, ?, ?, ?)
-  `).run(
-        id,
-        input.label,
-        token_hash,
-        JSON.stringify(input.scopes ?? []),
-        input.expires_at ?? null,
-        input.created_by_user ?? null,
-        nowIso()
-    );
- 
-    // Return raw token ONCE (never store raw)
-    return { token, id };
-}
- 
-export function verifyApiToken(
-    db: Database.Database,
-    rawToken: string
-): { token: ApiTokenRecord; scopes: string[] } | null {
-    const token_hash = hashToken(rawToken);
- 
-    const row = db.prepare(`
-    SELECT id, label, scopes_json, is_active, expires_at, created_by_user, last_used_at, created_at
-    FROM api_tokens
-    WHERE token_hash = ?
-    LIMIT 1
-  `).get(token_hash) as any | undefined;
- 
-    if (!row) return null;
-    if (row.is_active !== 1) return null;
- 
-    if (row.expires_at) {
-        const exp = Date.parse(row.expires_at);
-        if (Number.isFinite(exp) && exp <= Date.now()) return null;
-    }
- 
-    const scopes = safeParseJsonArray(row.scopes_json);
- 
-    // Touch last_used_at (cheap audit)
-    db.prepare(`UPDATE api_tokens SET last_used_at = ? WHERE id = ?`).run(nowIso(), row.id);
- 
-    return {
-        token: {
-            id: row.id,
-            label: row.label,
-            scopes,
-            is_active: row.is_active,
-            expires_at: row.expires_at ?? null,
-            created_by_user: row.created_by_user ?? null,
-            last_used_at: row.last_used_at ?? null,
-            created_at: row.created_at,
-        },
-        scopes,
-    };
-}
- 
-function safeParseJsonArray(input: any): string[] {
-    if (!input || typeof input !== "string") return [];
-    try {
-        const v = JSON.parse(input);
-        if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
-        return [];
-    } catch {
-        return [];
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db.ts.html b/coverage/lcov-report/src/db.ts.html deleted file mode 100644 index 6dc2067..0000000 --- a/coverage/lcov-report/src/db.ts.html +++ /dev/null @@ -1,1037 +0,0 @@ - - - - - - Code coverage report for src/db.ts - - - - - - - - - -
-
-

All files / src db.ts

-
- -
- 75.6% - Statements - 31/41 -
- - -
- 31.81% - Branches - 7/22 -
- - -
- 75% - Functions - 6/8 -
- - -
- 77.5% - Lines - 31/40 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -19x -  -  -  -  -19x -  -  -  -  -  -  -19x -  -  -  -19x -19x -  -  -  -19x -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -19x -  -  -19x -  -19x -19x -19x -19x -  -  -  -  -19x -  -  -  -  -  -  -  -  -19x -  -  -  -19x -  -  -  -  -  -  -  -  -19x -19x -19x -19x -  -  -19x -  -  -  -  -  -19x -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -19x -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  - 
// src/db.ts
-import Database from "better-sqlite3";
-import path from "path";
-import { fileURLToPath } from "url";
-import crypto from "crypto";
-import { env } from "./env.js";
-import { migrateLabNotesSchema, LAB_NOTES_SCHEMA_VERSION } from "./db/migrateLabNotes.js";
-import {dedupeLabNotesSlugs} from "./db/migrations/2025-01-dedupe-lab-notes-slugs.js";
-import { migrateApiTokensSchema } from "./db/migrateApiTokens.js";
- 
-export function resolveDbPath(): string {
-    const __filename = fileURLToPath(import.meta.url);
-    const __dirname = path.dirname(__filename);
- 
-    // Default DB file paths (only used when NOT in test, and DB_PATH not provided)
-    const defaultDbFile =
-        env.NODE_ENV === "development"
-            ? path.join(__dirname, "../data/lab.dev.db")
-            : path.join(__dirname, "../data/lab.db");
- 
-    const dbPath =
-        env.NODE_ENV === "test"
-            ? ":memory:"
-            : env.DB_PATH
-                ? path.resolve(env.DB_PATH)
-                : defaultDbFile;
- 
-    // Guardrail: tests must NEVER hit a file DB
-    if (env.NODE_ENV === "test" && dbPath !== ":memory:") {
-        throw new Error(`Refusing to run tests on file DB: ${dbPath}`);
-    }
- 
-    return dbPath;
-}
- 
-export function openDb(dbPath: string) {
-    const verbose = process.env.DB_VERBOSE === "1" ? console.log : undefined;
-    return new Database(dbPath, { verbose });
-}
- 
-export function getLabNotesSchemaVersion(db: Database.Database): number {
-    // Ensure meta table exists for fresh DBs
-    db.exec(`
-    CREATE TABLE IF NOT EXISTS schema_meta (
-      key TEXT PRIMARY KEY,
-      value TEXT NOT NULL
-    );
-  `);
- 
-    const row = db
-        .prepare(`SELECT value FROM schema_meta WHERE key = 'lab_notes_schema_version'`)
-        .get() as { value?: string } | undefined;
- 
-    const n = Number(row?.value ?? 0);
-    return Number.isFinite(n) ? n : 0;
-}
- 
-export function setLabNotesSchemaVersion(db: Database.Database, version: number) {
-    db.exec(`
-    CREATE TABLE IF NOT EXISTS schema_meta (
-      key TEXT PRIMARY KEY,
-      value TEXT NOT NULL
-    );
-  `);
- 
-    db.prepare(`
-    INSERT INTO schema_meta (key, value)
-    VALUES ('lab_notes_schema_version', ?)
-    ON CONFLICT(key) DO UPDATE SET value=excluded.value
-  `).run(String(version));
-}
- 
-export function bootstrapDb(db: Database.Database) {
-    const log = process.env.DB_MIGRATE_VERBOSE === "1" ? console.log : undefined;
- 
-// ✅ Read prevVersion ASAP (everything below can be version-gated)
-    const prevVersion = getLabNotesSchemaVersion(db);
-    // ✅ Single source of truth for schema + views
-    migrateLabNotesSchema(db, log);
-    migrateApiTokensSchema(db, log);
-    Eif (prevVersion < 3) {
-        dedupeLabNotesSlugs(db, log);
-    }
- 
-    // (Optional) Keep tag table here only if some routes still use it directly
-    // Note: migrator also creates it, but leaving this is harmless if you want belt + suspenders.
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS lab_note_tags (
-                                                     note_id TEXT NOT NULL,
-                                                     tag TEXT NOT NULL,
-                                                     UNIQUE(note_id, tag)
-        );
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_note_id ON lab_note_tags(note_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_tag ON lab_note_tags(tag);
-    `);
-    setLabNotesSchemaVersion(db, LAB_NOTES_SCHEMA_VERSION)
-}
- 
-function sha256Hex(input: string): string {
-    return crypto.createHash("sha256").update(input, "utf8").digest("hex");
-}
- 
-/**
- * Seeds a single marker note using the v2 ledger:
- * - lab_notes holds metadata + pointers
- * - lab_note_revisions holds markdown truth
- */
-export function seedMarkerNote(db: Database.Database) {
-    const nowIso = new Date().toISOString();
-    const noteId = "api-marker";
-    const slug = "api-marker-note";
-    const locale = "en";
- 
-    // If already seeded (has current revision), do nothing.
-    const existing = db.prepare(`
-    SELECT current_revision_id AS cur
-    FROM lab_notes
-    WHERE id = ?
-  `).get(noteId) as { cur?: string } | undefined;
- 
-    Iif (existing?.cur) return;
- 
-    // 1) Insert metadata row (idempotent)
-    db.prepare(`
-    INSERT OR IGNORE INTO lab_notes (
-      id,
-      group_id,
-      slug,
-      locale,
- 
-      type,
-      title,
- 
-      category,
-      excerpt,
-      department_id,
-      shadow_density,
-      safer_landing,
-      read_time_minutes,
-      coherence_score,
-      subtitle,
-      summary,
- 
-      tags_json,
-      dept,
- 
-      status,
-      published_at,
- 
-      author,
-      ai_author,
- 
-      source_locale,
-      translation_status,
-      translation_provider,
-      translation_version,
-      source_updated_at,
-      translation_meta_json,
- 
-      content_html,
- 
-      current_revision_id,
-      published_revision_id,
- 
-      created_at,
-      updated_at
-    )
-    VALUES (
-      ?, ?, ?, ?,
-      ?, ?,
-      ?, ?, ?, ?, ?, ?, ?, ?, ?,
-      ?, ?,
-      ?, ?,
-      ?, ?,
-      ?, ?, ?, ?, ?, ?,
-      ?,
-      NULL, NULL,
-      ?, ?
-    )
-  `).run(
-        noteId,
-        noteId,
-        slug,
-        locale,
- 
-        "memo",
-        "API Marker Note",
- 
-        "Debug",
-        "If you can see this in WebStorm, we are looking at the same DB.",
-        "SCMS",
-        0.0,
-        1,
-        1,
-        1.0,
-        null,
-        null,
- 
-        "[]",
-        "SCMS",
- 
-        "published",
-        nowIso.slice(0, 10),
- 
-        "Ada",
-        "Lyric",
- 
-        null,
-        "original",
-        null,
-        1,
-        null,
-        null,
- 
-        null,
-        nowIso,
-        nowIso
-    );
- 
-    // 2) Create revision (markdown truth)
-    const revisionId = crypto.randomUUID();
- 
-    const frontmatter = {
-        id: noteId,
-        slug,
-        type: "memo",
-        title: "API Marker Note",
-        status: "published",
-        published: nowIso.slice(0, 10),
-        locale,
-        dept: "SCMS",
-        department_id: "SCMS",
-        shadow_density: 0,
-        safer_landing: true,
-        tags: [],
-        author: "Ada",
-        ai_author: "Lyric",
-    };
- 
-    const contentMarkdown =
-        `---
-id: "${noteId}"
-slug: "${slug}"
-type: "memo"
-title: "API Marker Note"
-dept: "SCMS"
-published: "${nowIso.slice(0, 10)}"
-status: "published"
-locale: "en"
-tags: []
-summary: "If you can see this in WebStorm, we are looking at the same DB."
-readingTime: 1
-shadow_density: 0
-safer_landing: true
----
- 
-If you can see this in WebStorm, we are looking at the same DB.
-`;
- 
-    const canonical = `${JSON.stringify(frontmatter)}\n---\n${contentMarkdown}`;
-    const contentHash = sha256Hex(canonical);
- 
-    db.prepare(`
-    INSERT INTO lab_note_revisions (
-      id, note_id, revision_num, supersedes_revision_id,
-      frontmatter_json, content_markdown, content_hash,
-      schema_version, source,
-      intent, intent_version,
-      scope_json, side_effects_json, reversible,
-      auth_type, scopes_json,
-      reasoning_json,
-      created_at
-    )
-    VALUES (
-      ?, ?, ?, NULL,
-      ?, ?, ?,
-      ?, ?,
-      ?, ?,
-      ?, ?, ?,
-      ?, ?,
-      NULL,
-      ?
-    )
-  `).run(
-        revisionId,
-        noteId,
-        1,
-        JSON.stringify(frontmatter),
-        contentMarkdown,
-        contentHash,
-        "0.1",
-        "import",
-        "seed_marker_note",
-        "1",
-        JSON.stringify(["db"]),
-        JSON.stringify(["create"]),
-        1,
-        "human_session",
-        JSON.stringify([]),
-        nowIso
-    );
- 
-    // 3) Point note at revision
-    db.prepare(`
-    UPDATE lab_notes
-    SET current_revision_id = ?,
-        published_revision_id = COALESCE(published_revision_id, ?),
-        updated_at = ?
-    WHERE id = ?
-  `).run(revisionId, revisionId, nowIso, noteId);
-}
- 
-export function isDbEmpty(db: Database.Database): boolean {
-    const row = db.prepare(`SELECT COUNT(*) as count FROM lab_notes`).get() as { count: number };
-    return row.count === 0;
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db/index.html b/coverage/lcov-report/src/db/index.html deleted file mode 100644 index 0fa78db..0000000 --- a/coverage/lcov-report/src/db/index.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - Code coverage report for src/db - - - - - - - - - -
-
-

All files src/db

-
- -
- 80.88% - Statements - 55/68 -
- - -
- 68.57% - Branches - 24/35 -
- - -
- 66.66% - Functions - 8/12 -
- - -
- 87.09% - Lines - 54/62 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
migrateApiTokens.ts -
-
90%9/1050%2/4100%3/3100%9/9
migrateLabNotes.ts -
-
79.31%46/5870.96%22/3155.55%5/984.9%45/53
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db/migrateApiTokens.ts.html b/coverage/lcov-report/src/db/migrateApiTokens.ts.html deleted file mode 100644 index b265241..0000000 --- a/coverage/lcov-report/src/db/migrateApiTokens.ts.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - Code coverage report for src/db/migrateApiTokens.ts - - - - - - - - - -
-
-

All files / src/db migrateApiTokens.ts

-
- -
- 90% - Statements - 9/10 -
- - -
- 50% - Branches - 2/4 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60  -  -  -7x -  -  -19x -  -  -19x -  -  -  -19x -  -  -  -  -  -  -  -  -19x -  -  -  -  -  -  -19x -19x -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  - 
// src/db/migrateApiTokens.ts
-import Database from "better-sqlite3";
- 
-export const API_TOKENS_SCHEMA_VERSION = 1;
- 
-function getVersion(db: Database.Database): number {
-    const row = db
-        .prepare(`SELECT value FROM schema_meta WHERE key='api_tokens_schema_version'`)
-        .get() as { value?: string } | undefined;
-    return row?.value ? Number(row.value) : 0;
-}
- 
-function setVersion(db: Database.Database, version: number) {
-    db.prepare(`
-    INSERT INTO schema_meta (key, value)
-    VALUES ('api_tokens_schema_version', ?)
-    ON CONFLICT(key) DO UPDATE SET value = excluded.value
-  `).run(String(version));
-}
- 
-export function migrateApiTokensSchema(db: Database.Database, log: ((...data: any[]) => void) | undefined) {
-    // Ensure schema_meta exists
-    db.exec(`
-    CREATE TABLE IF NOT EXISTS schema_meta (
-      key TEXT PRIMARY KEY,
-      value TEXT NOT NULL
-    );
-  `);
- 
-    const prev = getVersion(db);
-    Iif (prev >= API_TOKENS_SCHEMA_VERSION) return;
- 
-    db.exec(`
-    PRAGMA foreign_keys = ON;
- 
-    CREATE TABLE IF NOT EXISTS api_tokens (
-      id TEXT PRIMARY KEY,
- 
-      label TEXT NOT NULL,
-      token_hash TEXT NOT NULL UNIQUE,
- 
-      scopes_json TEXT NOT NULL DEFAULT '[]',
- 
-      is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0,1)),
-      expires_at TEXT NULL,
- 
-      created_by_user TEXT NULL,
- 
-      last_used_at TEXT NULL,
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_api_tokens_active ON api_tokens(is_active);
-    CREATE INDEX IF NOT EXISTS idx_api_tokens_expires ON api_tokens(expires_at);
-    CREATE INDEX IF NOT EXISTS idx_api_tokens_last_used ON api_tokens(last_used_at);
-  `);
- 
-    setVersion(db, API_TOKENS_SCHEMA_VERSION);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db/migrateLabNotes.ts.html b/coverage/lcov-report/src/db/migrateLabNotes.ts.html deleted file mode 100644 index 743827d..0000000 --- a/coverage/lcov-report/src/db/migrateLabNotes.ts.html +++ /dev/null @@ -1,2165 +0,0 @@ - - - - - - Code coverage report for src/db/migrateLabNotes.ts - - - - - - - - - -
-
-

All files / src/db migrateLabNotes.ts

-
- -
- 79.31% - Statements - 46/58 -
- - -
- 70.96% - Branches - 22/31 -
- - -
- 55.55% - Functions - 5/9 -
- - -
- 84.9% - Lines - 45/53 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 -492 -493 -494 -495 -496 -497 -498 -499 -500 -501 -502 -503 -504 -505 -506 -507 -508 -509 -510 -511 -512 -513 -514 -515 -516 -517 -518 -519 -520 -521 -522 -523 -524 -525 -526 -527 -528 -529 -530 -531 -532 -533 -534 -535 -536 -537 -538 -539 -540 -541 -542 -543 -544 -545 -546 -547 -548 -549 -550 -551 -552 -553 -554 -555 -556 -557 -558 -559 -560 -561 -562 -563 -564 -565 -566 -567 -568 -569 -570 -571 -572 -573 -574 -575 -576 -577 -578 -579 -580 -581 -582 -583 -584 -585 -586 -587 -588 -589 -590 -591 -592 -593 -594 -595 -596 -597 -598 -599 -600 -601 -602 -603 -604 -605 -606 -607 -608 -609 -610 -611 -612 -613 -614 -615 -616 -617 -618 -619 -620 -621 -622 -623 -624 -625 -626 -627 -628 -629 -630 -631 -632 -633 -634 -635 -636 -637 -638 -639 -640 -641 -642 -643 -644 -645 -646 -647 -648 -649 -650 -651 -652 -653 -654 -655 -656 -657 -658 -659 -660 -661 -662 -663 -664 -665 -666 -667 -668 -669 -670 -671 -672 -673 -674 -675 -676 -677 -678 -679 -680 -681 -682 -683 -684 -685 -686 -687 -688 -689 -690 -691 -692 -693  -  -  -  -8x -  -  -25x -  -  -  -25x -  -23x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -  -  -  -25x -  -  -  -  -  -  -  -25x -  -  -  -  -25x -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -87x -  -  -25x -25x -775x -713x -713x -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -  -25x -23x -  -23x -23x -  -  -  -  -  -  -  -23x -1x -  -  -  -1x -  -  -1x -  -  -  -23x -23x -  -  -  -  -25x -23x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -23x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -25x -  -25x -  -  -  -  -  -25x -  -  -  -  -  -  -  -2x -  -  -  -2x -  -2x -  -  -  -  -25x -  -  -  -  -  -  -  -  -  -  -  -  - 
// src/db/migrateLabNotes.ts
-import Database from "better-sqlite3";
-import crypto from "crypto";
- 
-export const LAB_NOTES_SCHEMA_VERSION = 8;
- 
-function setLabNotesSchemaVersion(db: Database.Database, version: number) {
-    const cur = db
-        .prepare(`SELECT value FROM schema_meta WHERE key='lab_notes_schema_version'`)
-        .get() as { value?: string } | undefined;
- 
-    if (cur?.value === String(version)) return;
- 
-    db.prepare(`
-        INSERT INTO schema_meta (key, value)
-        VALUES ('lab_notes_schema_version', ?)
-        ON CONFLICT(key) DO UPDATE SET value = excluded.value
-    `).run(String(version));
-}
- 
- 
-type ColDef = { name: string; ddl: string };
-type MigrationLogFn = (msg: string) => void;
- 
-export type MigrationResult = {
-    addedColumns: string[];
-    createdFreshTable: boolean;
-    ranV2DataMigration: boolean;
-};
- 
-function sha256Hex(input: string): string {
-    return crypto.createHash("sha256").update(input, "utf8").digest("hex");
-}
- 
-function nowIso(): string {
-    return new Date().toISOString();
-}
- 
-function getLabNotesSchemaVersion(db: Database.Database): number {
-    const row = db
-        .prepare(`SELECT value FROM schema_meta WHERE key='lab_notes_schema_version'`)
-        .get() as { value?: string } | undefined;
- 
-    return row?.value ? Number(row.value) : 0;
-}
- 
- 
-/**
- * v2 model: lab_notes (identity + pointers) + lab_note_revisions (append-only truth)
- *
- * This migrator is intentionally idempotent:
- * - CREATE TABLE IF NOT EXISTS
- * - CREATE INDEX IF NOT EXISTS
- * - ALTER TABLE ADD COLUMN only when missing
- *
- * For installs that had v1's wide `lab_notes` table, we:
- * - keep the existing columns (non-destructive)
- * - add v2 pointer columns
- * - create v2 tables
- * - perform a one-time migration to create revision_num=1 records and set pointers
- */
- 
-export function migrateLabNotesSchema(
- 
-    db: Database.Database,
-    log?: MigrationLogFn
-): MigrationResult {
-    // Did the table exist before?
-    const hadLabNotesTable = !!db
-        .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='lab_notes'`)
-        .get();
- 
-    // Ensure schema_meta exists early (we read it before doing versioned work)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS schema_meta (
-                                                   key TEXT PRIMARY KEY,
-                                                   value TEXT NOT NULL
-        );
-    `);
- 
-    // Ensure lab_notes exists (seed)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS lab_notes (
-                                                 id TEXT PRIMARY KEY
-        );
-    `);
-    const prevVersion = getLabNotesSchemaVersion(db);
-    // --- v1 compatibility columns (existing wide model) ---
-    // Keep your original columns so older code / existing rows remain valid.
-    // We also add the v2 pointer columns and some core identity fields needed for 1.0.
-    const LAB_NOTES_REQUIRED_COLS: ColDef[] = [
-        // Core identity (v1 already had these; keep enforcing)
-        { name: "group_id", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "slug", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "locale", ddl: "TEXT NOT NULL DEFAULT 'en'" },
- 
-        // Basics
-        { name: "type", ddl: "TEXT NOT NULL DEFAULT 'labnote'" },
-        { name: "title", ddl: "TEXT NOT NULL DEFAULT ''" },
- 
-        // Kept attributes
-        { name: "category", ddl: "TEXT" },
-        { name: "excerpt", ddl: "TEXT" },
-        { name: "department_id", ddl: "TEXT" },
-        { name: "shadow_density", ddl: "REAL" },
-        { name: "safer_landing", ddl: "INTEGER" },
-        { name: "read_time_minutes", ddl: "INTEGER" },
- 
-        { name: "coherence_score", ddl: "REAL" },
-        { name: "subtitle", ddl: "TEXT" },
-        { name: "summary", ddl: "TEXT" },
- 
-        { name: "tags_json", ddl: "TEXT" },
-        { name: "dept", ddl: "TEXT" },
- 
-        // Publishing
-        { name: "status", ddl: "TEXT NOT NULL DEFAULT 'draft'" },
-        { name: "published_at", ddl: "TEXT" },
- 
-        // Authors
-        { name: "author", ddl: "TEXT" },
-        { name: "ai_author", ddl: "TEXT" },
- 
-        // Translation metadata
-        { name: "source_locale", ddl: "TEXT" },
-        { name: "translation_status", ddl: "TEXT NOT NULL DEFAULT 'original'" },
-        { name: "translation_provider", ddl: "TEXT" },
-        { name: "translation_version", ddl: "INTEGER NOT NULL DEFAULT 1" },
-        { name: "source_updated_at", ddl: "TEXT" },
-        { name: "translation_meta_json", ddl: "TEXT" },
- 
-        // Content artifacts (no legacy markdown column anymore)
-        { name: "content_html", ddl: "TEXT" },
- 
-        // Timestamps
-        { name: "created_at", ddl: "TEXT NOT NULL DEFAULT ''" },
-        { name: "updated_at", ddl: "TEXT NOT NULL DEFAULT ''" },
- 
-        // ---- v2 pointer fields (new) ----
-        { name: "current_revision_id", ddl: "TEXT" },
-        { name: "published_revision_id", ddl: "TEXT" },
-    ];
- 
-    const existingCols = new Set<string>(
-        db.prepare(`PRAGMA table_info(lab_notes)`).all().map((r: any) => r.name)
-    );
- 
-    const addedColumns: string[] = [];
-    for (const col of LAB_NOTES_REQUIRED_COLS) {
-        if (!existingCols.has(col.name)) {
-            db.exec(`ALTER TABLE lab_notes ADD COLUMN ${col.name} ${col.ddl};`);
-            addedColumns.push(col.name);
-        }
-    }
- 
-    // Backfills for legacy rows
-    db.exec(`
-        UPDATE lab_notes
-        SET group_id = id
-        WHERE group_id IS NULL OR group_id = '';
- 
-        UPDATE lab_notes SET slug = id WHERE slug IS NULL OR slug = '';
-        UPDATE lab_notes SET title = slug WHERE title IS NULL OR title = '';
- 
-        UPDATE lab_notes
-        SET created_at = COALESCE(created_at, updated_at, datetime('now'))
-        WHERE created_at IS NULL OR created_at = '';
- 
-        UPDATE lab_notes
-        SET updated_at = COALESCE(updated_at, created_at, datetime('now'))
-        WHERE updated_at IS NULL OR updated_at = '';
-    `);
- 
-    // Indexes (idempotent)
-    db.exec(`
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_locale ON lab_notes(locale);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_status ON lab_notes(status);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_published_at ON lab_notes(published_at);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_group_id ON lab_notes(group_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_department_id ON lab_notes(department_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_notes_slug_locale ON lab_notes(slug, locale);
-    `);
- 
-    // Optional tag table (normalized tags)
-    db.exec(`
-        CREATE TABLE IF NOT EXISTS lab_note_tags (
-                                                     note_id TEXT NOT NULL,
-                                                     tag TEXT NOT NULL,
-                                                     UNIQUE(note_id, tag)
-        );
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_note_id ON lab_note_tags(note_id);
-        CREATE INDEX IF NOT EXISTS idx_lab_note_tags_tag ON lab_note_tags(tag);
-    `);
- 
-    // ---- v2 tables: append-only revisions + proposals + events ----
-    db.exec(`
-    PRAGMA foreign_keys = ON;
- 
-    CREATE TABLE IF NOT EXISTS lab_note_revisions (
-      id TEXT PRIMARY KEY,
-      note_id TEXT NOT NULL,
- 
-      revision_num INTEGER NOT NULL,
-      supersedes_revision_id TEXT NULL,
- 
-      frontmatter_json TEXT NOT NULL,
-      content_markdown TEXT NOT NULL,
-      content_hash TEXT NOT NULL,
- 
-      schema_version TEXT NOT NULL,
-      source TEXT NOT NULL CHECK (source IN ('cli','web','api','import')),
- 
-      intent TEXT NOT NULL,
-      intent_version TEXT NOT NULL DEFAULT '1',
- 
-      scope_json TEXT NOT NULL DEFAULT '[]',
-      side_effects_json TEXT NOT NULL DEFAULT '[]',
-      reversible INTEGER NOT NULL DEFAULT 1 CHECK (reversible IN (0,1)),
- 
-      auth_type TEXT NOT NULL CHECK (auth_type IN ('human_session','lab_token')),
-      scopes_json TEXT NOT NULL DEFAULT '[]',
- 
-      reasoning_json TEXT NULL,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      UNIQUE (note_id, revision_num),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE CASCADE,
-      FOREIGN KEY (supersedes_revision_id) REFERENCES lab_note_revisions(id)
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_revisions_note ON lab_note_revisions(note_id);
-    CREATE INDEX IF NOT EXISTS idx_revisions_intent ON lab_note_revisions(intent);
-    CREATE INDEX IF NOT EXISTS idx_revisions_hash ON lab_note_revisions(content_hash);
- 
-    CREATE TABLE IF NOT EXISTS lab_note_proposals (
-      id TEXT PRIMARY KEY,
-      note_id TEXT NOT NULL,
- 
-      base_revision_id TEXT NOT NULL,
-      proposed_revision_id TEXT NOT NULL,
- 
-      status TEXT NOT NULL DEFAULT 'pending'
-        CHECK (status IN ('pending','accepted','rejected','withdrawn')),
- 
-      created_by TEXT NOT NULL,
-      created_by_type TEXT NOT NULL CHECK (created_by_type IN ('human','ai','system')),
- 
-      reviewed_by TEXT NULL,
-      reviewed_at TEXT NULL,
-      review_comment TEXT NULL,
- 
-      diff_patch TEXT NULL,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE CASCADE,
-      FOREIGN KEY (base_revision_id) REFERENCES lab_note_revisions(id),
-      FOREIGN KEY (proposed_revision_id) REFERENCES lab_note_revisions(id)
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_proposals_note ON lab_note_proposals(note_id);
-    CREATE INDEX IF NOT EXISTS idx_proposals_status ON lab_note_proposals(status);
- 
-    CREATE TABLE IF NOT EXISTS lab_events (
-      id TEXT PRIMARY KEY,
- 
-      event_type TEXT NOT NULL,
-      note_id TEXT NULL,
-      revision_id TEXT NULL,
-      proposal_id TEXT NULL,
- 
-      intent TEXT NULL,
-      intent_version TEXT NULL,
- 
-      actor_type TEXT NOT NULL CHECK (actor_type IN ('human','ai','system')),
-      actor_id TEXT NOT NULL,
- 
-      auth_type TEXT NULL CHECK (auth_type IN ('human_session','lab_token')),
-      scopes_json TEXT NULL,
- 
-      payload_json TEXT NOT NULL DEFAULT '{}',
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
- 
-      FOREIGN KEY (note_id) REFERENCES lab_notes(id) ON DELETE SET NULL,
-      FOREIGN KEY (revision_id) REFERENCES lab_note_revisions(id) ON DELETE SET NULL,
-      FOREIGN KEY (proposal_id) REFERENCES lab_note_proposals(id) ON DELETE SET NULL
-    );
- 
-    CREATE INDEX IF NOT EXISTS idx_events_type ON lab_events(event_type);
-    CREATE INDEX IF NOT EXISTS idx_events_note ON lab_events(note_id);
-    CREATE INDEX IF NOT EXISTS idx_events_created_at ON lab_events(created_at);
-  `);
- 
-    let ranV2DataMigration = false;
- 
-// ---- v2 data migration (one-time) ----
-    if (prevVersion < 2) {
-        let seededCount = 0;
- 
-        const tx = db.transaction(() => {
-            const rows = db.prepare(`
-      SELECT id, slug, locale, type, title, subtitle, summary, tags_json, dept,
-             status, published_at, author, ai_author, created_at, updated_at
-      FROM lab_notes
-    `).all() as any[];
- 
-            // ...prepare statements...
- 
-            for (const n of rows) {
-                const pointer = db
-                    .prepare(`SELECT current_revision_id FROM lab_notes WHERE id = ?`)
-                    .get(n.id) as { current_revision_id?: string } | undefined;
- 
-                Iif (pointer?.current_revision_id) continue;
- 
-                // ...insert revision, update pointers, insert event...
-                seededCount++;
-            }
-        });
- 
-        tx();
-        ranV2DataMigration = seededCount > 0;
-    }
- 
- 
-    // ---- drop legacy markdown column (table rebuild) ----
-    if (prevVersion < 7) {
-        db.exec(`
-      PRAGMA foreign_keys = OFF;
- 
-      BEGIN;
- 
-      DROP VIEW IF EXISTS v_lab_notes;
-      DROP VIEW IF EXISTS v_lab_notes_current;
- 
-      DROP TABLE IF EXISTS lab_notes_new;
- 
-      CREATE TABLE lab_notes_new (
-        id TEXT PRIMARY KEY,
- 
-        group_id TEXT NOT NULL,
-        slug TEXT NOT NULL,
-        locale TEXT NOT NULL,
- 
-        type TEXT NOT NULL,
-        title TEXT NOT NULL,
- 
-        category TEXT,
-        excerpt TEXT,
-        department_id TEXT,
-        shadow_density REAL,
-        safer_landing INTEGER,
-        read_time_minutes INTEGER,
- 
-        coherence_score REAL,
-        subtitle TEXT,
-        summary TEXT,
- 
-        tags_json TEXT,
-        dept TEXT,
- 
-        status TEXT NOT NULL,
-        published_at TEXT,
- 
-        author TEXT,
-        ai_author TEXT,
- 
-        source_locale TEXT,
-        translation_status TEXT NOT NULL,
-        translation_provider TEXT,
-        translation_version INTEGER NOT NULL,
-        source_updated_at TEXT,
-        translation_meta_json TEXT,
- 
-        content_html TEXT,
- 
-        current_revision_id TEXT,
-        published_revision_id TEXT,
- 
-        created_at TEXT NOT NULL,
-        updated_at TEXT NOT NULL
-      );
- 
-      INSERT INTO lab_notes_new (
-        id, group_id, slug, locale,
-        type, title,
-        category, excerpt, department_id,
-        shadow_density, safer_landing, read_time_minutes,
-        coherence_score, subtitle, summary,
-        tags_json, dept,
-        status, published_at,
-        author, ai_author,
-        source_locale, translation_status, translation_provider,
-        translation_version, source_updated_at, translation_meta_json,
-        content_html,
-        current_revision_id, published_revision_id,
-        created_at, updated_at
-      )
-      SELECT
-        id, group_id, slug, locale,
-        type, title,
-        category, excerpt, department_id,
-        shadow_density, safer_landing, read_time_minutes,
-        coherence_score, subtitle, summary,
-        tags_json, dept,
-        status, published_at,
-        author, ai_author,
-        source_locale, translation_status, translation_provider,
-        translation_version, source_updated_at, translation_meta_json,
-        content_html,
-        current_revision_id, published_revision_id,
-        created_at, updated_at
-      FROM lab_notes;
- 
-      DROP TABLE lab_notes;
-      ALTER TABLE lab_notes_new RENAME TO lab_notes;
- 
-      COMMIT;
- 
-      PRAGMA foreign_keys = ON;
-    `);
-    }
- 
-    // ---- v8: add real defaults + enforce unique (slug, locale) ----
-    if (prevVersion < 8) {
-        db.exec(`
-    PRAGMA foreign_keys = OFF;
-    BEGIN;
- 
-    DROP VIEW IF EXISTS v_lab_notes;
-    DROP VIEW IF EXISTS v_lab_notes_current;
- 
-    -- 1) DEDUPE by (slug, locale), keep most recently updated
-    DELETE FROM lab_notes
-    WHERE id IN (
-      SELECT id FROM (
-        SELECT
-          id,
-          ROW_NUMBER() OVER (
-            PARTITION BY slug, locale
-            ORDER BY updated_at DESC, created_at DESC
-          ) AS rn
-        FROM lab_notes
-      )
-      WHERE rn > 1
-    );
- 
-    -- 2) Rebuild lab_notes with DEFAULTs
-    DROP TABLE IF EXISTS lab_notes_new;
- 
-    CREATE TABLE lab_notes_new (
-      id TEXT PRIMARY KEY,
- 
-      group_id TEXT NOT NULL DEFAULT 'core',
-      slug TEXT NOT NULL,
-      locale TEXT NOT NULL DEFAULT 'en',
- 
-      type TEXT NOT NULL DEFAULT 'labnote',
-      title TEXT NOT NULL DEFAULT '',
- 
-      category TEXT,
-      excerpt TEXT,
-      department_id TEXT DEFAULT 'SCMS',
-      shadow_density REAL DEFAULT 0,
-      safer_landing INTEGER DEFAULT 0,
-      read_time_minutes INTEGER DEFAULT 5,
- 
-      coherence_score REAL DEFAULT 1.0,
-      subtitle TEXT,
-      summary TEXT,
- 
-      tags_json TEXT,
-      dept TEXT,
- 
-      status TEXT NOT NULL DEFAULT 'draft',
-      published_at TEXT,
- 
-      author TEXT,
-      ai_author TEXT,
- 
-      source_locale TEXT,
-      translation_status TEXT NOT NULL DEFAULT 'original',
-      translation_provider TEXT,
-      translation_version INTEGER NOT NULL DEFAULT 1,
-      source_updated_at TEXT,
-      translation_meta_json TEXT,
- 
-      content_html TEXT,
- 
-      current_revision_id TEXT,
-      published_revision_id TEXT,
- 
-      created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
-      updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
-    );
- 
-    INSERT INTO lab_notes_new (
-      id, group_id, slug, locale,
-      type, title,
-      category, excerpt, department_id,
-      shadow_density, safer_landing, read_time_minutes,
-      coherence_score, subtitle, summary,
-      tags_json, dept,
-      status, published_at,
-      author, ai_author,
-      source_locale, translation_status, translation_provider,
-      translation_version, source_updated_at, translation_meta_json,
-      content_html,
-      current_revision_id, published_revision_id,
-      created_at, updated_at
-    )
-    SELECT
-      id,
-      COALESCE(NULLIF(group_id,''), id, 'core'),
-      slug,
-      LOWER(COALESCE(NULLIF(locale,''), 'en')),
- 
-      COALESCE(NULLIF(type,''), 'labnote'),
-      COALESCE(NULLIF(title,''), ''),
- 
-      category,
-      excerpt,
-      COALESCE(NULLIF(department_id,''), 'SCMS'),
-      COALESCE(shadow_density, 0),
-      COALESCE(safer_landing, 0),
-      COALESCE(read_time_minutes, 5),
- 
-      COALESCE(coherence_score, 1.0),
-      subtitle,
-      summary,
- 
-      tags_json,
-      dept,
- 
-      COALESCE(NULLIF(status,''), CASE WHEN published_at IS NOT NULL THEN 'published' ELSE 'draft' END),
-      published_at,
- 
-      author,
-      ai_author,
- 
-      source_locale,
-      COALESCE(NULLIF(translation_status,''), 'original'),
-      translation_provider,
-      COALESCE(translation_version, 1),
-      source_updated_at,
-      translation_meta_json,
- 
-      content_html,
- 
-      current_revision_id,
-      published_revision_id,
- 
-      COALESCE(NULLIF(created_at,''), strftime('%Y-%m-%dT%H:%M:%fZ','now')),
-      COALESCE(NULLIF(updated_at,''), COALESCE(NULLIF(created_at,''), strftime('%Y-%m-%dT%H:%M:%fZ','now')))
-    FROM lab_notes;
- 
-    DROP TABLE lab_notes;
-    ALTER TABLE lab_notes_new RENAME TO lab_notes;
- 
-    -- 3) Now enforce uniqueness correctly for localized slugs
-    CREATE UNIQUE INDEX IF NOT EXISTS uq_lab_notes_slug_locale ON lab_notes(slug, locale);
- 
-    COMMIT;
-    PRAGMA foreign_keys = ON;
-  `);
-    }
- 
- 
-    // ✅ Views created LAST (after any rebuild)
-    db.exec(`
-    DROP VIEW IF EXISTS v_lab_notes;
- 
-    CREATE VIEW v_lab_notes AS
-    SELECT
-      id,
-      group_id,
-      slug,
-      locale,
-      type,
-      title,
- 
-      category,
-      excerpt,
-      department_id,
-      shadow_density,
-      safer_landing,
-      read_time_minutes,
-      coherence_score,
-      subtitle,
-      summary,
-      tags_json,
-      dept,
- 
-      status,
-      published_at,
-      author,
-      ai_author,
- 
-      source_locale,
-      translation_status,
-      translation_provider,
-      translation_version,
-      source_updated_at,
-      translation_meta_json,
- 
-      content_html,
- 
-      current_revision_id,
-      published_revision_id,
- 
-      created_at,
-      updated_at
-    FROM lab_notes;
- 
-    DROP VIEW IF EXISTS v_lab_notes_current;
- 
-    CREATE VIEW v_lab_notes_current AS
-    SELECT
-      n.id AS note_id,
-      n.slug,
-      n.locale,
-      n.title,
-      n.status,
-      n.author,
-      n.ai_author,
-      n.current_revision_id,
-      n.published_revision_id,
-      r.revision_num,
-      r.schema_version,
-      r.source,
-      r.intent,
-      r.intent_version,
-      r.scope_json,
-      r.side_effects_json,
-      r.reversible,
-      r.auth_type,
-      r.scopes_json,
-      r.frontmatter_json,
-      r.content_markdown,
-      r.content_hash,
-      r.created_at AS revision_created_at,
-      n.created_at,
-      n.updated_at
-    FROM lab_notes n
-    LEFT JOIN lab_note_revisions r
-      ON r.id = n.current_revision_id;
-  `);
- 
-    // Set DB version last (after successful schema + data migration)
-    setLabNotesSchemaVersion(db, LAB_NOTES_SCHEMA_VERSION);
- 
-    const result: MigrationResult = {
-        addedColumns,
-        createdFreshTable: !hadLabNotesTable,
-        ranV2DataMigration,
-    };
- 
-    if (
-        log &&
-        (result.createdFreshTable ||
-            result.addedColumns.length > 0 ||
-            prevVersion !== LAB_NOTES_SCHEMA_VERSION ||
-            ranV2DataMigration)
-    ) {
-        const colsPart =
-            result.addedColumns.length > 0
-                ? `added ${result.addedColumns.length} column(s): ${result.addedColumns.join(", ")}`
-                : "no column changes";
- 
-        const migPart = ranV2DataMigration ? "ran v2 revision seeding" : "no v2 data migration";
- 
-        log(
-            `[db] lab_notes migration: v${prevVersion} → v${LAB_NOTES_SCHEMA_VERSION}; ${colsPart}; ${migPart}`
-        );
-    }
- 
-    return result;
-}
- 
-function safeParseJsonArray(input: any): string[] {
-    if (!input || typeof input !== "string") return [];
-    try {
-        const v = JSON.parse(input);
-        if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
-        return [];
-    } catch {
-        return [];
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db/migrations/2025-01-dedupe-lab-notes-slugs.ts.html b/coverage/lcov-report/src/db/migrations/2025-01-dedupe-lab-notes-slugs.ts.html deleted file mode 100644 index 0f8a6b7..0000000 --- a/coverage/lcov-report/src/db/migrations/2025-01-dedupe-lab-notes-slugs.ts.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - Code coverage report for src/db/migrations/2025-01-dedupe-lab-notes-slugs.ts - - - - - - - - - -
-
-

All files / src/db/migrations 2025-01-dedupe-lab-notes-slugs.ts

-
- -
- 75% - Statements - 3/4 -
- - -
- 66.66% - Branches - 2/3 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28  -  -  -  -19x -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -  - 
// src/db/migrations/2025-01-dedupe-lab-notes-slugs.ts
-import type Database from "better-sqlite3";
- 
-export function dedupeLabNotesSlugs(db: Database.Database, log = console.log) {
-    Iif (process.env.NODE_ENV !== "test") console.log("🧹 Dedupe...");
- 
-    db.exec(`
-    DELETE FROM lab_notes
-    WHERE id IN (
-      SELECT id FROM (
-        SELECT
-          id,
-          ROW_NUMBER() OVER (
-            PARTITION BY slug
-            ORDER BY updated_at DESC, created_at DESC
-          ) AS rn
-        FROM lab_notes
-      )
-      WHERE rn > 1
-    );
-  `);
- 
-    db.exec(`
-    CREATE UNIQUE INDEX IF NOT EXISTS idx_lab_notes_slug
-    ON lab_notes(slug);
-  `);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/db/migrations/index.html b/coverage/lcov-report/src/db/migrations/index.html deleted file mode 100644 index b3c3795..0000000 --- a/coverage/lcov-report/src/db/migrations/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for src/db/migrations - - - - - - - - - -
-
-

All files src/db/migrations

-
- -
- 75% - Statements - 3/4 -
- - -
- 66.66% - Branches - 2/3 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
2025-01-dedupe-lab-notes-slugs.ts -
-
75%3/466.66%2/3100%1/1100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/env.ts.html b/coverage/lcov-report/src/env.ts.html deleted file mode 100644 index 92cde68..0000000 --- a/coverage/lcov-report/src/env.ts.html +++ /dev/null @@ -1,425 +0,0 @@ - - - - - - Code coverage report for src/env.ts - - - - - - - - - -
-
-

All files / src env.ts

-
- -
- 48.38% - Statements - 15/31 -
- - -
- 44.18% - Branches - 19/43 -
- - -
- 75% - Functions - 3/4 -
- - -
- 44.82% - Lines - 13/29 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -7x -  -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -7x -7x -  -  -  -  -7x -  -  -  -  -  -  -  -  -  -7x -7x -  -7x -  -7x -  -  -  -  -  -7x -7x -  -  -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -7x - 
// src/env.ts
-import dotenv from "dotenv";
-import fs from "node:fs";
-import path from "node:path";
- 
-/* ===========================================================
-   🌱 HUMAN PATTERN LAB — ENV LOADER + VALIDATOR
-   =========================================================== */
- 
-type NodeEnv = "development" | "test" | "production";
- 
-type Env = {
-    NODE_ENV: NodeEnv;
-    PORT: number;
-    DB_PATH: string;
-    SESSION_SECRET?: string;
- 
-    UI_BASE_URL?: string;
- 
-    // optional OAuth vars (used by auth.ts via process.env)
-    OAUTH_GITHUB_CLIENT_ID?: string;
-    OAUTH_GITHUB_CLIENT_SECRET?: string;
-    OAUTH_GITHUB_CALLBACK_URL?: string;
- 
-    ALLOWED_GITHUB_USERNAME?: string;
-    DB_SEED?: string;
-    DB_MIGRATE_VERBOSE?: string;
-};
- 
-class EnvError extends Error {
-    constructor(message: string) {
-        super(message);
-        this.name = "EnvError";
-    }
-}
- 
-// ── Load dotenv early ───────────────────────────────────────
-const rawNodeEnv = process.env.NODE_ENV ?? "development";
- 
-// 🛑 Prevent double-loading (PM2 + import graph safety)
-Iif (!process.env.__HPL_ENV_LOADED && rawNodeEnv !== "test") {
-    // Load base .env
-    dotenv.config({ path: path.join(process.cwd(), ".env") });
-    console.log("ENV CHECK:", {
-        NODE_ENV: process.env.NODE_ENV ?? null,
-        cwd: process.cwd(),
-        hasSessionSecret: Boolean(process.env.SESSION_SECRET),
-    });
-    // Load env-specific file (override base)
-    const envFile = `.env.${rawNodeEnv}`;
-    const envPath = path.join(process.cwd(), envFile);
-    if (fs.existsSync(envPath)) {
-        dotenv.config({ path: envFile, override: true });
-    }
- 
-    // 🔒 Mark as loaded
-    process.env.__HPL_ENV_LOADED = "true";
-}
- 
-// ── Normalizers ────────────────────────────────────────────
-function normalizeNodeEnv(value: string): NodeEnv {
-    const v = value === "dev" ? "development" : value === "prod" ? "production" : value;
-    Eif (v === "development" || v === "test" || v === "production") return v;
-    throw new EnvError('Invalid NODE_ENV="' + value + '". Use "development", "test", or "production".');
-}
- 
-function parsePort(value: string | undefined, fallback: number): number {
-    Eif (value == null || value.trim() === "") return fallback;
-    const n = Number(value);
-    if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0 || n > 65535) {
-        throw new EnvError('Invalid PORT="' + value + '". Must be an integer from 1-65535.');
-    }
-    return n;
-}
- 
-// ── Validator ──────────────────────────────────────────────
-function validateEnv(input: NodeJS.ProcessEnv): Env {
-    const NODE_ENV = normalizeNodeEnv(input.NODE_ENV ?? "development");
-    const PORT = parsePort(input.PORT, 8001);
- 
-    const DB_PATH = (input.DB_PATH ?? "").trim();
- 
-    Iif (NODE_ENV === "test" && DB_PATH !== "" && DB_PATH !== ":memory:") {
-        throw new EnvError(
-            'Refusing NODE_ENV=test with DB_PATH="' + DB_PATH + '". Use DB_PATH=":memory:" or unset DB_PATH.'
-        );
-    }
- 
-    const SESSION_SECRET = input.SESSION_SECRET?.trim();
-    Iif (NODE_ENV === "production" && (!SESSION_SECRET || SESSION_SECRET.length < 16)) {
-        console.warn("⚠️ SESSION_SECRET is missing/short in production. Set a strong secret.");
-    }
- 
-    return {
-        NODE_ENV,
-        PORT,
-        DB_PATH,
-        SESSION_SECRET,
- 
-        UI_BASE_URL: input.UI_BASE_URL?.trim(),
- 
-        OAUTH_GITHUB_CLIENT_ID: input.OAUTH_GITHUB_CLIENT_ID?.trim(),
-        OAUTH_GITHUB_CLIENT_SECRET: input.OAUTH_GITHUB_CLIENT_SECRET?.trim(),
-        OAUTH_GITHUB_CALLBACK_URL: input.OAUTH_GITHUB_CALLBACK_URL?.trim(),
- 
-        ALLOWED_GITHUB_USERNAME: input.ALLOWED_GITHUB_USERNAME?.trim(),
-        DB_SEED: input.DB_SEED?.trim(),
-        DB_MIGRATE_VERBOSE: input.DB_MIGRATE_VERBOSE?.trim(),
-    };
-}
- 
-export const env = validateEnv(process.env);
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/index.html b/coverage/lcov-report/src/index.html deleted file mode 100644 index 3790a43..0000000 --- a/coverage/lcov-report/src/index.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - Code coverage report for src - - - - - - - - - -
-
-

All files src

-
- -
- 49.16% - Statements - 59/120 -
- - -
- 33.96% - Branches - 36/106 -
- - -
- 53.84% - Functions - 14/26 -
- - -
- 50.9% - Lines - 56/110 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
auth.ts -
-
27.08%13/4824.39%10/4135.71%5/1429.26%12/41
db.ts -
-
75.6%31/4131.81%7/2275%6/877.5%31/40
env.ts -
-
48.38%15/3144.18%19/4375%3/444.82%13/29
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/lib/helpers.ts.html b/coverage/lcov-report/src/lib/helpers.ts.html deleted file mode 100644 index c051cfb..0000000 --- a/coverage/lcov-report/src/lib/helpers.ts.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/lib/helpers.ts - - - - - - - - - -
-
-

All files / src/lib helpers.ts

-
- -
- 55.55% - Statements - 5/9 -
- - -
- 50% - Branches - 5/10 -
- - -
- 100% - Functions - 1/1 -
- - -
- 66.66% - Lines - 4/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15  -  -  -7x -7x -  -  -7x -7x -  -  -  -  -  - 
// lib/helpers.ts
- 
-export function normalizeLocale(input: unknown) {
-    const raw = String(input ?? "en").trim().toLowerCase();
-    Iif (!raw) return "en";
- 
-    // Handle common variants: en-US, en_US, en-us -> en
-    const two = raw.split(/[-_]/)[0];
-    Eif (two === "en" || two === "ko") return two;
- 
-    // Fallback: keep first two letters if present
-    if (two.length >= 2) return two.slice(0, 2);
- 
-    return "en";
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/lib/index.html b/coverage/lcov-report/src/lib/index.html deleted file mode 100644 index fc0ca49..0000000 --- a/coverage/lcov-report/src/lib/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for src/lib - - - - - - - - - -
-
-

All files src/lib

-
- -
- 55.55% - Statements - 5/9 -
- - -
- 50% - Branches - 5/10 -
- - -
- 100% - Functions - 1/1 -
- - -
- 66.66% - Lines - 4/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
helpers.ts -
-
55.55%5/950%5/10100%1/166.66%4/6
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/mappers/index.html b/coverage/lcov-report/src/mappers/index.html deleted file mode 100644 index d3fb330..0000000 --- a/coverage/lcov-report/src/mappers/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for src/mappers - - - - - - - - - -
-
-

All files src/mappers

-
- -
- 81.81% - Statements - 18/22 -
- - -
- 60.56% - Branches - 43/71 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 16/16 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
labNotesMapper.ts -
-
81.81%18/2260.56%43/71100%5/5100%16/16
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/mappers/labNotesMapper.ts.html b/coverage/lcov-report/src/mappers/labNotesMapper.ts.html deleted file mode 100644 index 3c67eb1..0000000 --- a/coverage/lcov-report/src/mappers/labNotesMapper.ts.html +++ /dev/null @@ -1,554 +0,0 @@ - - - - - - Code coverage report for src/mappers/labNotesMapper.ts - - - - - - - - - -
-
-

All files / src/mappers labNotesMapper.ts

-
- -
- 81.81% - Statements - 18/22 -
- - -
- 60.56% - Branches - 43/71 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 16/16 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156  -  -  -1x -  -  -  -3x -3x -1x -  -  -7x -  -  -  -  -  -  -  -  -  -3x -  -3x -  -  -1x -1x -1x -1x -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import {LabNoteRecord, LabNoteView} from "../types/labNotes.js";
- 
-function deriveStatus(published: string): "published" | "draft" {
-    return published ? "published" : "draft";
-}
- 
-function normalizeStatus(s?: "published" | "draft" | "archived" | null | undefined, published?: string): "published" | "draft" | "archived" {
-    const v = (s ?? "").toLowerCase();
-    if (v === "published" || v === "draft" || v === "archived") return v;
-    return deriveStatus(published ?? "");
-}
-type LabNoteType = "labnote" | "paper" | "memo" | "lore" | "weather";
-const ALLOWED_NOTE_TYPES: ReadonlySet<LabNoteType> = new Set([
-    "labnote",
-    "paper",
-    "memo",
-    "lore",
-    "weather",
-]);
- 
-function deriveType(note: LabNoteRecord): LabNoteType {
-    // Prefer stored note.type if present
-    const raw = (note.type ?? "").toLowerCase() as LabNoteType;
- 
-    if (raw && ALLOWED_NOTE_TYPES.has(raw)) return raw;
- 
-    // Optional: derive from category if it has meaning
-    Iif (note.category === "paper") return "paper";
-    Iif (note.category === "memo") return "memo";
-    Iif (note.category === "lore") return "lore";
-    Iif (note.category === "weather") return "weather";
- 
-    return "labnote";
-}
-export function mapToLabNoteView(note: LabNoteRecord, tags: string[]): {
-    id: string;
-    slug: string;
-    title: string;
-    subtitle: string | undefined;
-    summary: string;
-    contentHtml: string;
-    published: string;
-    status: "published" | "draft" | "archived";
-    type: "labnote" | "paper" | "memo" | "lore" | "weather";
-    dept: string | undefined;
-    locale: string;
-    author: { kind: "human" | "ai" | "hybrid" } | undefined;
-    department_id: string;
-    shadow_density: number;
-    safer_landing: boolean;
-    tags: string[];
-    readingTime: number;
-    created_at: string | undefined;
-    updated_at: string | undefined
-} {
-    const published = note.published_at ?? "";
- 
-    return {
-        id: note.id,
-        slug: note.slug,
-        title: note.title,
- 
-        subtitle: note.subtitle ?? undefined,
-        summary: note.excerpt ?? "",
- 
-        contentHtml: note.content_html?.trim() || "<p>Content pending migration.</p>",
-        published,
- 
-        status: normalizeStatus(note.status, published),
-        type: deriveType(note),
- 
-        dept: note.dept ?? undefined,
-        locale: note.locale ?? "en",
- 
-        author: note.author_kind
-            ? {
-                kind: note.author_kind,
-                ...(note.author_name ? { name: note.author_name } : {}),
-                ...(note.author_id ? { id: note.author_id } : {}),
-            }
-            : undefined,
- 
-        department_id: note.department_id ?? "SCMS",
-        shadow_density: note.shadow_density ?? 0,
-        safer_landing: Boolean(note.safer_landing),
- 
-        tags,
-        readingTime: note.read_time_minutes ?? 5,
- 
-        created_at: note.created_at,
-        updated_at: note.updated_at,
-    };
-}
- 
-export function mapToLabNotePreview(note: LabNoteRecord, tags: string[]): {
-    id: string;
-    slug: string;
-    title: string;
-    subtitle: string | undefined;
-    summary: string;
-    contentHtml: string;
-    published: string;
-    status: "published" | "draft" | "archived";
-    type: "labnote" | "paper" | "memo" | "lore" | "weather";
-    dept: string | undefined;
-    locale: string;
-    author: { kind: "human" | "ai" | "hybrid" } | undefined;
-    department_id: string;
-    shadow_density: number;
-    safer_landing: boolean;
-    tags: string[];
-    readingTime: number;
-    created_at: string | undefined;
-    updated_at: string | undefined
-} {
-    const published = note.published_at ?? "";
- 
-    return {
-        id: note.id,
-        slug: note.slug,
-        title: note.title,
- 
-        subtitle: note.subtitle ?? undefined,
-        summary: note.excerpt ?? "",
- 
-        contentHtml: "",
-        published,
- 
-        status: normalizeStatus(note.status, published),
-        type: deriveType(note),
- 
-        dept: note.dept ?? undefined,
-        locale: note.locale ?? "en",
- 
-        author: note.author_kind
-            ? {
-                kind: note.author_kind,
-                ...(note.author_name ? { name: note.author_name } : {}),
-                ...(note.author_id ? { id: note.author_id } : {}),
-            }
-            : undefined,
- 
-        department_id: note.department_id ?? "SCMS",
-        shadow_density: note.shadow_density ?? 0,
-        safer_landing: Boolean(note.safer_landing),
- 
-        tags,
-        readingTime: note.read_time_minutes ?? 5,
- 
-        created_at: note.created_at,
-        updated_at: note.updated_at,
-    };
-}
- 
- 
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/middleware/index.html b/coverage/lcov-report/src/middleware/index.html deleted file mode 100644 index 5abab61..0000000 --- a/coverage/lcov-report/src/middleware/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for src/middleware - - - - - - - - - -
-
-

All files src/middleware

-
- -
- 11.53% - Statements - 3/26 -
- - -
- 0% - Branches - 0/23 -
- - -
- 0% - Functions - 0/5 -
- - -
- 12% - Lines - 3/25 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
requireAdmin.ts -
-
11.53%3/260%0/230%0/512%3/25
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/middleware/requireAdmin.ts.html b/coverage/lcov-report/src/middleware/requireAdmin.ts.html deleted file mode 100644 index 1e0dde5..0000000 --- a/coverage/lcov-report/src/middleware/requireAdmin.ts.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - Code coverage report for src/middleware/requireAdmin.ts - - - - - - - - - -
-
-

All files / src/middleware requireAdmin.ts

-
- -
- 11.53% - Statements - 3/26 -
- - -
- 0% - Branches - 0/23 -
- - -
- 0% - Functions - 0/5 -
- - -
- 12% - Lines - 3/25 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -7x -7x -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import type { Request, Response, NextFunction } from "express";
-import { getGithubLogin } from "../auth.js";
- 
-function getAuthToken(req: Request): string | null {
-    const h = req.header("authorization");
-    if (!h) return null;
-    const m = /^(Bearer|token)\s+(.+)$/i.exec(h.trim());
-    return m?.[2] ?? null;
-}
- 
-function parseAllowlist(envValue?: string): string[] {
-    return String(envValue ?? "")
-        .split(",")
-        .map(s => s.trim().toLowerCase())
-        .filter(Boolean);
-}
- 
-function getSessionLogin(req: Request): string | null {
-    // passport-github2 typically gives profile.username
-    const u: any = (req as any).user;
-    const login = (u?.username ?? u?.login ?? "").toString().trim();
-    return login ? login.toLowerCase() : null;
-}
- 
-// tiny cache: token -> { login, expiresAt }
-const tokenCache = new Map<string, { login: string; expiresAt: number }>();
-const CACHE_MS = 60_000;
- 
-export const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
-    // ✅ DEV BYPASS (explicit opt-in, never in production)
-    if (
-        process.env.NODE_ENV !== "production" &&
-        process.env.ADMIN_DEV_BYPASS === "true"
-    ) {
-        return next();
-    }
-    // 1) Not logged in? -> 401
-    if (!req.isAuthenticated?.()) {
-        return res.status(401).json({ error: "Unauthorized" });
-    }
- 
-    const login = getGithubLogin((req as any).user);
-    if (!login) {
-        return res.status(401).json({ error: "Unauthorized" });
-    }
- 
-    // 2) Now check allowlist
-    const allow = new Set<string>([
-        ...parseAllowlist(process.env.ADMIN_GITHUB_USERS),
-        ...parseAllowlist(process.env.ADMIN_GITHUB_LOGINS),
-        ...parseAllowlist(process.env.ALLOWED_GITHUB_USERNAME),
-    ]);
- 
-    // 3) If no allowlist is configured, fail closed (prod + everywhere if you prefer)
-    if (allow.size === 0) {
-        return res.status(403).json({ error: "Admin not configured" });
-    }
- 
-    // 4) Logged in but not allowed -> 403
-    if (!allow.has(login)) {
-        return res.status(403).json({ error: "Forbidden" });
-    }
- 
-    return next();
-};
- 
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/routes/adminRoutes.ts.html b/coverage/lcov-report/src/routes/adminRoutes.ts.html deleted file mode 100644 index a59690a..0000000 --- a/coverage/lcov-report/src/routes/adminRoutes.ts.html +++ /dev/null @@ -1,731 +0,0 @@ - - - - - - Code coverage report for src/routes/adminRoutes.ts - - - - - - - - - -
-
-

All files / src/routes adminRoutes.ts

-
- -
- 75% - Statements - 36/48 -
- - -
- 75% - Branches - 42/56 -
- - -
- 70% - Functions - 7/10 -
- - -
- 74.41% - Lines - 32/43 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215  -  -  -  -  -  -  -  -  -19x -  -  -  -  -19x -4x -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -  -  -19x -9x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -9x -  -9x -7x -  -5x -5x -  -5x -5x -  -  -5x -  -  -  -5x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -5x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -5x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -1x -1x -  -  -  -19x -1x -  -1x -1x -1x -  -  -  -1x -  -  -  -  -  -  -19x -1x -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -19x -  -  -  -  - 
// src/routes/adminRoutes.ts
-import type { Request, Response } from "express";
-import type Database from "better-sqlite3";
-import { randomUUID } from "node:crypto";
-import passport, { requireAdmin, isGithubOAuthEnabled } from "../auth.js";
-import { normalizeLocale } from "../lib/helpers.js";
- 
-export function registerAdminRoutes(app: any, db: Database.Database) {
-    // Must match your UI origin exactly (no trailing slash)
-    const UI_BASE_URL = process.env.UI_BASE_URL ?? "http://localhost:8001";
- 
-    // ---------------------------------------------------------------------------
-    // Admin: list Lab Notes (protected)
-    // ---------------------------------------------------------------------------
-    app.get("/admin/notes", requireAdmin, (_req: Request, res: Response) => {
-        try {
-            const rows = db
-                .prepare(
-                    `
-          SELECT
-            id, slug, title, locale,
-            type, status, dept,
-            category, excerpt, summary,
-            content_html,
-            department_id, shadow_density, coherence_score,
-            safer_landing, read_time_minutes, published_at,
-            created_at, updated_at
-          FROM v_lab_notes
-          ORDER BY published_at DESC, updated_at DESC
-        `
-                )
-                .all();
- 
-            return res.json(rows);
-        } catch (e: any) {
-            return res
-                .status(500)
-                .json({ error: "Database error", details: String(e?.message ?? e) });
-        }
-    });
- 
-    // ---------------------------------------------------------------------------
-    // Admin: upsert Lab Note (protected)
-    // ---------------------------------------------------------------------------
-    app.post("/admin/notes", requireAdmin, (req: Request, res: Response) => {
-        try {
-            const {
-                id,
-                title,
-                slug,
-                locale,
-                category,
-                excerpt,
-                content_html,
-                department_id,
-                shadow_density,
-                coherence_score,
-                safer_landing,
-                read_time_minutes,
-                published_at,
-                type,
-                status,
-                dept,
-                summary,
-                // tags, // keep for later
-            } = req.body ?? {};
- 
-            if (!title) return res.status(400).json({ error: "title is required" });
-            if (!slug) return res.status(400).json({ error: "slug is required" });
- 
-            const noteId = id ?? randomUUID();
-            const noteLocale = normalizeLocale(locale);
- 
-            const noteType = String(type ?? "labnote");
-            const noteStatus = String(status ?? (published_at ? "published" : "draft"));
- 
-            const normalizedPublishedAt =
-                noteStatus === "published"
-                    ? published_at || new Date().toISOString().slice(0, 10)
-                    : published_at || null;
- 
-            const stmt = db.prepare(`
-        INSERT INTO lab_notes (
-          id, title, slug, locale,
-          type, status, dept,
-          category, excerpt, summary, content_html,
-          department_id, shadow_density, coherence_score,
-          safer_landing, read_time_minutes, published_at,
-          updated_at
-        )
-        VALUES (
-          ?, ?, ?, ?,
-          ?, ?, ?,
-          ?, ?, ?, ?,
-          ?, ?, ?,
-          ?, ?, ?,
-          strftime('%Y-%m-%dT%H:%M:%fZ','now')
-        )
-        ON CONFLICT(slug, locale) DO UPDATE SET
-          title=excluded.title,
-          type=excluded.type,
-          status=excluded.status,
-          dept=excluded.dept,
-          category=excluded.category,
-          excerpt=excluded.excerpt,
-          summary=excluded.summary,
-          content_html=excluded.content_html,
-          department_id=excluded.department_id,
-          shadow_density=excluded.shadow_density,
-          coherence_score=excluded.coherence_score,
-          safer_landing=excluded.safer_landing,
-          read_time_minutes=excluded.read_time_minutes,
-          published_at=excluded.published_at,
-          updated_at=excluded.updated_at
-      `);
- 
-            stmt.run(
-                noteId,
-                title,
-                slug,
-                noteLocale,
- 
-                noteType,
-                noteStatus,
-                dept ?? null,
- 
-                category || "Uncategorized",
-                excerpt || "",
-                summary || "",
- 
-                content_html || null,
- 
-                department_id || "SCMS",
-                shadow_density ?? 0,
-                coherence_score ?? 1.0,
-                safer_landing ? 1 : 0,
-                read_time_minutes ?? 5,
- 
-                normalizedPublishedAt
-            );
- 
-            return res.status(201).json({
-                id: noteId,
-                slug,
-                locale: noteLocale,
-                type: noteType,
-                status: noteStatus,
-                message: "Note saved with energetic metadata",
-            });
-        } catch (e: any) {
-            return res.status(500).json({
-                error: "Database error",
-                details: String(e?.message ?? e),
-            });
-        }
-    });
- 
-    // ---------------------------------------------------------------------------
-    // Auth helpers (always available)
-    // ---------------------------------------------------------------------------
-    app.get("/auth/me", (req: Request, res: Response) => {
-        const user = (req as any).user ?? null;
-        Eif (!user) return res.status(401).json({ user: null });
-        return res.json({ user });
-    });
- 
-    app.post("/auth/logout", (req: Request, res: Response) => {
-        const done = () => res.json({ ok: true });
- 
-        try {
-            const anyReq = req as any;
-            Iif (typeof anyReq.logout === "function") {
-                if (anyReq.logout.length > 0) return anyReq.logout(done);
-                anyReq.logout();
-            }
-            return done();
-        } catch {
-            return done();
-        }
-    });
- 
-    // Simple status endpoint (handy for debugging)
-    app.get("/github/status", (_req: Request, res: Response) => {
-        res.json({ enabled: isGithubOAuthEnabled() });
-    });
- 
-    // ---------------------------------------------------------------------------
-    // GitHub OAuth
-    // ---------------------------------------------------------------------------
-    Iif (isGithubOAuthEnabled()) {
-        app.get("/auth/github", passport.authenticate("github", { scope: ["user:email"] }));
- 
-        app.get(
-            "/auth/github/callback",
-            passport.authenticate("github", {
-                failureRedirect: `${UI_BASE_URL}/admin/login`,
-                session: true,
-            }),
-            (_req: Request, res: Response) => {
-                // Land somewhere unambiguous (avoids "/admin" vs "/admin/admin" weirdness)
-                res.redirect(`${UI_BASE_URL}/admin/dashboard`);
-            }
-        );
-    } else {
-        // Fail loudly/clearly when OAuth is not configured
-        app.get("/auth/github", (_req: Request, res: Response) => {
-            res.status(503).json({ error: "GitHub OAuth disabled" });
-        });
- 
-        app.get("/auth/github/callback", (_req: Request, res: Response) => {
-            res.status(503).json({ error: "GitHub OAuth disabled" });
-        });
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/routes/adminTokensRoutes.ts.html b/coverage/lcov-report/src/routes/adminTokensRoutes.ts.html deleted file mode 100644 index 43a6b9d..0000000 --- a/coverage/lcov-report/src/routes/adminTokensRoutes.ts.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - Code coverage report for src/routes/adminTokensRoutes.ts - - - - - - - - - -
-
-

All files / src/routes adminTokensRoutes.ts

-
- -
- 10.71% - Statements - 3/28 -
- - -
- 0% - Branches - 0/21 -
- - -
- 12.5% - Functions - 1/8 -
- - -
- 13.04% - Lines - 3/23 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63  -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import type { Request, Response } from "express";
-import type Database from "better-sqlite3";
-import { mintApiToken } from "../auth/tokens.js";
-import { requireAdmin } from "../middleware/requireAdmin.js"; // your existing allowlist gate
- 
-export function registerAdminTokensRoutes(app: any, db: Database.Database) {
-    // List (no raw tokens)
-    app.get("/admin/tokens", requireAdmin, (_req: Request, res: Response) => {
-        const rows = db.prepare(`
-      SELECT id, label, scopes_json, is_active, expires_at, created_by_user, last_used_at, created_at
-      FROM api_tokens
-      ORDER BY created_at DESC
-    `).all() as any[];
- 
-        const data = rows.map((r) => ({
-            ...r,
-            scopes: safeParseJsonArray(r.scopes_json),
-        }));
- 
-        res.json({ ok: true, data });
-    });
- 
-    // Mint (returns raw token ONCE)
-    app.post("/admin/tokens", requireAdmin, (req: Request, res: Response) => {
-        const { label, scopes, expires_at } = req.body ?? {};
-        if (!label || typeof label !== "string") {
-            return res.status(400).json({ ok: false, error: { code: "bad_request", message: "label is required" } });
-        }
-        if (!Array.isArray(scopes) || scopes.some((s) => typeof s !== "string")) {
-            return res.status(400).json({ ok: false, error: { code: "bad_request", message: "scopes must be string[]" } });
-        }
- 
-        const created_by_user = (req as any).user?.username ?? (req as any).user?.login ?? null;
- 
-        const minted = mintApiToken(db, {
-            label,
-            scopes,
-            expires_at: typeof expires_at === "string" ? expires_at : null,
-            created_by_user,
-        });
- 
-        res.json({ ok: true, data: minted }); // includes raw token once
-    });
- 
-    // Revoke
-    app.post("/admin/tokens/:id/revoke", requireAdmin, (req: Request, res: Response) => {
-        const { id } = req.params;
-        db.prepare(`UPDATE api_tokens SET is_active = 0 WHERE id = ?`).run(id);
-        res.json({ ok: true });
-    });
-}
- 
-function safeParseJsonArray(input: any): string[] {
-    if (!input || typeof input !== "string") return [];
-    try {
-        const v = JSON.parse(input);
-        if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
-        return [];
-    } catch {
-        return [];
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/routes/healthRoutes.ts.html b/coverage/lcov-report/src/routes/healthRoutes.ts.html deleted file mode 100644 index dec8dd3..0000000 --- a/coverage/lcov-report/src/routes/healthRoutes.ts.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - Code coverage report for src/routes/healthRoutes.ts - - - - - - - - - -
-
-

All files / src/routes healthRoutes.ts

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9  -  -  -  -19x -1x -  -  - 
// src/routes/healthRoutes.ts
-import type { Request, Response } from "express";
- 
-export function registerHealthRoutes(app: any, dbPath: string) {
-    app.get("/health", (_req: Request, res: Response) => {
-        res.json({ status: "ok", dbPath });
-    });
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/routes/index.html b/coverage/lcov-report/src/routes/index.html deleted file mode 100644 index 969425b..0000000 --- a/coverage/lcov-report/src/routes/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - Code coverage report for src/routes - - - - - - - - - -
-
-

All files src/routes

-
- -
- 42.06% - Statements - 61/145 -
- - -
- 32.14% - Branches - 45/140 -
- - -
- 48.27% - Functions - 14/29 -
- - -
- 43.41% - Lines - 56/129 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
adminRoutes.ts -
-
75%36/4875%42/5670%7/1074.41%32/43
adminTokensRoutes.ts -
-
10.71%3/280%0/2112.5%1/813.04%3/23
healthRoutes.ts -
-
100%2/2100%0/0100%2/2100%2/2
labNotesRoutes.ts -
-
29.85%20/674.76%3/6344.44%4/931.14%19/61
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/src/routes/labNotesRoutes.ts.html b/coverage/lcov-report/src/routes/labNotesRoutes.ts.html deleted file mode 100644 index a83996b..0000000 --- a/coverage/lcov-report/src/routes/labNotesRoutes.ts.html +++ /dev/null @@ -1,866 +0,0 @@ - - - - - - Code coverage report for src/routes/labNotesRoutes.ts - - - - - - - - - -
-
-

All files / src/routes labNotesRoutes.ts

-
- -
- 29.85% - Statements - 20/67 -
- - -
- 4.76% - Branches - 3/63 -
- - -
- 44.44% - Functions - 4/9 -
- - -
- 31.14% - Lines - 19/61 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -2x -2x -  -  -  -  -  -  -2x -  -2x -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -2x -2x -  -  -  -2x -  -  -2x -  -  -  -  -  -  -  -  -19x -2x -  -2x -  -  -  -  -  -  -  -  -  -2x -  -1x -  -  -  -1x -  -  -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
// src/routes/labNotesRoutes.ts
-import type { Request, Response } from "express";
-import type Database from "better-sqlite3";
-import type { LabNoteRecord, TagResult } from "../types/labNotes.js";
-import type { UpsertBody } from "../types/UpsertBody.js"
-import { mapToLabNotePreview, mapToLabNoteView } from "../mappers/labNotesMapper.js";
-import { normalizeLocale } from "../lib/helpers.js"
- 
-// OPTIONAL: markdown -> html (pick one implementation)
-// If you already have a markdown renderer elsewhere in the API, use that instead.
-import { marked } from "marked"; // npm i marked
- 
-export function registerLabNotesRoutes(app: any, db: Database.Database) {
-    // Public: Lab Notes list (preview)
-    app.get("/lab-notes", (req: Request, res: Response) => {
-        try {
-            const baseLocale = (input: unknown) => {
-                const raw = String(input ?? "en").trim().toLowerCase();
-                if (!raw) return "en";
-                const two = raw.split(/[-_]/)[0];
-                return two === "all" ? "all" : (two || "en");
-            };
- 
-            const locale = normalizeLocale(req.query.locale);
- 
-            const orderBy = `
-      ORDER BY
-        CASE WHEN published_at IS NULL OR published_at = '' THEN 1 ELSE 0 END,
-        published_at DESC,
-        updated_at DESC
-    `;
- 
-            const sqlAll = `
-      SELECT
-        id, slug, locale, type, status, title, subtitle, summary, excerpt, content_html,
-        department_id, dept, shadow_density, safer_landing, read_time_minutes,
-        published_at, created_at, updated_at
-      FROM v_lab_notes
-      WHERE status != 'archived'
-      ${orderBy}
-    `;
- 
-            const sqlByLocale = `
-      SELECT
-        id, slug, locale, type, status, title, subtitle, summary, excerpt, content_html,
-        department_id, dept, shadow_density, safer_landing, read_time_minutes,
-        published_at, created_at, updated_at
-      FROM v_lab_notes
-      WHERE locale = ?
-        AND status != 'archived'
-      ${orderBy}
-    `;
- 
-            const notes = (locale === "all"
-                    ? db.prepare(sqlAll).all()
-                    : db.prepare(sqlByLocale).all(locale)
-            ) as LabNoteRecord[];
- 
-            const mapped = notes.map((note) => {
-                const tagRows = db
-                    .prepare("SELECT tag FROM lab_note_tags WHERE note_id = ?")
-                    .all(note.id) as TagResult[];
- 
-                return mapToLabNotePreview(note, tagRows.map((t) => t.tag));
-            });
- 
-            return res.json(mapped);
-        } catch (e: any) {
-            console.error("GET /lab-notes failed:", e?.message);
-            if (res.headersSent) return;
-            return res.status(500).json({ error: e?.message ?? "unknown" });
-        }
-    });
- 
-    // Public: single Lab Note (detail)
-    app.get("/lab-notes/:slug", (req: Request, res: Response) => {
-        const { slug } = req.params;
- 
-        const note = db.prepare(`
-          SELECT
-            id, slug, title, excerpt, content_html, 
-            department_id, shadow_density, coherence_score,
-            safer_landing, read_time_minutes, published_at, category, created_at, updated_at
-          FROM v_lab_notes
-          WHERE slug = ?
-        `).get(slug) as LabNoteRecord | undefined;
- 
- 
-        if (!note) return res.status(404).json({ error: "Not found" });
- 
-        const tagRows = db
-            .prepare("SELECT tag FROM lab_note_tags WHERE note_id = ?")
-            .all(note.id) as TagResult[];
- 
-        res.json(mapToLabNoteView(note, tagRows.map((t) => t.tag)));
-    });
- 
-    // Authenticated: upsert a note by slug
-    app.post("/lab-notes/upsert", (req: Request, res: Response) => {
-        // TODO: add auth middleware (Bearer token) when ready
-        const body = req.body as Partial<UpsertBody>;
- 
-        if (!body.slug || !body.title || !body.markdown) {
-            return res.status(400).json({ error: "slug, title, markdown are required" });
-        }
- 
-        const slug = body.slug.trim();
-        const title = body.title.trim();
-        const markdown = String(body.markdown);
-        const html = marked.parse(markdown) as string;
- 
-        // ✅ locale must match your schema + UI behavior
-        const locale = normalizeLocale((body as any).locale);
- 
-        // tags + metadata
-        const tags = body.tags ?? [];
-        const department_id = body.department_id ?? "SCMS";
-        const dept = (body as any).dept ?? null;
- 
-        const shadow_density = body.shadow_density ?? 0;
-        const safer_landing = body.safer_landing ? 1 : 0;
-        const read_time_minutes = body.read_time_minutes ?? 5;
- 
-        // type/status (support your taxonomy)
-        const type = ((body as any).type ?? "labnote");
-        const status =
-            ((body as any).status ?? ((body.published_at ?? "").trim() ? "published" : "draft"));
- 
-        // published date only when published
-        const published_at =
-            status === "published"
-                ? ((body.published_at ?? "").trim() || new Date().toISOString().slice(0, 10))
-                : null;
- 
-        // summary/excerpt
-        const summary = (body as any).summary ?? null;
- 
-        const excerpt =
-            body.excerpt ??
-            markdown
-                .replace(/```[\s\S]*?```/g, "")
-                .replace(/[#>*_`]/g, "")
-                .trim()
-                .slice(0, 220);
- 
-        // category: keep if you still use it, otherwise null
-        const category = body.category ?? null;
- 
-        // ✅ check existing by (slug, locale)
-        const existing = db
-            .prepare("SELECT id FROM lab_notes WHERE slug = ? AND locale = ?")
-            .get(slug, locale) as { id: string } | undefined;
- 
-        const noteId = existing?.id ?? crypto.randomUUID();
- 
-        const tx = db.transaction(() => {
-            if (existing) {
-                db.prepare(`
-        UPDATE lab_notes
-        SET
-          title = ?,
-          excerpt = ?,
-          summary = ?,
-          content_html = ?,
-          department_id = ?,
-          dept = ?,
-          type = ?,
-          status = ?,
-          shadow_density = ?,
-          safer_landing = ?,
-          read_time_minutes = ?,
-          published_at = ?,
-          category = ?,
-          updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
-        WHERE slug = ? AND locale = ?
-      `).run(
-                    title,
-                    excerpt,
-                    summary,
-                    html,              // ✅ store HTML in content_html
-                    department_id,
-                    dept,
-                    type,
-                    status,
-                    shadow_density,
-                    safer_landing,
-                    read_time_minutes,
-                    published_at,
-                    category,
-                    slug,
-                    locale
-                );
- 
-                db.prepare("DELETE FROM lab_note_tags WHERE note_id = ?").run(noteId);
-            } else {
-                db.prepare(`
-        INSERT INTO lab_notes (
-          id, group_id, slug, locale,
-          type, status, title,
-          category, excerpt, summary, content_html,
-          department_id, dept, shadow_density, safer_landing,
-          read_time_minutes, published_at,
-          created_at, updated_at
-        ) VALUES (
-          ?, ?, ?, ?,
-          ?, ?, ?,
-          ?, ?, ?, ?,
-          ?, ?, ?, ?,
-          ?, ?,
-          strftime('%Y-%m-%dT%H:%M:%fZ','now'),
-          strftime('%Y-%m-%dT%H:%M:%fZ','now')
-        )
-      `).run(
-                    noteId,
-                    noteId,            // group_id defaulting to noteId keeps v2 happy
-                    slug,
-                    locale,
-                    type,
-                    status,
-                    title,
-                    category,
-                    excerpt,
-                    summary,
-                    html,              // ✅ store HTML
-                    department_id,
-                    dept,
-                    shadow_density,
-                    safer_landing,
-                    read_time_minutes,
-                    published_at
-                );
-            }
- 
-            const insertTag = db.prepare(
-                "INSERT OR IGNORE INTO lab_note_tags (note_id, tag) VALUES (?, ?)"
-            );
- 
-            for (const t of tags) {
-                const tag = String(t).trim();
-                if (tag) insertTag.run(noteId, tag);
-            }
-        });
- 
-        try {
-            tx();
-            return res.json({
-                ok: true,
-                slug,
-                locale,
-                id: noteId,
-                action: existing ? "updated" : "created",
-            });
-        } catch (e: any) {
-            return res.status(500).json({ ok: false, error: e?.message ?? "upsert failed" });
-        }
-    });
- 
- 
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/tests/helpers/createTestApp.js.html b/coverage/lcov-report/tests/helpers/createTestApp.js.html deleted file mode 100644 index 48a46d5..0000000 --- a/coverage/lcov-report/tests/helpers/createTestApp.js.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - Code coverage report for tests/helpers/createTestApp.js - - - - - - - - - -
-
-

All files / tests/helpers createTestApp.js

-
- -
- 100% - Statements - 15/15 -
- - -
- 87.5% - Branches - 7/8 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37  -  -  -  -  -  -  -  -  -24x -24x -24x -  -  -  -  -  -19x -5x -  -  -19x -19x -  -19x -  -19x -19x -19x -19x -  -19x -19x -  -19x -  - 
// tests/helpers/createTestApp.js
-import express from "express";
-import { registerHealthRoutes } from "../../src/routes/healthRoutes.js";
-import { registerLabNotesRoutes } from "../../src/routes/labNotesRoutes.js";
-import { registerAdminRoutes } from "../../src/routes/adminRoutes.js";
-import { registerAdminTokensRoutes} from "../../src/routes/adminTokensRoutes.js";
-import { openDb, bootstrapDb, seedMarkerNote } from "../../src/db.js";
- 
-export function api(path) {
-    const prefix = process.env.API_PREFIX ?? "";
-    const p = path.startsWith("/") ? path : `/${path}`;
-    return `${prefix}${p}`;
-}
- 
-export function createTestApp() {
-    // ✅ Make admin auth "configured" in tests so unauthenticated requests return 401
-    // (otherwise requireAdmin returns 403: "Admin not configured")
-    if (!(process.env.ADMIN_GITHUB_USERS ?? "").trim()) {
-        process.env.ADMIN_GITHUB_USERS = "ada";
-    }
- 
-    const app = express();
-    app.use(express.json());
- 
-    const db = openDb(":memory:");
- 
-    registerHealthRoutes(app, db);
-    registerLabNotesRoutes(app, db);
-    registerAdminRoutes(app, db);
-    registerAdminTokensRoutes(app, db);
- 
-    bootstrapDb(db);
-    seedMarkerNote(db);
- 
-    return { app, db };
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/tests/helpers/index.html b/coverage/lcov-report/tests/helpers/index.html deleted file mode 100644 index ceae81d..0000000 --- a/coverage/lcov-report/tests/helpers/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Code coverage report for tests/helpers - - - - - - - - - -
-
-

All files tests/helpers

-
- -
- 100% - Statements - 15/15 -
- - -
- 87.5% - Branches - 7/8 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
createTestApp.js -
-
100%15/1587.5%7/8100%2/2100%15/15
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info deleted file mode 100644 index 0f66f73..0000000 --- a/coverage/lcov.info +++ /dev/null @@ -1,1147 +0,0 @@ -TN: -SF:src\auth.ts -FN:8,getGithubOAuthEnv -FN:16,isGithubOAuthEnabled -FN:25,missingGithubOAuthKeys -FN:27,(anonymous_3) -FN:30,configurePassport -FN:49,(anonymous_5) -FN:57,(anonymous_6) -FN:58,(anonymous_7) -FN:61,(anonymous_8) -FN:70,getGithubLogin -FN:75,parseAllowlist -FN:78,(anonymous_11) -FN:83,buildAdminAllowlist -FN:98,(anonymous_13) -FNF:14 -FNH:5 -FNDA:0,getGithubOAuthEnv -FNDA:20,isGithubOAuthEnabled -FNDA:0,missingGithubOAuthKeys -FNDA:0,(anonymous_3) -FNDA:0,configurePassport -FNDA:0,(anonymous_5) -FNDA:0,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -FNDA:0,getGithubLogin -FNDA:12,parseAllowlist -FNDA:12,(anonymous_11) -FNDA:4,buildAdminAllowlist -FNDA:17,(anonymous_13) -DA:9,0 -DA:17,20 -DA:18,0 -DA:26,0 -DA:27,0 -DA:31,0 -DA:33,0 -DA:34,0 -DA:35,0 -DA:39,0 -DA:42,0 -DA:43,0 -DA:44,0 -DA:46,0 -DA:50,0 -DA:51,0 -DA:52,0 -DA:57,0 -DA:58,0 -DA:61,7 -DA:66,0 -DA:67,0 -DA:71,0 -DA:72,0 -DA:76,12 -DA:78,12 -DA:84,4 -DA:98,7 -DA:100,17 -DA:104,13 -DA:107,4 -DA:110,4 -DA:111,0 -DA:114,4 -DA:115,4 -DA:118,0 -DA:119,0 -DA:120,0 -DA:123,0 -DA:124,0 -DA:127,0 -LF:41 -LH:12 -BRDA:10,0,0,0 -BRDA:10,0,1,0 -BRDA:10,0,2,0 -BRDA:17,1,0,20 -BRDA:17,1,1,0 -BRDA:19,2,0,0 -BRDA:19,2,1,0 -BRDA:19,2,2,0 -BRDA:31,3,0,0 -BRDA:31,3,1,0 -BRDA:34,4,0,0 -BRDA:34,4,1,0 -BRDA:51,5,0,0 -BRDA:51,5,1,0 -BRDA:51,6,0,0 -BRDA:51,6,1,0 -BRDA:66,7,0,0 -BRDA:66,7,1,0 -BRDA:71,8,0,0 -BRDA:71,8,1,0 -BRDA:71,8,2,0 -BRDA:72,9,0,0 -BRDA:72,9,1,0 -BRDA:76,10,0,12 -BRDA:76,10,1,8 -BRDA:100,11,0,13 -BRDA:100,11,1,4 -BRDA:101,12,0,17 -BRDA:101,12,1,17 -BRDA:110,13,0,0 -BRDA:110,13,1,4 -BRDA:110,14,0,4 -BRDA:110,14,1,0 -BRDA:114,15,0,4 -BRDA:114,15,1,0 -BRDA:119,16,0,0 -BRDA:119,16,1,0 -BRDA:123,17,0,0 -BRDA:123,17,1,0 -BRDA:123,18,0,0 -BRDA:123,18,1,0 -BRF:41 -BRH:10 -end_of_record -TN: -SF:src\db.ts -FN:11,resolveDbPath -FN:36,openDb -FN:41,getLabNotesSchemaVersion -FN:58,setLabNotesSchemaVersion -FN:73,bootstrapDb -FN:99,sha256Hex -FN:108,seedMarkerNote -FN:313,isDbEmpty -FNF:8 -FNH:6 -FNDA:0,resolveDbPath -FNDA:19,openDb -FNDA:19,getLabNotesSchemaVersion -FNDA:19,setLabNotesSchemaVersion -FNDA:19,bootstrapDb -FNDA:19,sha256Hex -FNDA:19,seedMarkerNote -FNDA:0,isDbEmpty -DA:12,0 -DA:13,0 -DA:17,0 -DA:22,0 -DA:29,0 -DA:30,0 -DA:33,0 -DA:37,19 -DA:38,19 -DA:43,19 -DA:50,19 -DA:54,19 -DA:55,19 -DA:59,19 -DA:66,19 -DA:74,19 -DA:77,19 -DA:79,19 -DA:80,19 -DA:81,19 -DA:82,19 -DA:87,19 -DA:96,19 -DA:100,19 -DA:109,19 -DA:110,19 -DA:111,19 -DA:112,19 -DA:115,19 -DA:121,19 -DA:124,19 -DA:221,19 -DA:223,19 -DA:241,19 -DA:260,19 -DA:261,19 -DA:263,19 -DA:304,19 -DA:314,0 -DA:315,0 -LF:40 -LH:31 -BRDA:17,0,0,0 -BRDA:17,0,1,0 -BRDA:22,1,0,0 -BRDA:22,1,1,0 -BRDA:24,2,0,0 -BRDA:24,2,1,0 -BRDA:29,3,0,0 -BRDA:29,3,1,0 -BRDA:29,4,0,0 -BRDA:29,4,1,0 -BRDA:37,5,0,0 -BRDA:37,5,1,19 -BRDA:54,6,0,19 -BRDA:54,6,1,19 -BRDA:55,7,0,19 -BRDA:55,7,1,0 -BRDA:74,8,0,0 -BRDA:74,8,1,19 -BRDA:81,9,0,19 -BRDA:81,9,1,0 -BRDA:121,10,0,0 -BRDA:121,10,1,19 -BRF:22 -BRH:7 -end_of_record -TN: -SF:src\env.ts -FN:31,(anonymous_0) -FN:61,normalizeNodeEnv -FN:67,parsePort -FN:77,validateEnv -FNF:4 -FNH:3 -FNDA:0,(anonymous_0) -FNDA:7,normalizeNodeEnv -FNDA:7,parsePort -FNDA:7,validateEnv -DA:32,0 -DA:33,0 -DA:38,7 -DA:41,7 -DA:43,0 -DA:44,0 -DA:50,0 -DA:51,0 -DA:52,0 -DA:53,0 -DA:57,0 -DA:62,7 -DA:63,7 -DA:64,0 -DA:68,7 -DA:69,0 -DA:70,0 -DA:71,0 -DA:73,0 -DA:78,7 -DA:79,7 -DA:81,7 -DA:83,7 -DA:84,0 -DA:89,7 -DA:90,7 -DA:91,0 -DA:94,7 -DA:112,7 -LF:29 -LH:13 -BRDA:38,0,0,7 -BRDA:38,0,1,0 -BRDA:41,1,0,0 -BRDA:41,1,1,7 -BRDA:41,2,0,7 -BRDA:41,2,1,7 -BRDA:45,3,0,0 -BRDA:45,3,1,0 -BRDA:52,4,0,0 -BRDA:52,4,1,0 -BRDA:62,5,0,0 -BRDA:62,5,1,7 -BRDA:62,6,0,0 -BRDA:62,6,1,7 -BRDA:63,7,0,7 -BRDA:63,7,1,0 -BRDA:63,8,0,7 -BRDA:63,8,1,7 -BRDA:63,8,2,0 -BRDA:68,9,0,7 -BRDA:68,9,1,0 -BRDA:68,10,0,7 -BRDA:68,10,1,0 -BRDA:70,11,0,0 -BRDA:70,11,1,0 -BRDA:70,12,0,0 -BRDA:70,12,1,0 -BRDA:70,12,2,0 -BRDA:70,12,3,0 -BRDA:78,13,0,7 -BRDA:78,13,1,0 -BRDA:81,14,0,7 -BRDA:81,14,1,7 -BRDA:83,15,0,0 -BRDA:83,15,1,7 -BRDA:83,16,0,7 -BRDA:83,16,1,7 -BRDA:83,16,2,0 -BRDA:90,17,0,0 -BRDA:90,17,1,7 -BRDA:90,18,0,7 -BRDA:90,18,1,0 -BRDA:90,18,2,0 -BRF:43 -BRH:19 -end_of_record -TN: -SF:src\auth\tokens.ts -FN:15,nowIso -FN:20,base64Url -FN:29,tokenPepper -FN:41,hashToken -FN:46,generateRawToken -FN:53,mintApiToken -FN:85,verifyApiToken -FN:126,safeParseJsonArray -FN:130,(anonymous_8) -FNF:9 -FNH:0 -FNDA:0,nowIso -FNDA:0,base64Url -FNDA:0,tokenPepper -FNDA:0,hashToken -FNDA:0,generateRawToken -FNDA:0,mintApiToken -FNDA:0,verifyApiToken -FNDA:0,safeParseJsonArray -FNDA:0,(anonymous_8) -DA:16,0 -DA:21,0 -DA:30,0 -DA:31,0 -DA:33,0 -DA:34,0 -DA:36,0 -DA:38,0 -DA:42,0 -DA:43,0 -DA:48,0 -DA:49,0 -DA:50,0 -DA:62,0 -DA:63,0 -DA:64,0 -DA:66,0 -DA:82,0 -DA:89,0 -DA:91,0 -DA:98,0 -DA:99,0 -DA:101,0 -DA:102,0 -DA:103,0 -DA:106,0 -DA:109,0 -DA:111,0 -DA:127,0 -DA:128,0 -DA:129,0 -DA:130,0 -DA:131,0 -DA:133,0 -LF:34 -LH:0 -BRDA:31,0,0,0 -BRDA:31,0,1,0 -BRDA:31,1,0,0 -BRDA:31,1,1,0 -BRDA:33,2,0,0 -BRDA:33,2,1,0 -BRDA:49,3,0,0 -BRDA:49,3,1,0 -BRDA:75,4,0,0 -BRDA:75,4,1,0 -BRDA:76,5,0,0 -BRDA:76,5,1,0 -BRDA:77,6,0,0 -BRDA:77,6,1,0 -BRDA:98,7,0,0 -BRDA:98,7,1,0 -BRDA:99,8,0,0 -BRDA:99,8,1,0 -BRDA:101,9,0,0 -BRDA:101,9,1,0 -BRDA:103,10,0,0 -BRDA:103,10,1,0 -BRDA:103,11,0,0 -BRDA:103,11,1,0 -BRDA:117,12,0,0 -BRDA:117,12,1,0 -BRDA:118,13,0,0 -BRDA:118,13,1,0 -BRDA:119,14,0,0 -BRDA:119,14,1,0 -BRDA:127,15,0,0 -BRDA:127,15,1,0 -BRDA:127,16,0,0 -BRDA:127,16,1,0 -BRDA:130,17,0,0 -BRDA:130,17,1,0 -BRF:36 -BRH:0 -end_of_record -TN: -SF:src\db\migrateApiTokens.ts -FN:6,getVersion -FN:13,setVersion -FN:21,migrateApiTokensSchema -FNF:3 -FNH:3 -FNDA:19,getVersion -FNDA:19,setVersion -FNDA:19,migrateApiTokensSchema -DA:4,7 -DA:7,19 -DA:10,19 -DA:14,19 -DA:23,19 -DA:30,19 -DA:31,19 -DA:33,19 -DA:58,19 -LF:9 -LH:9 -BRDA:10,0,0,0 -BRDA:10,0,1,19 -BRDA:31,1,0,0 -BRDA:31,1,1,19 -BRF:4 -BRH:2 -end_of_record -TN: -SF:src\db\migrateLabNotes.ts -FN:7,setLabNotesSchemaVersion -FN:31,sha256Hex -FN:35,nowIso -FN:39,getLabNotesSchemaVersion -FN:63,migrateLabNotesSchema -FN:145,(anonymous_5) -FN:303,(anonymous_6) -FN:683,safeParseJsonArray -FN:687,(anonymous_8) -FNF:9 -FNH:5 -FNDA:25,setLabNotesSchemaVersion -FNDA:0,sha256Hex -FNDA:0,nowIso -FNDA:25,getLabNotesSchemaVersion -FNDA:25,migrateLabNotesSchema -FNDA:87,(anonymous_5) -FNDA:23,(anonymous_6) -FNDA:0,safeParseJsonArray -FNDA:0,(anonymous_8) -DA:5,8 -DA:8,25 -DA:12,25 -DA:14,23 -DA:32,0 -DA:36,0 -DA:40,25 -DA:44,25 -DA:69,25 -DA:74,25 -DA:82,25 -DA:87,25 -DA:91,25 -DA:144,25 -DA:145,87 -DA:148,25 -DA:149,25 -DA:150,775 -DA:151,713 -DA:152,713 -DA:157,25 -DA:175,25 -DA:185,25 -DA:196,25 -DA:297,25 -DA:300,25 -DA:301,23 -DA:303,23 -DA:304,23 -DA:312,23 -DA:313,1 -DA:317,1 -DA:320,1 -DA:324,23 -DA:325,23 -DA:330,25 -DA:331,23 -DA:428,25 -DA:429,23 -DA:573,25 -DA:653,25 -DA:655,25 -DA:661,25 -DA:669,2 -DA:673,2 -DA:675,2 -DA:680,25 -DA:684,0 -DA:685,0 -DA:686,0 -DA:687,0 -DA:688,0 -DA:690,0 -LF:53 -LH:45 -BRDA:12,0,0,2 -BRDA:12,0,1,23 -BRDA:44,1,0,2 -BRDA:44,1,1,23 -BRDA:150,2,0,713 -BRDA:150,2,1,62 -BRDA:300,3,0,23 -BRDA:300,3,1,2 -BRDA:317,4,0,0 -BRDA:317,4,1,1 -BRDA:330,5,0,23 -BRDA:330,5,1,2 -BRDA:428,6,0,23 -BRDA:428,6,1,2 -BRDA:661,7,0,2 -BRDA:661,7,1,23 -BRDA:662,8,0,25 -BRDA:662,8,1,3 -BRDA:662,8,2,1 -BRDA:662,8,3,1 -BRDA:662,8,4,1 -BRDA:669,9,0,2 -BRDA:669,9,1,0 -BRDA:673,10,0,0 -BRDA:673,10,1,2 -BRDA:684,11,0,0 -BRDA:684,11,1,0 -BRDA:684,12,0,0 -BRDA:684,12,1,0 -BRDA:687,13,0,0 -BRDA:687,13,1,0 -BRF:31 -BRH:22 -end_of_record -TN: -SF:src\db\migrations\2025-01-dedupe-lab-notes-slugs.ts -FN:4,dedupeLabNotesSlugs -FNF:1 -FNH:1 -FNDA:19,dedupeLabNotesSlugs -DA:5,19 -DA:7,19 -DA:23,19 -LF:3 -LH:3 -BRDA:4,0,0,19 -BRDA:5,1,0,0 -BRDA:5,1,1,19 -BRF:3 -BRH:2 -end_of_record -TN: -SF:src\lib\helpers.ts -FN:3,normalizeLocale -FNF:1 -FNH:1 -FNDA:7,normalizeLocale -DA:4,7 -DA:5,7 -DA:8,7 -DA:9,7 -DA:12,0 -DA:14,0 -LF:6 -LH:4 -BRDA:4,0,0,7 -BRDA:4,0,1,2 -BRDA:5,1,0,0 -BRDA:5,1,1,7 -BRDA:9,2,0,7 -BRDA:9,2,1,0 -BRDA:9,3,0,7 -BRDA:9,3,1,0 -BRDA:12,4,0,0 -BRDA:12,4,1,0 -BRF:10 -BRH:5 -end_of_record -TN: -SF:src\mappers\labNotesMapper.ts -FN:3,deriveStatus -FN:7,normalizeStatus -FN:21,deriveType -FN:35,mapToLabNoteView -FN:95,mapToLabNotePreview -FNF:5 -FNH:5 -FNDA:1,deriveStatus -FNDA:3,normalizeStatus -FNDA:3,deriveType -FNDA:1,mapToLabNoteView -FNDA:2,mapToLabNotePreview -DA:4,1 -DA:8,3 -DA:9,3 -DA:10,1 -DA:13,7 -DA:23,3 -DA:25,3 -DA:28,1 -DA:29,1 -DA:30,1 -DA:31,1 -DA:33,1 -DA:56,1 -DA:58,1 -DA:116,2 -DA:118,2 -LF:16 -LH:16 -BRDA:4,0,0,1 -BRDA:4,0,1,0 -BRDA:8,1,0,3 -BRDA:8,1,1,1 -BRDA:9,2,0,2 -BRDA:9,2,1,1 -BRDA:9,3,0,3 -BRDA:9,3,1,1 -BRDA:9,3,2,1 -BRDA:10,4,0,1 -BRDA:10,4,1,0 -BRDA:23,5,0,3 -BRDA:23,5,1,1 -BRDA:25,6,0,2 -BRDA:25,6,1,1 -BRDA:25,7,0,3 -BRDA:25,7,1,2 -BRDA:28,8,0,0 -BRDA:28,8,1,1 -BRDA:29,9,0,0 -BRDA:29,9,1,1 -BRDA:30,10,0,0 -BRDA:30,10,1,1 -BRDA:31,11,0,0 -BRDA:31,11,1,1 -BRDA:56,12,0,1 -BRDA:56,12,1,0 -BRDA:63,13,0,1 -BRDA:63,13,1,1 -BRDA:64,14,0,1 -BRDA:64,14,1,0 -BRDA:66,15,0,1 -BRDA:66,15,1,1 -BRDA:72,16,0,1 -BRDA:72,16,1,1 -BRDA:73,17,0,1 -BRDA:73,17,1,1 -BRDA:75,18,0,0 -BRDA:75,18,1,1 -BRDA:78,19,0,0 -BRDA:78,19,1,0 -BRDA:79,20,0,0 -BRDA:79,20,1,0 -BRDA:83,21,0,1 -BRDA:83,21,1,0 -BRDA:84,22,0,1 -BRDA:84,22,1,0 -BRDA:88,23,0,1 -BRDA:88,23,1,0 -BRDA:116,24,0,2 -BRDA:116,24,1,0 -BRDA:123,25,0,2 -BRDA:123,25,1,2 -BRDA:124,26,0,2 -BRDA:124,26,1,0 -BRDA:132,27,0,2 -BRDA:132,27,1,0 -BRDA:133,28,0,2 -BRDA:133,28,1,0 -BRDA:135,29,0,0 -BRDA:135,29,1,2 -BRDA:138,30,0,0 -BRDA:138,30,1,0 -BRDA:139,31,0,0 -BRDA:139,31,1,0 -BRDA:143,32,0,2 -BRDA:143,32,1,0 -BRDA:144,33,0,2 -BRDA:144,33,1,0 -BRDA:148,34,0,2 -BRDA:148,34,1,0 -BRF:71 -BRH:43 -end_of_record -TN: -SF:src\middleware\requireAdmin.ts -FN:4,getAuthToken -FN:11,parseAllowlist -FN:14,(anonymous_2) -FN:18,getSessionLogin -FN:29,(anonymous_4) -FNF:5 -FNH:0 -FNDA:0,getAuthToken -FNDA:0,parseAllowlist -FNDA:0,(anonymous_2) -FNDA:0,getSessionLogin -FNDA:0,(anonymous_4) -DA:5,0 -DA:6,0 -DA:7,0 -DA:8,0 -DA:12,0 -DA:14,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:26,7 -DA:27,7 -DA:29,7 -DA:31,0 -DA:35,0 -DA:38,0 -DA:39,0 -DA:42,0 -DA:43,0 -DA:44,0 -DA:48,0 -DA:55,0 -DA:56,0 -DA:60,0 -DA:61,0 -DA:64,0 -LF:25 -LH:3 -BRDA:6,0,0,0 -BRDA:6,0,1,0 -BRDA:8,1,0,0 -BRDA:8,1,1,0 -BRDA:12,2,0,0 -BRDA:12,2,1,0 -BRDA:21,3,0,0 -BRDA:21,3,1,0 -BRDA:21,3,2,0 -BRDA:22,4,0,0 -BRDA:22,4,1,0 -BRDA:31,5,0,0 -BRDA:31,5,1,0 -BRDA:32,6,0,0 -BRDA:32,6,1,0 -BRDA:38,7,0,0 -BRDA:38,7,1,0 -BRDA:43,8,0,0 -BRDA:43,8,1,0 -BRDA:55,9,0,0 -BRDA:55,9,1,0 -BRDA:60,10,0,0 -BRDA:60,10,1,0 -BRF:23 -BRH:0 -end_of_record -TN: -SF:src\routes\adminRoutes.ts -FN:8,registerAdminRoutes -FN:15,(anonymous_1) -FN:45,(anonymous_2) -FN:161,(anonymous_3) -FN:167,(anonymous_4) -FN:168,(anonymous_5) -FN:183,(anonymous_6) -FN:199,(anonymous_7) -FN:206,(anonymous_8) -FN:210,(anonymous_9) -FNF:10 -FNH:7 -FNDA:19,registerAdminRoutes -FNDA:4,(anonymous_1) -FNDA:9,(anonymous_2) -FNDA:1,(anonymous_3) -FNDA:1,(anonymous_4) -FNDA:1,(anonymous_5) -FNDA:1,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -FNDA:0,(anonymous_9) -DA:10,19 -DA:15,19 -DA:16,4 -DA:17,4 -DA:34,4 -DA:36,0 -DA:45,19 -DA:46,9 -DA:66,9 -DA:68,9 -DA:69,7 -DA:71,5 -DA:72,5 -DA:74,5 -DA:75,5 -DA:78,5 -DA:82,5 -DA:117,5 -DA:142,5 -DA:151,0 -DA:161,19 -DA:162,1 -DA:163,1 -DA:164,0 -DA:167,19 -DA:168,1 -DA:170,1 -DA:171,1 -DA:172,1 -DA:173,0 -DA:174,0 -DA:176,1 -DA:178,0 -DA:183,19 -DA:184,1 -DA:190,19 -DA:191,0 -DA:193,0 -DA:201,0 -DA:206,19 -DA:207,0 -DA:210,19 -DA:211,0 -LF:43 -LH:32 -BRDA:10,0,0,19 -BRDA:10,0,1,19 -BRDA:38,1,0,0 -BRDA:38,1,1,0 -BRDA:66,2,0,9 -BRDA:66,2,1,0 -BRDA:68,3,0,2 -BRDA:68,3,1,7 -BRDA:69,4,0,2 -BRDA:69,4,1,5 -BRDA:71,5,0,5 -BRDA:71,5,1,5 -BRDA:74,6,0,5 -BRDA:74,6,1,5 -BRDA:75,7,0,5 -BRDA:75,7,1,0 -BRDA:75,8,0,0 -BRDA:75,8,1,0 -BRDA:78,9,0,1 -BRDA:78,9,1,4 -BRDA:79,10,0,1 -BRDA:79,10,1,1 -BRDA:80,11,0,4 -BRDA:80,11,1,4 -BRDA:125,12,0,5 -BRDA:125,12,1,5 -BRDA:127,13,0,5 -BRDA:127,13,1,5 -BRDA:128,14,0,5 -BRDA:128,14,1,5 -BRDA:129,15,0,5 -BRDA:129,15,1,3 -BRDA:131,16,0,5 -BRDA:131,16,1,5 -BRDA:133,17,0,5 -BRDA:133,17,1,5 -BRDA:134,18,0,5 -BRDA:134,18,1,5 -BRDA:135,19,0,5 -BRDA:135,19,1,5 -BRDA:136,20,0,0 -BRDA:136,20,1,5 -BRDA:137,21,0,5 -BRDA:137,21,1,5 -BRDA:153,22,0,0 -BRDA:153,22,1,0 -BRDA:162,23,0,1 -BRDA:162,23,1,1 -BRDA:163,24,0,1 -BRDA:163,24,1,0 -BRDA:172,25,0,0 -BRDA:172,25,1,1 -BRDA:173,26,0,0 -BRDA:173,26,1,0 -BRDA:190,27,0,0 -BRDA:190,27,1,19 -BRF:56 -BRH:42 -end_of_record -TN: -SF:src\routes\adminTokensRoutes.ts -FN:6,registerAdminTokensRoutes -FN:8,(anonymous_1) -FN:15,(anonymous_2) -FN:24,(anonymous_3) -FN:29,(anonymous_4) -FN:46,(anonymous_5) -FN:53,safeParseJsonArray -FN:57,(anonymous_7) -FNF:8 -FNH:1 -FNDA:19,registerAdminTokensRoutes -FNDA:0,(anonymous_1) -FNDA:0,(anonymous_2) -FNDA:0,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -FNDA:0,safeParseJsonArray -FNDA:0,(anonymous_7) -DA:8,19 -DA:9,0 -DA:15,0 -DA:20,0 -DA:24,19 -DA:25,0 -DA:26,0 -DA:27,0 -DA:29,0 -DA:30,0 -DA:33,0 -DA:35,0 -DA:42,0 -DA:46,19 -DA:47,0 -DA:48,0 -DA:49,0 -DA:54,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:60,0 -LF:23 -LH:3 -BRDA:25,0,0,0 -BRDA:25,0,1,0 -BRDA:26,1,0,0 -BRDA:26,1,1,0 -BRDA:26,2,0,0 -BRDA:26,2,1,0 -BRDA:29,3,0,0 -BRDA:29,3,1,0 -BRDA:29,4,0,0 -BRDA:29,4,1,0 -BRDA:33,5,0,0 -BRDA:33,5,1,0 -BRDA:33,5,2,0 -BRDA:38,6,0,0 -BRDA:38,6,1,0 -BRDA:54,7,0,0 -BRDA:54,7,1,0 -BRDA:54,8,0,0 -BRDA:54,8,1,0 -BRDA:57,9,0,0 -BRDA:57,9,1,0 -BRF:21 -BRH:0 -end_of_record -TN: -SF:src\routes\healthRoutes.ts -FN:4,registerHealthRoutes -FN:5,(anonymous_1) -FNF:2 -FNH:2 -FNDA:19,registerHealthRoutes -FNDA:1,(anonymous_1) -DA:5,19 -DA:6,1 -LF:2 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src\routes\labNotesRoutes.ts -FN:13,registerLabNotesRoutes -FN:15,(anonymous_1) -FN:17,(anonymous_2) -FN:59,(anonymous_3) -FN:64,(anonymous_4) -FN:76,(anonymous_5) -FN:95,(anonymous_6) -FN:99,(anonymous_7) -FN:156,(anonymous_8) -FNF:9 -FNH:4 -FNDA:19,registerLabNotesRoutes -FNDA:2,(anonymous_1) -FNDA:0,(anonymous_2) -FNDA:2,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:2,(anonymous_5) -FNDA:0,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -DA:15,19 -DA:16,2 -DA:17,2 -DA:18,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:24,2 -DA:26,2 -DA:33,2 -DA:43,2 -DA:54,2 -DA:59,2 -DA:60,2 -DA:64,2 -DA:67,2 -DA:69,0 -DA:70,0 -DA:71,0 -DA:76,19 -DA:77,2 -DA:79,2 -DA:89,2 -DA:91,1 -DA:95,1 -DA:99,19 -DA:101,0 -DA:103,0 -DA:104,0 -DA:107,0 -DA:108,0 -DA:109,0 -DA:110,0 -DA:113,0 -DA:116,0 -DA:117,0 -DA:118,0 -DA:120,0 -DA:121,0 -DA:122,0 -DA:125,0 -DA:127,0 -DA:131,0 -DA:136,0 -DA:139,0 -DA:147,0 -DA:150,0 -DA:154,0 -DA:156,0 -DA:157,0 -DA:158,0 -DA:194,0 -DA:196,0 -DA:234,0 -DA:238,0 -DA:239,0 -DA:240,0 -DA:244,0 -DA:245,0 -DA:246,0 -DA:254,0 -LF:61 -LH:19 -BRDA:18,0,0,0 -BRDA:18,0,1,0 -BRDA:19,1,0,0 -BRDA:19,1,1,0 -BRDA:21,2,0,0 -BRDA:21,2,1,0 -BRDA:21,3,0,0 -BRDA:21,3,1,0 -BRDA:54,4,0,0 -BRDA:54,4,1,2 -BRDA:70,5,0,0 -BRDA:70,5,1,0 -BRDA:71,6,0,0 -BRDA:71,6,1,0 -BRDA:89,7,0,1 -BRDA:89,7,1,1 -BRDA:103,8,0,0 -BRDA:103,8,1,0 -BRDA:103,9,0,0 -BRDA:103,9,1,0 -BRDA:103,9,2,0 -BRDA:116,10,0,0 -BRDA:116,10,1,0 -BRDA:117,11,0,0 -BRDA:117,11,1,0 -BRDA:118,12,0,0 -BRDA:118,12,1,0 -BRDA:120,13,0,0 -BRDA:120,13,1,0 -BRDA:121,14,0,0 -BRDA:121,14,1,0 -BRDA:122,15,0,0 -BRDA:122,15,1,0 -BRDA:125,16,0,0 -BRDA:125,16,1,0 -BRDA:127,17,0,0 -BRDA:127,17,1,0 -BRDA:127,18,0,0 -BRDA:127,18,1,0 -BRDA:127,19,0,0 -BRDA:127,19,1,0 -BRDA:131,20,0,0 -BRDA:131,20,1,0 -BRDA:132,21,0,0 -BRDA:132,21,1,0 -BRDA:132,22,0,0 -BRDA:132,22,1,0 -BRDA:136,23,0,0 -BRDA:136,23,1,0 -BRDA:139,24,0,0 -BRDA:139,24,1,0 -BRDA:147,25,0,0 -BRDA:147,25,1,0 -BRDA:154,26,0,0 -BRDA:154,26,1,0 -BRDA:157,27,0,0 -BRDA:157,27,1,0 -BRDA:240,28,0,0 -BRDA:240,28,1,0 -BRDA:251,29,0,0 -BRDA:251,29,1,0 -BRDA:254,30,0,0 -BRDA:254,30,1,0 -BRF:63 -BRH:3 -end_of_record -TN: -SF:tests\helpers\createTestApp.js -FN:9,api -FN:15,createTestApp -FNF:2 -FNH:2 -FNDA:24,api -FNDA:19,createTestApp -DA:10,24 -DA:11,24 -DA:12,24 -DA:18,19 -DA:19,5 -DA:22,19 -DA:23,19 -DA:25,19 -DA:27,19 -DA:28,19 -DA:29,19 -DA:30,19 -DA:32,19 -DA:33,19 -DA:35,19 -LF:15 -LH:15 -BRDA:10,0,0,24 -BRDA:10,0,1,24 -BRDA:11,1,0,24 -BRDA:11,1,1,0 -BRDA:18,2,0,5 -BRDA:18,2,1,14 -BRDA:18,3,0,19 -BRDA:18,3,1,5 -BRF:8 -BRH:7 -end_of_record diff --git a/src/db/migrateLabNotes.ts b/src/db/migrateLabNotes.ts index d72d910..f8a9b78 100644 --- a/src/db/migrateLabNotes.ts +++ b/src/db/migrateLabNotes.ts @@ -1,8 +1,23 @@ // src/db/migrateLabNotes.ts import Database from "better-sqlite3"; import crypto from "crypto"; +/** + * migrateLabNotesSchema + * + * Responsibilities: + * - Ensure lab_notes is an identity + pointer table + * - Ensure lab_note_revisions is the sole content truth + * - Ensure views NEVER treat lab_notes.content_html as authoritative + * + * Non-responsibilities: + * - Rendering markdown to HTML + * - Business logic for publish/unpublish + * - Auth decisions + * + * If content appears stale or "reverts", check views FIRST. + */ -export const LAB_NOTES_SCHEMA_VERSION = 8; +export const LAB_NOTES_SCHEMA_VERSION = 9; function setLabNotesSchemaVersion(db: Database.Database, version: number) { const cur = db @@ -296,36 +311,16 @@ export function migrateLabNotesSchema( let ranV2DataMigration = false; -// ---- v2 data migration (one-time) ---- + // ---- v2 data migration (one-time) ---- if (prevVersion < 2) { - let seededCount = 0; - - const tx = db.transaction(() => { - const rows = db.prepare(` - SELECT id, slug, locale, type, title, subtitle, summary, tags_json, dept, - status, published_at, author, ai_author, created_at, updated_at - FROM lab_notes - `).all() as any[]; - - // ...prepare statements... - - for (const n of rows) { - const pointer = db - .prepare(`SELECT current_revision_id FROM lab_notes WHERE id = ?`) - .get(n.id) as { current_revision_id?: string } | undefined; - - if (pointer?.current_revision_id) continue; - - // ...insert revision, update pointers, insert event... - seededCount++; - } - }); - - tx(); - ranV2DataMigration = seededCount > 0; + // ---- v2 data migration (legacy installs only) ---- + // NOTE: + // This migration was used during early v2 rollout. + // New installs and modern DBs rely on seedMarkerNote + admin writes. + // Intentionally left as a no-op placeholder for schema history clarity. + let ranV2DataMigration = false; } - // ---- drop legacy markdown column (table rebuild) ---- if (prevVersion < 7) { db.exec(` @@ -569,89 +564,130 @@ export function migrateLabNotesSchema( } - // ✅ Views created LAST (after any rebuild) - db.exec(` - DROP VIEW IF EXISTS v_lab_notes; - - CREATE VIEW v_lab_notes AS - SELECT - id, - group_id, - slug, - locale, - type, - title, - - category, - excerpt, - department_id, - shadow_density, - safer_landing, - read_time_minutes, - coherence_score, - subtitle, - summary, - tags_json, - dept, - - status, - published_at, - author, - ai_author, - - source_locale, - translation_status, - translation_provider, - translation_version, - source_updated_at, - translation_meta_json, - content_html, - - current_revision_id, - published_revision_id, + // ⚠️ WARNING ⚠️ + // If v_lab_notes ever reads lab_notes.content_html as the primary content source, + // the system will desync (admin writes one model, readers consume another). + // Always join through lab_note_revisions. - created_at, - updated_at - FROM lab_notes; - DROP VIEW IF EXISTS v_lab_notes_current; + // ✅ Views created LAST (after any rebuild) + // IMPORTANT: + // These views MUST be ledger-first. + // lab_notes.content_html is legacy-only and must never be treated as truth. + db.exec(` + DROP VIEW IF EXISTS v_lab_notes; + DROP VIEW IF EXISTS v_lab_notes_current; + + -- Canonical effective view + -- Used by admin lists, detail views, and public reads (with filtering). + CREATE VIEW v_lab_notes AS + SELECT + n.id, + n.group_id, + n.slug, + n.locale, + n.type, + n.title, + + n.category, + n.excerpt, + n.department_id, + n.shadow_density, + n.safer_landing, + n.read_time_minutes, + n.coherence_score, + n.subtitle, + n.summary, + n.tags_json, + n.dept, + + n.status, + n.published_at, + n.author, + n.ai_author, + + n.source_locale, + n.translation_status, + n.translation_provider, + n.translation_version, + n.source_updated_at, + n.translation_meta_json, + + -- Ledger-first content resolution: + -- 1) Published revision if published + -- 2) Current draft revision + -- 3) Any available revision + -- 4) Legacy content_html (last-resort fallback) + COALESCE( + CASE + WHEN n.status = 'published' THEN pub.content_markdown + ELSE cur.content_markdown + END, + cur.content_markdown, + pub.content_markdown, + n.content_html + ) AS content_markdown, + + -- Legacy passthrough (do not rely on this) + n.content_html, + + n.current_revision_id, + n.published_revision_id, + + n.created_at, + n.updated_at + + FROM lab_notes n + LEFT JOIN lab_note_revisions cur + ON cur.id = n.current_revision_id + LEFT JOIN lab_note_revisions pub + ON pub.id = n.published_revision_id + ; + + -- Admin-only "current draft truth" view + CREATE VIEW v_lab_notes_current AS + SELECT + n.id AS note_id, + n.slug, + n.locale, + n.title, + n.status, + n.published_at, + n.author, + n.ai_author, + + n.current_revision_id, + n.published_revision_id, + + r.revision_num, + r.schema_version, + r.source, + r.intent, + r.intent_version, + r.scope_json, + r.side_effects_json, + r.reversible, + r.auth_type, + r.scopes_json, + r.frontmatter_json, + r.content_markdown, + r.content_hash, + r.created_at AS revision_created_at, + + n.created_at, + n.updated_at + + FROM lab_notes n + LEFT JOIN lab_note_revisions r + ON r.id = n.current_revision_id + ; +`); - CREATE VIEW v_lab_notes_current AS - SELECT - n.id AS note_id, - n.slug, - n.locale, - n.title, - n.status, - n.author, - n.ai_author, - n.current_revision_id, - n.published_revision_id, - r.revision_num, - r.schema_version, - r.source, - r.intent, - r.intent_version, - r.scope_json, - r.side_effects_json, - r.reversible, - r.auth_type, - r.scopes_json, - r.frontmatter_json, - r.content_markdown, - r.content_hash, - r.created_at AS revision_created_at, - n.created_at, - n.updated_at - FROM lab_notes n - LEFT JOIN lab_note_revisions r - ON r.id = n.current_revision_id; - `); // Set DB version last (after successful schema + data migration) setLabNotesSchemaVersion(db, LAB_NOTES_SCHEMA_VERSION); - + db.pragma("foreign_keys = ON"); const result: MigrationResult = { addedColumns, createdFreshTable: !hadLabNotesTable, diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index fc9ac7a..2879602 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,4 +1,5 @@ // lib/helpers.ts +import crypto from "crypto"; export function normalizeLocale(input: unknown) { const raw = String(input ?? "en").trim().toLowerCase(); @@ -12,4 +13,8 @@ export function normalizeLocale(input: unknown) { if (two.length >= 2) return two.slice(0, 2); return "en"; +} + +export function sha256Hex(input: string): string { + return crypto.createHash("sha256").update(input, "utf8").digest("hex"); } \ No newline at end of file diff --git a/src/routes/adminRoutes.ts b/src/routes/adminRoutes.ts index 231cc68..4c64a04 100644 --- a/src/routes/adminRoutes.ts +++ b/src/routes/adminRoutes.ts @@ -3,7 +3,7 @@ import type { Request, Response } from "express"; import type Database from "better-sqlite3"; import { randomUUID } from "node:crypto"; import passport, { requireAdmin, isGithubOAuthEnabled } from "../auth.js"; -import { normalizeLocale } from "../lib/helpers.js"; +import { normalizeLocale, sha256Hex } from "../lib/helpers.js"; export function registerAdminRoutes(app: any, db: Database.Database) { // Must match your UI origin exactly (no trailing slash) @@ -17,28 +17,68 @@ export function registerAdminRoutes(app: any, db: Database.Database) { const rows = db .prepare( ` - SELECT - id, slug, title, locale, - type, status, dept, - category, excerpt, summary, - content_html, - department_id, shadow_density, coherence_score, - safer_landing, read_time_minutes, published_at, - created_at, updated_at - FROM v_lab_notes - ORDER BY published_at DESC, updated_at DESC - ` + SELECT + id, slug, title, locale, + type, status, dept, + category, excerpt, summary, + department_id, shadow_density, coherence_score, + safer_landing, read_time_minutes, + published_at, + created_at, updated_at + FROM v_lab_notes + ORDER BY + CASE WHEN status = 'published' THEN 0 ELSE 1 END, + published_at DESC, + updated_at DESC + ` ) .all(); return res.json(rows); } catch (e: any) { - return res - .status(500) - .json({ error: "Database error", details: String(e?.message ?? e) }); + return res.status(500).json({ error: "Database error", details: String(e?.message ?? e) }); + } + }); + + + // --------------------------------------------------------------------------- + // Admin: single Lab Note (protected) + // --------------------------------------------------------------------------- + app.get("/admin/notes/:slug", requireAdmin, (req: Request, res: Response) => { + try { + const slug = String(req.params.slug ?? "").trim(); + const locale = normalizeLocale(String(req.query.locale ?? "en")); + + if (!slug) return res.status(400).json({ error: "slug is required" }); + + const row = db + .prepare( + ` + SELECT + id, slug, title, locale, + type, status, dept, + category, excerpt, summary, + content_markdown, + department_id, shadow_density, coherence_score, + safer_landing, read_time_minutes, + published_at, + created_at, updated_at + FROM v_lab_notes + WHERE slug = ? AND locale = ? + LIMIT 1 + ` + ) + .get(slug, locale); + + if (!row) return res.status(404).json({ error: "Not found" }); + return res.json(row); + } catch (e: any) { + return res.status(500).json({ error: "Database error", details: String(e?.message ?? e) }); } }); + + // --------------------------------------------------------------------------- // Admin: upsert Lab Note (protected) // --------------------------------------------------------------------------- @@ -51,7 +91,8 @@ export function registerAdminRoutes(app: any, db: Database.Database) { locale, category, excerpt, - content_html, + summary, + content_markdown, // optional (empty allowed) department_id, shadow_density, coherence_score, @@ -61,92 +102,232 @@ export function registerAdminRoutes(app: any, db: Database.Database) { type, status, dept, - summary, - // tags, // keep for later } = req.body ?? {}; if (!title) return res.status(400).json({ error: "title is required" }); if (!slug) return res.status(400).json({ error: "slug is required" }); - const noteId = id ?? randomUUID(); const noteLocale = normalizeLocale(locale); - const noteType = String(type ?? "labnote"); const noteStatus = String(status ?? (published_at ? "published" : "draft")); - const normalizedPublishedAt = noteStatus === "published" - ? published_at || new Date().toISOString().slice(0, 10) - : published_at || null; + ? (published_at ?? new Date().toISOString().slice(0, 10)) + : null; - const stmt = db.prepare(` - INSERT INTO lab_notes ( - id, title, slug, locale, - type, status, dept, - category, excerpt, summary, content_html, - department_id, shadow_density, coherence_score, - safer_landing, read_time_minutes, published_at, - updated_at - ) - VALUES ( - ?, ?, ?, ?, - ?, ?, ?, - ?, ?, ?, ?, - ?, ?, ?, - ?, ?, ?, - strftime('%Y-%m-%dT%H:%M:%fZ','now') - ) - ON CONFLICT(slug, locale) DO UPDATE SET - title=excluded.title, - type=excluded.type, - status=excluded.status, - dept=excluded.dept, - category=excluded.category, - excerpt=excluded.excerpt, - summary=excluded.summary, - content_html=excluded.content_html, - department_id=excluded.department_id, - shadow_density=excluded.shadow_density, - coherence_score=excluded.coherence_score, - safer_landing=excluded.safer_landing, - read_time_minutes=excluded.read_time_minutes, - published_at=excluded.published_at, - updated_at=excluded.updated_at - `); - - stmt.run( - noteId, - title, - slug, - noteLocale, + // Allow empty markdown so existing tests that only send metadata still pass. + const bodyMarkdown = String(content_markdown ?? ""); - noteType, - noteStatus, - dept ?? null, + // ✅ Resolve canonical noteId by (slug, locale) to make upserts stable + const existing = db + .prepare("SELECT id FROM lab_notes WHERE slug = ? AND locale = ?") + .get(slug, noteLocale) as { id: string } | undefined; - category || "Uncategorized", - excerpt || "", - summary || "", + // If the row already exists, prefer its id over any incoming id. + // This prevents “identity drift” where (slug, locale) updates a different id. + const noteId = existing?.id ?? id ?? randomUUID(); - content_html || null, + const tx = db.transaction(() => { + // 1) Upsert metadata row (NO content_html writes, NO content_markdown column) + db.prepare(` + INSERT INTO lab_notes ( + id, title, slug, locale, + type, status, dept, + category, excerpt, summary, + department_id, shadow_density, coherence_score, + safer_landing, read_time_minutes, published_at, + updated_at + ) + VALUES ( + ?, ?, ?, ?, + ?, ?, ?, + ?, ?, ?, + ?, ?, ?, + ?, ?, ?, + strftime('%Y-%m-%dT%H:%M:%fZ','now') + ) + ON CONFLICT(slug, locale) DO UPDATE SET + title=excluded.title, + type=excluded.type, + status=excluded.status, + dept=excluded.dept, + category=excluded.category, + excerpt=excluded.excerpt, + summary=excluded.summary, + department_id=excluded.department_id, + shadow_density=excluded.shadow_density, + coherence_score=excluded.coherence_score, + safer_landing=excluded.safer_landing, + read_time_minutes=excluded.read_time_minutes, + published_at=excluded.published_at, + updated_at=excluded.updated_at + `).run( + noteId, + title, + slug, + noteLocale, - department_id || "SCMS", - shadow_density ?? 0, - coherence_score ?? 1.0, - safer_landing ? 1 : 0, - read_time_minutes ?? 5, + noteType, + noteStatus, + dept ?? null, - normalizedPublishedAt - ); + category || "Uncategorized", + excerpt || "", + summary || "", - return res.status(201).json({ - id: noteId, - slug, - locale: noteLocale, - type: noteType, - status: noteStatus, - message: "Note saved with energetic metadata", + department_id || "SCMS", + shadow_density ?? 0, + coherence_score ?? 1.0, + safer_landing ? 1 : 0, + read_time_minutes ?? 5, + + normalizedPublishedAt + ); + + // 2) Clear legacy HTML so nothing can “win” accidentally + db.prepare(`UPDATE lab_notes SET content_html = NULL WHERE id = ?`).run(noteId); + + // 3) Compute next revision_num + const revRow = db + .prepare(` + SELECT COALESCE(MAX(revision_num), 0) AS maxRev + FROM lab_note_revisions + WHERE note_id = ? + `) + .get(noteId) as { maxRev: number } | undefined; + + const nextRev = (revRow?.maxRev ?? 0) + 1; + + // 4) Create revision row (ledger truth) + const revisionId = crypto.randomUUID(); + + const prevPointer = db + .prepare(` + SELECT current_revision_id AS cur + FROM lab_notes + WHERE id = ? + `) + .get(noteId) as { cur?: string } | undefined; + + const frontmatter = { + id: noteId, + slug, + locale: noteLocale, + type: noteType, + title, + status: noteStatus, + published: normalizedPublishedAt ?? undefined, + dept: dept ?? null, + department_id: department_id || "SCMS", + shadow_density: shadow_density ?? 0, + coherence_score: coherence_score ?? 1.0, + safer_landing: Boolean(safer_landing), + summary: summary || "", + excerpt: excerpt || "", + category: category || "Uncategorized", + read_time_minutes: read_time_minutes ?? 5, + }; + + const canonical = `${JSON.stringify(frontmatter)}\n---\n${bodyMarkdown}`; + const contentHash = sha256Hex(canonical); + + db.prepare(` + INSERT INTO lab_note_revisions ( + id, note_id, revision_num, supersedes_revision_id, + frontmatter_json, content_markdown, content_hash, + schema_version, source, + intent, intent_version, + scope_json, side_effects_json, reversible, + auth_type, scopes_json, + reasoning_json, + created_at + ) + VALUES ( + ?, ?, ?, ?, + ?, ?, ?, + ?, ?, + ?, ?, + ?, ?, ?, + ?, ?, + NULL, + strftime('%Y-%m-%dT%H:%M:%fZ','now') + ) + `).run( + revisionId, + noteId, + nextRev, + prevPointer?.cur ?? null, + + JSON.stringify(frontmatter), + bodyMarkdown, + contentHash, + + "0.1", + "web", // must satisfy CHECK constraint + "admin_save", + "1", + + JSON.stringify(["db"]), + JSON.stringify(["update_note"]), + 1, + + "human_session", + JSON.stringify([]) + ); + + // 5) Update pointers (current always points at latest save) + db.prepare(` + UPDATE lab_notes + SET + current_revision_id = ?, + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ? + `).run(revisionId, noteId); + + // 6) Enforce publish/draft hard rules + if (noteStatus === "published") { + db.prepare(` + UPDATE lab_notes + SET + published_revision_id = current_revision_id, + published_at = COALESCE(published_at, ?), + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ? + `).run( + normalizedPublishedAt ?? new Date().toISOString().slice(0, 10), + noteId + ); + } else { + db.prepare(` + UPDATE lab_notes + SET + published_revision_id = NULL, + published_at = NULL, + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ? + `).run(noteId); + } + + return { noteId }; }); + + const { noteId: savedId } = tx(); + + // Return canonical row with current ledger content + const saved = db + .prepare(` + SELECT + n.*, + r.content_markdown AS content_markdown, + r.frontmatter_json AS frontmatter_json + FROM lab_notes n + LEFT JOIN lab_note_revisions r ON r.id = n.current_revision_id + WHERE n.id = ? + LIMIT 1 + `) + .get(savedId); + + return res.status(201).json(saved); } catch (e: any) { return res.status(500).json({ error: "Database error", @@ -155,6 +336,68 @@ export function registerAdminRoutes(app: any, db: Database.Database) { } }); + + + // --------------------------------------------------------------------------- + // Admin: Publish Lab Note (protected) + // --------------------------------------------------------------------------- + app.post("/admin/notes/:id/publish", requireAdmin, (req: Request, res: Response) => { + try { + const id = String(req.params.id ?? "").trim(); + if (!id) return res.status(400).json({ error: "id is required" }); + + const nowDate = new Date().toISOString().slice(0, 10); + + const result = db + .prepare( + ` + UPDATE lab_notes + SET + status = 'published', + published_at = COALESCE(published_at, ?), + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ? + ` + ) + .run(nowDate, id); + + if (result.changes === 0) return res.status(404).json({ error: "Not found" }); + return res.json({ ok: true, id, status: "published" }); + } catch (e: any) { + return res.status(500).json({ error: "Database error", details: String(e?.message ?? e) }); + } + }); + + + // --------------------------------------------------------------------------- + // Admin: Un-publish Lab Note (protected) + // --------------------------------------------------------------------------- + app.post("/admin/notes/:id/unpublish", requireAdmin, (req: Request, res: Response) => { + try { + const id = String(req.params.id ?? "").trim(); + if (!id) return res.status(400).json({ error: "id is required" }); + + const result = db + .prepare( + ` + UPDATE lab_notes + SET + status = 'draft', + published_at = NULL, + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') + WHERE id = ? + ` + ) + .run(id); + + if (result.changes === 0) return res.status(404).json({ error: "Not found" }); + return res.json({ ok: true, id, status: "draft" }); + } catch (e: any) { + return res.status(500).json({ error: "Database error", details: String(e?.message ?? e) }); + } + }); + + // --------------------------------------------------------------------------- // Auth helpers (always available) // --------------------------------------------------------------------------- diff --git a/src/routes/adminTokensRoutes.ts b/src/routes/adminTokensRoutes.ts index 676aee1..bbf551d 100644 --- a/src/routes/adminTokensRoutes.ts +++ b/src/routes/adminTokensRoutes.ts @@ -39,7 +39,7 @@ export function registerAdminTokensRoutes(app: any, db: Database.Database) { created_by_user, }); - res.json({ ok: true, data: minted }); // includes raw token once + return res.status(201).json({ ok: true, data: minted }); // includes raw token once }); // Revoke diff --git a/src/routes/labNotesRoutes.ts b/src/routes/labNotesRoutes.ts index a5e2edd..62d5d35 100644 --- a/src/routes/labNotesRoutes.ts +++ b/src/routes/labNotesRoutes.ts @@ -2,54 +2,48 @@ import type { Request, Response } from "express"; import type Database from "better-sqlite3"; import type { LabNoteRecord, TagResult } from "../types/labNotes.js"; -import type { UpsertBody } from "../types/UpsertBody.js" import { mapToLabNotePreview, mapToLabNoteView } from "../mappers/labNotesMapper.js"; -import { normalizeLocale } from "../lib/helpers.js" +import { normalizeLocale } from "../lib/helpers.js"; -// OPTIONAL: markdown -> html (pick one implementation) -// If you already have a markdown renderer elsewhere in the API, use that instead. +// Markdown -> HTML for public rendering import { marked } from "marked"; // npm i marked export function registerLabNotesRoutes(app: any, db: Database.Database) { - // Public: Lab Notes list (preview) + // --------------------------------------------------------------------------- + // Public: Lab Notes list (preview) — PUBLISHED ONLY, PREVIEW FIELDS ONLY + // --------------------------------------------------------------------------- app.get("/lab-notes", (req: Request, res: Response) => { try { - const baseLocale = (input: unknown) => { - const raw = String(input ?? "en").trim().toLowerCase(); - if (!raw) return "en"; - const two = raw.split(/[-_]/)[0]; - return two === "all" ? "all" : (two || "en"); - }; - const locale = normalizeLocale(req.query.locale); const orderBy = ` - ORDER BY - CASE WHEN published_at IS NULL OR published_at = '' THEN 1 ELSE 0 END, - published_at DESC, - updated_at DESC - `; + ORDER BY + published_at DESC, + updated_at DESC + `; const sqlAll = ` - SELECT - id, slug, locale, type, status, title, subtitle, summary, excerpt, content_html, - department_id, dept, shadow_density, safer_landing, read_time_minutes, - published_at, created_at, updated_at - FROM v_lab_notes - WHERE status != 'archived' - ${orderBy} - `; + SELECT + id, slug, locale, type, status, + title, subtitle, summary, excerpt, + department_id, dept, shadow_density, safer_landing, read_time_minutes, + published_at, created_at, updated_at + FROM v_lab_notes + WHERE status = 'published' + ${orderBy} + `; const sqlByLocale = ` - SELECT - id, slug, locale, type, status, title, subtitle, summary, excerpt, content_html, - department_id, dept, shadow_density, safer_landing, read_time_minutes, - published_at, created_at, updated_at - FROM v_lab_notes - WHERE locale = ? - AND status != 'archived' - ${orderBy} - `; + SELECT + id, slug, locale, type, status, + title, subtitle, summary, excerpt, + department_id, dept, shadow_density, safer_landing, read_time_minutes, + published_at, created_at, updated_at + FROM v_lab_notes + WHERE locale = ? + AND status = 'published' + ${orderBy} + `; const notes = (locale === "all" ? db.prepare(sqlAll).all() @@ -72,188 +66,63 @@ export function registerLabNotesRoutes(app: any, db: Database.Database) { } }); - // Public: single Lab Note (detail) + // --------------------------------------------------------------------------- + // Public: single Lab Note (detail) — PUBLISHED ONLY, (slug, locale) identity + // Renders markdown from ledger-backed content_markdown via v_lab_notes. + // --------------------------------------------------------------------------- app.get("/lab-notes/:slug", (req: Request, res: Response) => { - const { slug } = req.params; - - const note = db.prepare(` - SELECT - id, slug, title, excerpt, content_html, - department_id, shadow_density, coherence_score, - safer_landing, read_time_minutes, published_at, category, created_at, updated_at - FROM v_lab_notes - WHERE slug = ? - `).get(slug) as LabNoteRecord | undefined; - - - if (!note) return res.status(404).json({ error: "Not found" }); - - const tagRows = db - .prepare("SELECT tag FROM lab_note_tags WHERE note_id = ?") - .all(note.id) as TagResult[]; - - res.json(mapToLabNoteView(note, tagRows.map((t) => t.tag))); - }); - - // Authenticated: upsert a note by slug - app.post("/lab-notes/upsert", (req: Request, res: Response) => { - // TODO: add auth middleware (Bearer token) when ready - const body = req.body as Partial; - - if (!body.slug || !body.title || !body.markdown) { - return res.status(400).json({ error: "slug, title, markdown are required" }); - } - - const slug = body.slug.trim(); - const title = body.title.trim(); - const markdown = String(body.markdown); - const html = marked.parse(markdown) as string; - - // ✅ locale must match your schema + UI behavior - const locale = normalizeLocale((body as any).locale); - - // tags + metadata - const tags = body.tags ?? []; - const department_id = body.department_id ?? "SCMS"; - const dept = (body as any).dept ?? null; - - const shadow_density = body.shadow_density ?? 0; - const safer_landing = body.safer_landing ? 1 : 0; - const read_time_minutes = body.read_time_minutes ?? 5; - - // type/status (support your taxonomy) - const type = ((body as any).type ?? "labnote"); - const status = - ((body as any).status ?? ((body.published_at ?? "").trim() ? "published" : "draft")); - - // published date only when published - const published_at = - status === "published" - ? ((body.published_at ?? "").trim() || new Date().toISOString().slice(0, 10)) - : null; - - // summary/excerpt - const summary = (body as any).summary ?? null; - - const excerpt = - body.excerpt ?? - markdown - .replace(/```[\s\S]*?```/g, "") - .replace(/[#>*_`]/g, "") - .trim() - .slice(0, 220); - - // category: keep if you still use it, otherwise null - const category = body.category ?? null; - - // ✅ check existing by (slug, locale) - const existing = db - .prepare("SELECT id FROM lab_notes WHERE slug = ? AND locale = ?") - .get(slug, locale) as { id: string } | undefined; - - const noteId = existing?.id ?? crypto.randomUUID(); - - const tx = db.transaction(() => { - if (existing) { - db.prepare(` - UPDATE lab_notes - SET - title = ?, - excerpt = ?, - summary = ?, - content_html = ?, - department_id = ?, - dept = ?, - type = ?, - status = ?, - shadow_density = ?, - safer_landing = ?, - read_time_minutes = ?, - published_at = ?, - category = ?, - updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') - WHERE slug = ? AND locale = ? - `).run( - title, - excerpt, - summary, - html, // ✅ store HTML in content_html - department_id, - dept, - type, - status, - shadow_density, - safer_landing, - read_time_minutes, - published_at, - category, - slug, - locale - ); - - db.prepare("DELETE FROM lab_note_tags WHERE note_id = ?").run(noteId); - } else { - db.prepare(` - INSERT INTO lab_notes ( - id, group_id, slug, locale, - type, status, title, - category, excerpt, summary, content_html, - department_id, dept, shadow_density, safer_landing, - read_time_minutes, published_at, - created_at, updated_at - ) VALUES ( - ?, ?, ?, ?, - ?, ?, ?, - ?, ?, ?, ?, - ?, ?, ?, ?, - ?, ?, - strftime('%Y-%m-%dT%H:%M:%fZ','now'), - strftime('%Y-%m-%dT%H:%M:%fZ','now') - ) - `).run( - noteId, - noteId, // group_id defaulting to noteId keeps v2 happy - slug, - locale, - type, - status, - title, - category, - excerpt, - summary, - html, // ✅ store HTML - department_id, - dept, - shadow_density, - safer_landing, - read_time_minutes, - published_at - ); - } - - const insertTag = db.prepare( - "INSERT OR IGNORE INTO lab_note_tags (note_id, tag) VALUES (?, ?)" - ); - - for (const t of tags) { - const tag = String(t).trim(); - if (tag) insertTag.run(noteId, tag); - } - }); - try { - tx(); - return res.json({ - ok: true, - slug, - locale, - id: noteId, - action: existing ? "updated" : "created", - }); + const slug = String(req.params.slug ?? "").trim(); + const locale = normalizeLocale(req.query.locale); + + if (!slug) return res.status(400).json({ error: "slug is required" }); + + const row = db.prepare(` + SELECT + id, slug, locale, type, status, + title, subtitle, summary, excerpt, category, + department_id, dept, shadow_density, coherence_score, + safer_landing, read_time_minutes, + published_at, created_at, updated_at, + content_markdown + FROM v_lab_notes + WHERE slug = ? + AND locale = ? + AND status = 'published' + LIMIT 1 + `).get(slug, locale) as (LabNoteRecord & { content_markdown?: string }) | undefined; + + if (!row) return res.status(404).json({ error: "Not found" }); + + const tagRows = db + .prepare("SELECT tag FROM lab_note_tags WHERE note_id = ?") + .all(row.id) as TagResult[]; + + // Public output stays HTML-based if your UI expects that: + const html = marked.parse(String(row.content_markdown ?? "")) as string; + + // mapToLabNoteView currently expects content_html on the record. + const noteForMapper = { + ...(row as any), + content_html: html, + } as LabNoteRecord; + + return res.json(mapToLabNoteView(noteForMapper, tagRows.map((t) => t.tag))); } catch (e: any) { - return res.status(500).json({ ok: false, error: e?.message ?? "upsert failed" }); + console.error("GET /lab-notes/:slug failed:", e?.message); + if (res.headersSent) return; + return res.status(500).json({ error: e?.message ?? "unknown" }); } }); - + // --------------------------------------------------------------------------- + // Public Upsert — DISABLED + // This endpoint wrote v1 wide-row content_html directly, which desyncs the ledger. + // When re-enabled, it must write a new lab_note_revisions row + update pointers. + // --------------------------------------------------------------------------- + app.post("/lab-notes/upsert", (_req: Request, res: Response) => { + return res.status(503).json({ + error: "Upsert disabled (ledger-only). Use admin endpoints / revision writes instead.", + }); + }); } diff --git a/tests/adminTokens.routes.test.ts b/tests/adminTokens.routes.test.ts new file mode 100644 index 0000000..998a7a3 --- /dev/null +++ b/tests/adminTokens.routes.test.ts @@ -0,0 +1,107 @@ +import request from "supertest"; +import { createTestApp, api } from "./helpers/createTestApp.js"; + +describe("Admin token routes (with ADMIN_DEV_BYPASS)", () => { + const prevEnv = process.env; + + beforeEach(() => { + process.env = { ...prevEnv }; + process.env.NODE_ENV = "test"; + process.env.ADMIN_DEV_BYPASS = "true"; + process.env.TOKEN_PEPPER = "unit-test-pepper"; + delete process.env.API_PREFIX; + }); + + afterEach(() => { + process.env = prevEnv; + }); + + test("POST /admin/tokens mints a token and returns raw token", async () => { + const { app } = createTestApp(); + + const res = await request(app) + .post(api("/admin/tokens")) + .send({ label: "CI", scopes: ["notes:write"] }); + + expect([200, 201]).toContain(res.status); + + expect(res.body).toHaveProperty("ok", true); + expect(res.body).toHaveProperty("data"); + + expect(res.body.data).toHaveProperty("id"); + expect(res.body.data).toHaveProperty("token"); + + expect(typeof res.body.data.id).toBe("string"); + expect(typeof res.body.data.token).toBe("string"); + expect(res.body.data.token.startsWith("hpl_")).toBe(true); + }); + + test("GET /admin/tokens lists tokens and does not return raw token", async () => { + const { app } = createTestApp(); + + const minted = await request(app) + .post(api("/admin/tokens")) + .send({ label: "Agent", scopes: ["notes:read"] }); + + expect([200, 201]).toContain(minted.status); + + const mintedId = minted.body?.data?.id; + expect(typeof mintedId).toBe("string"); + + const list = await request(app).get(api("/admin/tokens")); + expect(list.status).toBe(200); + + expect(list.body).toHaveProperty("ok", true); + expect(Array.isArray(list.body.data)).toBe(true); + + const row = list.body.data.find((t: any) => t.id === mintedId); + expect(row).toBeTruthy(); + expect(row.label).toBe("Agent"); + + // never leak raw token in list + expect(row.token).toBeUndefined(); + + // scopes should be normalized onto the output rows + if ("scopes" in row) { + expect(Array.isArray(row.scopes)).toBe(true); + } + }); + + test("POST /admin/tokens/:id/revoke disables token", async () => { + const { app } = createTestApp(); + + const minted = await request(app) + .post(api("/admin/tokens")) + .send({ label: "RevokeMe", scopes: ["notes:read"] }); + + expect([200, 201]).toContain(minted.status); + + const mintedId = minted.body?.data?.id; + expect(typeof mintedId).toBe("string"); + + const revoke = await request(app).post(api(`/admin/tokens/${mintedId}/revoke`)); + expect(revoke.status).toBe(200); + expect(revoke.body).toHaveProperty("ok", true); + + const list = await request(app).get(api("/admin/tokens")); + expect(list.status).toBe(200); + expect(list.body).toHaveProperty("ok", true); + expect(Array.isArray(list.body.data)).toBe(true); + + const row = list.body.data.find((t: any) => t.id === mintedId); + expect(row).toBeTruthy(); + + if ("is_active" in row) expect(row.is_active).toBe(0); + }); + + test("POST /admin/tokens rejects missing label", async () => { + const { app } = createTestApp(); + + const res = await request(app) + .post(api("/admin/tokens")) + .send({ scopes: ["notes:write"] }); + + expect(res.status).toBe(400); + expect(res.body).toHaveProperty("ok", false); + }); +}); \ No newline at end of file diff --git a/tests/auth.tokens.test.ts b/tests/auth.tokens.test.ts new file mode 100644 index 0000000..7e8c022 --- /dev/null +++ b/tests/auth.tokens.test.ts @@ -0,0 +1,154 @@ +// tests/requireAdmin.http.test.ts +import {generateRawToken, hashToken, mintApiToken, verifyApiToken} from "../src/auth/tokens.js"; +import Database from "better-sqlite3"; + +function createDb() { + const db = new Database(":memory:"); + db.exec(` + CREATE TABLE api_tokens ( + id TEXT PRIMARY KEY, + label TEXT NOT NULL, + token_hash TEXT NOT NULL, + scopes_json TEXT NOT NULL DEFAULT '[]', + is_active INTEGER NOT NULL DEFAULT 1, + expires_at TEXT NULL, + created_by_user TEXT NULL, + last_used_at TEXT NULL, + created_at TEXT NOT NULL + ); + CREATE UNIQUE INDEX uq_api_tokens_hash ON api_tokens(token_hash); + `); + return db; +} + +describe("auth/tokens", () => { + const prevEnv = process.env; + + beforeEach(() => { + process.env = { ...prevEnv }; + process.env.NODE_ENV = "test"; + process.env.TOKEN_PEPPER = "unit-test-pepper"; + }); + + afterEach(() => { + process.env = prevEnv; + }); + + test("hashToken is stable for same input (with pepper)", () => { + const h1 = hashToken("hpl_test_abc"); + const h2 = hashToken("hpl_test_abc"); + expect(h1).toBe(h2); + }); + + test("generateRawToken uses test prefix outside production", () => { + process.env.NODE_ENV = "test"; + const t = generateRawToken(); + expect(t.startsWith("hpl_test_")).toBe(true); + }); + + test("generateRawToken uses live prefix in production", () => { + process.env.NODE_ENV = "production"; + process.env.TOKEN_PEPPER = "prod-pepper"; + const t = generateRawToken(); + expect(t.startsWith("hpl_live_")).toBe(true); + }); + + test("tokenPepper fails closed in production when missing", () => { + process.env.NODE_ENV = "production"; + delete process.env.TOKEN_PEPPER; + expect(() => hashToken("hpl_live_x")).toThrow(/TOKEN_PEPPER is not set/i); + }); + + test("mintApiToken inserts row and returns raw token once", () => { + const db = createDb(); + + const { token, id } = mintApiToken(db, { + label: "CI", + scopes: ["notes:write", "notes:publish"], + created_by_user: "ada", + }); + + expect(typeof token).toBe("string"); + expect(typeof id).toBe("string"); + expect(token.startsWith("hpl_test_")).toBe(true); + + const row = db + .prepare( + "SELECT id, label, token_hash, scopes_json, is_active FROM api_tokens WHERE id = ?" + ) + .get(id) as any; + + expect(row).toBeTruthy(); + expect(row.label).toBe("CI"); + expect(row.is_active).toBe(1); + expect(row.token_hash).toBe(hashToken(token)); + expect(JSON.parse(row.scopes_json)).toEqual(["notes:write", "notes:publish"]); + }); + + test("verifyApiToken returns null for unknown token", () => { + const db = createDb(); + const out = verifyApiToken(db, "hpl_test_not_real"); + expect(out).toBeNull(); + }); + + test("verifyApiToken returns null for inactive token", () => { + const db = createDb(); + + const { token, id } = mintApiToken(db, { label: "Inactive", scopes: [] }); + db.prepare("UPDATE api_tokens SET is_active = 0 WHERE id = ?").run(id); + + const out = verifyApiToken(db, token); + expect(out).toBeNull(); + }); + + test("verifyApiToken returns null for expired token", () => { + const db = createDb(); + + const { token, id } = mintApiToken(db, { label: "Expired", scopes: [] }); + db.prepare("UPDATE api_tokens SET expires_at = ? WHERE id = ?").run( + "2000-01-01T00:00:00.000Z", + id + ); + + const out = verifyApiToken(db, token); + expect(out).toBeNull(); + }); + + test("verifyApiToken returns scopes and touches last_used_at", () => { + const db = createDb(); + + const { token, id } = mintApiToken(db, { + label: "Agent", + scopes: ["notes:read"], + }); + + const before = db + .prepare("SELECT last_used_at FROM api_tokens WHERE id = ?") + .get(id) as any; + + expect(before.last_used_at).toBeNull(); + + const out = verifyApiToken(db, token); + expect(out).not.toBeNull(); + expect(out?.token.id).toBe(id); + expect(out?.scopes).toEqual(["notes:read"]); + + const after = db + .prepare("SELECT last_used_at FROM api_tokens WHERE id = ?") + .get(id) as any; + + expect(after.last_used_at).toBeTruthy(); + expect(Number.isFinite(Date.parse(after.last_used_at))).toBe(true); + }); + + test("verifyApiToken safely handles malformed scopes_json", () => { + const db = createDb(); + + const { token, id } = mintApiToken(db, { label: "Weird", scopes: ["ok"] }); + db.prepare("UPDATE api_tokens SET scopes_json = ? WHERE id = ?").run("not-json", id); + + const out = verifyApiToken(db, token); + expect(out).not.toBeNull(); + expect(out?.scopes).toEqual([]); + }); +}); diff --git a/tests/helpers/api.ts b/tests/helpers/api.ts index 01f7651..7315e95 100644 --- a/tests/helpers/api.ts +++ b/tests/helpers/api.ts @@ -1,4 +1,4 @@ // tests/helpers/api.ts export const API_PREFIX = process.env.API_PREFIX ?? ""; export const api = (path: string) => - `${API_PREFIX}${path.startsWith("/") ? path : `/${path}`}`; \ No newline at end of file + `${API_PREFIX}${path.startsWith("/") ? path : `/${path}`}`; diff --git a/tests/requireAdmin.http.test.ts b/tests/requireAdmin.http.test.ts new file mode 100644 index 0000000..cb9e02a --- /dev/null +++ b/tests/requireAdmin.http.test.ts @@ -0,0 +1,34 @@ +// tests/requireAdmin.http.test.ts +import request from "supertest"; +import { createTestApp, api } from "./helpers/createTestApp.js"; + +describe("requireAdmin (integration via routes)", () => { + const prevEnv = process.env; + + beforeEach(() => { + process.env = { ...prevEnv }; + process.env.NODE_ENV = "test"; + delete process.env.API_PREFIX; + // createTestApp sets ADMIN_GITHUB_USERS="ada" if missing + }); + + afterEach(() => { + process.env = prevEnv; + }); + + test("401 when bypass off and unauthenticated (admin route)", async () => { + delete process.env.ADMIN_DEV_BYPASS; // bypass OFF + const { app } = createTestApp(); + + const res = await request(app).get(api("/admin/notes")); + expect(res.status).toBe(401); + }); + + test("200 when bypass on (admin route)", async () => { + process.env.ADMIN_DEV_BYPASS = "true"; + const { app } = createTestApp(); + + const res = await request(app).get(api("/admin/notes")); + expect(res.status).toBe(200); + }); +});