@selvajs/selva 4.3.5 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/client/_app/immutable/assets/0.fkooa_RK.css +1 -0
- package/build/client/_app/immutable/assets/0.fkooa_RK.css.br +0 -0
- package/build/client/_app/immutable/assets/0.fkooa_RK.css.gz +0 -0
- package/build/client/_app/immutable/assets/rhino3dm.BI8ESrUA.wasm +0 -0
- package/build/client/_app/immutable/assets/rhino3dm.BI8ESrUA.wasm.br +0 -0
- package/build/client/_app/immutable/assets/rhino3dm.BI8ESrUA.wasm.gz +0 -0
- package/build/client/_app/immutable/chunks/-xEd7dwr.js +35 -0
- package/build/client/_app/immutable/chunks/-xEd7dwr.js.br +0 -0
- package/build/client/_app/immutable/chunks/-xEd7dwr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/13pdkq-i.js +1 -0
- package/build/client/_app/immutable/chunks/13pdkq-i.js.br +0 -0
- package/build/client/_app/immutable/chunks/13pdkq-i.js.gz +0 -0
- package/build/client/_app/immutable/chunks/2BnY_oab.js +1 -0
- package/build/client/_app/immutable/chunks/2BnY_oab.js.br +0 -0
- package/build/client/_app/immutable/chunks/2BnY_oab.js.gz +0 -0
- package/build/client/_app/immutable/chunks/4WJ3PVN5.js +1 -0
- package/build/client/_app/immutable/chunks/4WJ3PVN5.js.br +0 -0
- package/build/client/_app/immutable/chunks/4WJ3PVN5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/5h7wo9xN.js +1 -0
- package/build/client/_app/immutable/chunks/5h7wo9xN.js.br +0 -0
- package/build/client/_app/immutable/chunks/5h7wo9xN.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B3KcsPFR.js +1 -0
- package/build/client/_app/immutable/chunks/B3KcsPFR.js.br +0 -0
- package/build/client/_app/immutable/chunks/B3KcsPFR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B48NJnzU.js +1 -0
- package/build/client/_app/immutable/chunks/B48NJnzU.js.br +0 -0
- package/build/client/_app/immutable/chunks/B48NJnzU.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B5HD-6Ib.js +1 -0
- package/build/client/_app/immutable/chunks/B5HD-6Ib.js.br +0 -0
- package/build/client/_app/immutable/chunks/B5HD-6Ib.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BH9oGrlA.js +1 -0
- package/build/client/_app/immutable/chunks/BH9oGrlA.js.br +0 -0
- package/build/client/_app/immutable/chunks/BH9oGrlA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BHzbkLpq.js +1 -0
- package/build/client/_app/immutable/chunks/BHzbkLpq.js.br +0 -0
- package/build/client/_app/immutable/chunks/BHzbkLpq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BKZ_D8Qd.js +1 -0
- package/build/client/_app/immutable/chunks/BKZ_D8Qd.js.br +0 -0
- package/build/client/_app/immutable/chunks/BKZ_D8Qd.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BMNUAT2D.js +1 -0
- package/build/client/_app/immutable/chunks/BMNUAT2D.js.br +0 -0
- package/build/client/_app/immutable/chunks/BMNUAT2D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BNs1__IY.js +1 -0
- package/build/client/_app/immutable/chunks/BNs1__IY.js.br +0 -0
- package/build/client/_app/immutable/chunks/BNs1__IY.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BUqheMDA.js +1 -0
- package/build/client/_app/immutable/chunks/BUqheMDA.js.br +0 -0
- package/build/client/_app/immutable/chunks/BUqheMDA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Ba74WbdD.js +1 -0
- package/build/client/_app/immutable/chunks/Ba74WbdD.js.br +0 -0
- package/build/client/_app/immutable/chunks/Ba74WbdD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bex2dy3F.js +1 -0
- package/build/client/_app/immutable/chunks/Bex2dy3F.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bex2dy3F.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BfraRswf.js +1 -0
- package/build/client/_app/immutable/chunks/BfraRswf.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfraRswf.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BjB88iwt.js +1 -0
- package/build/client/_app/immutable/chunks/BjB88iwt.js.br +0 -0
- package/build/client/_app/immutable/chunks/BjB88iwt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BoqXamYe.js +1 -0
- package/build/client/_app/immutable/chunks/BoqXamYe.js.br +0 -0
- package/build/client/_app/immutable/chunks/BoqXamYe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BsZHa2Wi.js +1 -0
- package/build/client/_app/immutable/chunks/BsZHa2Wi.js.br +0 -0
- package/build/client/_app/immutable/chunks/BsZHa2Wi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C-j7zMFr.js +1 -0
- package/build/client/_app/immutable/chunks/C-j7zMFr.js.br +0 -0
- package/build/client/_app/immutable/chunks/C-j7zMFr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CBjt6cm5.js +1 -0
- package/build/client/_app/immutable/chunks/CBjt6cm5.js.br +1 -0
- package/build/client/_app/immutable/chunks/CBjt6cm5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CE8BcsFf.js +1 -0
- package/build/client/_app/immutable/chunks/CE8BcsFf.js.br +0 -0
- package/build/client/_app/immutable/chunks/CE8BcsFf.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CJPg7PqR.js +1 -0
- package/build/client/_app/immutable/chunks/CJPg7PqR.js.br +0 -0
- package/build/client/_app/immutable/chunks/CJPg7PqR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CO55fzTt.js +1 -0
- package/build/client/_app/immutable/chunks/CO55fzTt.js.br +0 -0
- package/build/client/_app/immutable/chunks/CO55fzTt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/COSBQK9i.js +1 -0
- package/build/client/_app/immutable/chunks/COSBQK9i.js.br +0 -0
- package/build/client/_app/immutable/chunks/COSBQK9i.js.gz +0 -0
- package/build/client/_app/immutable/chunks/COWz2et1.js +1 -0
- package/build/client/_app/immutable/chunks/COWz2et1.js.br +0 -0
- package/build/client/_app/immutable/chunks/COWz2et1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CQerFpzT.js +1 -0
- package/build/client/_app/immutable/chunks/CQerFpzT.js.br +0 -0
- package/build/client/_app/immutable/chunks/CQerFpzT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cauzi832.js +1 -0
- package/build/client/_app/immutable/chunks/Cauzi832.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cauzi832.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CcBbOJ-v.js +1 -0
- package/build/client/_app/immutable/chunks/CcBbOJ-v.js.br +2 -0
- package/build/client/_app/immutable/chunks/CcBbOJ-v.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Ch5nD1rA.js +1 -0
- package/build/client/_app/immutable/chunks/Ch5nD1rA.js.br +0 -0
- package/build/client/_app/immutable/chunks/Ch5nD1rA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Ckd6DpGW.js +1 -0
- package/build/client/_app/immutable/chunks/Ckd6DpGW.js.br +0 -0
- package/build/client/_app/immutable/chunks/Ckd6DpGW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cpd8PqU3.js +1 -0
- package/build/client/_app/immutable/chunks/Cpd8PqU3.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cpd8PqU3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CwEiVMBL.js +1 -0
- package/build/client/_app/immutable/chunks/CwEiVMBL.js.br +0 -0
- package/build/client/_app/immutable/chunks/CwEiVMBL.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D9MTzuwb.js +1 -0
- package/build/client/_app/immutable/chunks/D9MTzuwb.js.br +0 -0
- package/build/client/_app/immutable/chunks/D9MTzuwb.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D9Y6MiT8.js +9 -0
- package/build/client/_app/immutable/chunks/D9Y6MiT8.js.br +0 -0
- package/build/client/_app/immutable/chunks/D9Y6MiT8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DApsykQI.js +1 -0
- package/build/client/_app/immutable/chunks/DApsykQI.js.br +0 -0
- package/build/client/_app/immutable/chunks/DApsykQI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DC8WqQE0.js +1 -0
- package/build/client/_app/immutable/chunks/DC8WqQE0.js.br +0 -0
- package/build/client/_app/immutable/chunks/DC8WqQE0.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DCgyaAAq.js +1 -0
- package/build/client/_app/immutable/chunks/DCgyaAAq.js.br +0 -0
- package/build/client/_app/immutable/chunks/DCgyaAAq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DFnVpznB.js +1 -0
- package/build/client/_app/immutable/chunks/DFnVpznB.js.br +4 -0
- package/build/client/_app/immutable/chunks/DFnVpznB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DH_OLMrQ.js +1 -0
- package/build/client/_app/immutable/chunks/DH_OLMrQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/DH_OLMrQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DU8tQv0c.js +1 -0
- package/build/client/_app/immutable/chunks/DU8tQv0c.js.br +0 -0
- package/build/client/_app/immutable/chunks/DU8tQv0c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DUpVdSFf.js +1 -0
- package/build/client/_app/immutable/chunks/DUpVdSFf.js.br +0 -0
- package/build/client/_app/immutable/chunks/DUpVdSFf.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D_C_YIH8.js +1 -0
- package/build/client/_app/immutable/chunks/D_C_YIH8.js.br +0 -0
- package/build/client/_app/immutable/chunks/D_C_YIH8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Db5P7BwC.js +64 -0
- package/build/client/_app/immutable/chunks/Db5P7BwC.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db5P7BwC.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DfcDRMtE.js +1 -0
- package/build/client/_app/immutable/chunks/DfcDRMtE.js.br +0 -0
- package/build/client/_app/immutable/chunks/DfcDRMtE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DnURu9OT.js +1 -0
- package/build/client/_app/immutable/chunks/DnURu9OT.js.br +0 -0
- package/build/client/_app/immutable/chunks/DnURu9OT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DsuMOTav.js +2 -0
- package/build/client/_app/immutable/chunks/DsuMOTav.js.br +0 -0
- package/build/client/_app/immutable/chunks/DsuMOTav.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dtx1M6QG.js +1 -0
- package/build/client/_app/immutable/chunks/Dtx1M6QG.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dtx1M6QG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DuHtWI31.js +1 -0
- package/build/client/_app/immutable/chunks/DuHtWI31.js.br +0 -0
- package/build/client/_app/immutable/chunks/DuHtWI31.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dw6fzgfW.js +1 -0
- package/build/client/_app/immutable/chunks/Dw6fzgfW.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dw6fzgfW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DzNzLgOO.js +1 -0
- package/build/client/_app/immutable/chunks/DzNzLgOO.js.br +0 -0
- package/build/client/_app/immutable/chunks/DzNzLgOO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/E7F5LllG.js +1 -0
- package/build/client/_app/immutable/chunks/E7F5LllG.js.br +0 -0
- package/build/client/_app/immutable/chunks/E7F5LllG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/FdBsYR_f.js +1 -0
- package/build/client/_app/immutable/chunks/FdBsYR_f.js.br +0 -0
- package/build/client/_app/immutable/chunks/FdBsYR_f.js.gz +0 -0
- package/build/client/_app/immutable/chunks/NPsDidaV.js +1 -0
- package/build/client/_app/immutable/chunks/NPsDidaV.js.br +0 -0
- package/build/client/_app/immutable/chunks/NPsDidaV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/OVtUuDzp.js +1 -0
- package/build/client/_app/immutable/chunks/OVtUuDzp.js.br +0 -0
- package/build/client/_app/immutable/chunks/OVtUuDzp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Qf_Lg02h.js +1 -0
- package/build/client/_app/immutable/chunks/Qf_Lg02h.js.br +0 -0
- package/build/client/_app/immutable/chunks/Qf_Lg02h.js.gz +0 -0
- package/build/client/_app/immutable/chunks/RmXOMQTe.js +1 -0
- package/build/client/_app/immutable/chunks/RmXOMQTe.js.br +0 -0
- package/build/client/_app/immutable/chunks/RmXOMQTe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/U_alzAUv.js +3 -0
- package/build/client/_app/immutable/chunks/U_alzAUv.js.br +0 -0
- package/build/client/_app/immutable/chunks/U_alzAUv.js.gz +0 -0
- package/build/client/_app/immutable/chunks/X4d9Q9UP.js +1 -0
- package/build/client/_app/immutable/chunks/X4d9Q9UP.js.br +0 -0
- package/build/client/_app/immutable/chunks/X4d9Q9UP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/atM6maCg.js +1 -0
- package/build/client/_app/immutable/chunks/atM6maCg.js.br +0 -0
- package/build/client/_app/immutable/chunks/atM6maCg.js.gz +0 -0
- package/build/client/_app/immutable/chunks/qxoHibue.js +1 -0
- package/build/client/_app/immutable/chunks/qxoHibue.js.br +0 -0
- package/build/client/_app/immutable/chunks/qxoHibue.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DXOixdTQ.js +2 -0
- package/build/client/_app/immutable/entry/app.DXOixdTQ.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DXOixdTQ.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.lHsRDqJ_.js +1 -0
- package/build/client/_app/immutable/entry/start.lHsRDqJ_.js.br +2 -0
- package/build/client/_app/immutable/entry/start.lHsRDqJ_.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.B0UCbyvV.js +1 -0
- package/build/client/_app/immutable/nodes/0.B0UCbyvV.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.B0UCbyvV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Cgpx1yHy.js +1 -0
- package/build/client/_app/immutable/nodes/1.Cgpx1yHy.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Cgpx1yHy.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.Ct49OwC1.js +3 -0
- package/build/client/_app/immutable/nodes/10.Ct49OwC1.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.Ct49OwC1.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.BIaqG9xz.js +2 -0
- package/build/client/_app/immutable/nodes/11.BIaqG9xz.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.BIaqG9xz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/12.BqHQ_pQ5.js +1 -0
- package/build/client/_app/immutable/nodes/12.BqHQ_pQ5.js.br +0 -0
- package/build/client/_app/immutable/nodes/12.BqHQ_pQ5.js.gz +0 -0
- package/build/client/_app/immutable/nodes/13.ByWkldPP.js +2 -0
- package/build/client/_app/immutable/nodes/13.ByWkldPP.js.br +0 -0
- package/build/client/_app/immutable/nodes/13.ByWkldPP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/14.87fRSA-Y.js +2 -0
- package/build/client/_app/immutable/nodes/14.87fRSA-Y.js.br +0 -0
- package/build/client/_app/immutable/nodes/14.87fRSA-Y.js.gz +0 -0
- package/build/client/_app/immutable/nodes/15.B2tLGWDu.js +13 -0
- package/build/client/_app/immutable/nodes/15.B2tLGWDu.js.br +0 -0
- package/build/client/_app/immutable/nodes/15.B2tLGWDu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/16.DWG3CLUv.js +6 -0
- package/build/client/_app/immutable/nodes/16.DWG3CLUv.js.br +0 -0
- package/build/client/_app/immutable/nodes/16.DWG3CLUv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/17.CgiETa37.js +2 -0
- package/build/client/_app/immutable/nodes/17.CgiETa37.js.br +0 -0
- package/build/client/_app/immutable/nodes/17.CgiETa37.js.gz +0 -0
- package/build/client/_app/immutable/nodes/19.DO2ja5iW.js +2 -0
- package/build/client/_app/immutable/nodes/19.DO2ja5iW.js.br +0 -0
- package/build/client/_app/immutable/nodes/19.DO2ja5iW.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DIAYMP4_.js +1 -0
- package/build/client/_app/immutable/nodes/2.DIAYMP4_.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DIAYMP4_.js.gz +0 -0
- package/build/client/_app/immutable/nodes/20.DYgQYcr-.js +5021 -0
- package/build/client/_app/immutable/nodes/20.DYgQYcr-.js.br +0 -0
- package/build/client/_app/immutable/nodes/20.DYgQYcr-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/21.CTwryTPC.js +6 -0
- package/build/client/_app/immutable/nodes/21.CTwryTPC.js.br +0 -0
- package/build/client/_app/immutable/nodes/21.CTwryTPC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/23.uUN6jlSq.js +16 -0
- package/build/client/_app/immutable/nodes/23.uUN6jlSq.js.br +0 -0
- package/build/client/_app/immutable/nodes/23.uUN6jlSq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/24.cDDvPQLh.js +6 -0
- package/build/client/_app/immutable/nodes/24.cDDvPQLh.js.br +0 -0
- package/build/client/_app/immutable/nodes/24.cDDvPQLh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/25.DQ1u0u88.js +1 -0
- package/build/client/_app/immutable/nodes/25.DQ1u0u88.js.br +0 -0
- package/build/client/_app/immutable/nodes/25.DQ1u0u88.js.gz +0 -0
- package/build/client/_app/immutable/nodes/26.Ds_gf_O9.js +3 -0
- package/build/client/_app/immutable/nodes/26.Ds_gf_O9.js.br +0 -0
- package/build/client/_app/immutable/nodes/26.Ds_gf_O9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/27.lHQ_8tDM.js +4 -0
- package/build/client/_app/immutable/nodes/27.lHQ_8tDM.js.br +0 -0
- package/build/client/_app/immutable/nodes/27.lHQ_8tDM.js.gz +0 -0
- package/build/client/_app/immutable/nodes/28.DZO_WZjq.js +4 -0
- package/build/client/_app/immutable/nodes/28.DZO_WZjq.js.br +0 -0
- package/build/client/_app/immutable/nodes/28.DZO_WZjq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/29.BBkvtbbm.js +2 -0
- package/build/client/_app/immutable/nodes/29.BBkvtbbm.js.br +0 -0
- package/build/client/_app/immutable/nodes/29.BBkvtbbm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.Clu5JbAY.js +1 -0
- package/build/client/_app/immutable/nodes/3.Clu5JbAY.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Clu5JbAY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/30.CunrYTKW.js +2 -0
- package/build/client/_app/immutable/nodes/30.CunrYTKW.js.br +0 -0
- package/build/client/_app/immutable/nodes/30.CunrYTKW.js.gz +0 -0
- package/build/client/_app/immutable/nodes/31.CaWXI_qm.js +2 -0
- package/build/client/_app/immutable/nodes/31.CaWXI_qm.js.br +0 -0
- package/build/client/_app/immutable/nodes/31.CaWXI_qm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/32.Drmb2nxJ.js +3 -0
- package/build/client/_app/immutable/nodes/32.Drmb2nxJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/32.Drmb2nxJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.CwEyn2eq.js +1 -0
- package/build/client/_app/immutable/nodes/5.CwEyn2eq.js.br +1 -0
- package/build/client/_app/immutable/nodes/5.CwEyn2eq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.Cybg-4zL.js +1 -0
- package/build/client/_app/immutable/nodes/6.Cybg-4zL.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Cybg-4zL.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.U-nwvAMy.js +1 -0
- package/build/client/_app/immutable/nodes/7.U-nwvAMy.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.U-nwvAMy.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.Ds78IC3h.js +2 -0
- package/build/client/_app/immutable/nodes/8.Ds78IC3h.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Ds78IC3h.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.B4Dh5-cJ.js +1 -0
- package/build/client/_app/immutable/nodes/9.B4Dh5-cJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.B4Dh5-cJ.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/0-DhT7yxre.js +55 -0
- package/build/server/chunks/0-DhT7yxre.js.map +1 -0
- package/build/server/chunks/1-B3qAsEuf.js +9 -0
- package/build/server/chunks/1-B3qAsEuf.js.map +1 -0
- package/build/server/chunks/10-YMBFU3cy.js +222 -0
- package/build/server/chunks/10-YMBFU3cy.js.map +1 -0
- package/build/server/chunks/11-DEH3nbKm.js +59 -0
- package/build/server/chunks/11-DEH3nbKm.js.map +1 -0
- package/build/server/chunks/12-Bra8rAOu.js +52 -0
- package/build/server/chunks/12-Bra8rAOu.js.map +1 -0
- package/build/server/chunks/13-D_djeolg.js +57 -0
- package/build/server/chunks/13-D_djeolg.js.map +1 -0
- package/build/server/chunks/14-B1A-3AIw.js +100 -0
- package/build/server/chunks/14-B1A-3AIw.js.map +1 -0
- package/build/server/chunks/15-KF7YQWQR.js +45 -0
- package/build/server/chunks/15-KF7YQWQR.js.map +1 -0
- package/build/server/chunks/16-27Ug2Gl4.js +85 -0
- package/build/server/chunks/16-27Ug2Gl4.js.map +1 -0
- package/build/server/chunks/17-B9EWzdfY.js +9 -0
- package/build/server/chunks/17-B9EWzdfY.js.map +1 -0
- package/build/server/chunks/18-Lwe4TEuf.js +74 -0
- package/build/server/chunks/18-Lwe4TEuf.js.map +1 -0
- package/build/server/chunks/19-DFpWVk9Q.js +94 -0
- package/build/server/chunks/19-DFpWVk9Q.js.map +1 -0
- package/build/server/chunks/2-psSdZh8h.js +43 -0
- package/build/server/chunks/2-psSdZh8h.js.map +1 -0
- package/build/server/chunks/20-B3RVyzHq.js +218 -0
- package/build/server/chunks/20-B3RVyzHq.js.map +1 -0
- package/build/server/chunks/21-DjJlF-s8.js +83 -0
- package/build/server/chunks/21-DjJlF-s8.js.map +1 -0
- package/build/server/chunks/23-Cv-iXsup.js +137 -0
- package/build/server/chunks/23-Cv-iXsup.js.map +1 -0
- package/build/server/chunks/24-C9N_eRrr.js +145 -0
- package/build/server/chunks/24-C9N_eRrr.js.map +1 -0
- package/build/server/chunks/25-DL8VLuLg.js +51 -0
- package/build/server/chunks/25-DL8VLuLg.js.map +1 -0
- package/build/server/chunks/26-Dia3LwgX.js +9 -0
- package/build/server/chunks/26-Dia3LwgX.js.map +1 -0
- package/build/server/chunks/27-HUwDEbMb.js +57 -0
- package/build/server/chunks/27-HUwDEbMb.js.map +1 -0
- package/build/server/chunks/28-l4BuMkhA.js +69 -0
- package/build/server/chunks/28-l4BuMkhA.js.map +1 -0
- package/build/server/chunks/29-DefkuWIf.js +55 -0
- package/build/server/chunks/29-DefkuWIf.js.map +1 -0
- package/build/server/chunks/3-FhxHeIS3.js +9 -0
- package/build/server/chunks/3-FhxHeIS3.js.map +1 -0
- package/build/server/chunks/30-BC7V7mS7.js +57 -0
- package/build/server/chunks/30-BC7V7mS7.js.map +1 -0
- package/build/server/chunks/31-DJIq1bRM.js +42 -0
- package/build/server/chunks/31-DJIq1bRM.js.map +1 -0
- package/build/server/chunks/32-fvzIyQyk.js +34 -0
- package/build/server/chunks/32-fvzIyQyk.js.map +1 -0
- package/build/server/chunks/5-BSabZ7B0.js +32 -0
- package/build/server/chunks/5-BSabZ7B0.js.map +1 -0
- package/build/server/chunks/6-BDAMjeLc.js +40 -0
- package/build/server/chunks/6-BDAMjeLc.js.map +1 -0
- package/build/server/chunks/7-C5uuYI8Y.js +19 -0
- package/build/server/chunks/7-C5uuYI8Y.js.map +1 -0
- package/build/server/chunks/8-Yg3bHJY8.js +130 -0
- package/build/server/chunks/8-Yg3bHJY8.js.map +1 -0
- package/build/server/chunks/9-BOWShy6C.js +48 -0
- package/build/server/chunks/9-BOWShy6C.js.map +1 -0
- package/build/server/chunks/AppHeader-vae2Doza.js +271 -0
- package/build/server/chunks/AppHeader-vae2Doza.js.map +1 -0
- package/build/server/chunks/DefinitionCard-RTmYlhOH.js +268 -0
- package/build/server/chunks/DefinitionCard-RTmYlhOH.js.map +1 -0
- package/build/server/chunks/ErrorScreen-d8Z8hQ1U.js +131 -0
- package/build/server/chunks/ErrorScreen-d8Z8hQ1U.js.map +1 -0
- package/build/server/chunks/FilterableDropdown-0UzIjcXJ.js +61 -0
- package/build/server/chunks/FilterableDropdown-0UzIjcXJ.js.map +1 -0
- package/build/server/chunks/SideNav-QbExg2MG.js +87 -0
- package/build/server/chunks/SideNav-QbExg2MG.js.map +1 -0
- package/build/server/chunks/StatCard-CRzbit9Z.js +50 -0
- package/build/server/chunks/StatCard-CRzbit9Z.js.map +1 -0
- package/build/server/chunks/UserChip-BPLEgzMp.js +1220 -0
- package/build/server/chunks/UserChip-BPLEgzMp.js.map +1 -0
- package/build/server/chunks/_error.svelte-DCuOEylf.js +29 -0
- package/build/server/chunks/_error.svelte-DCuOEylf.js.map +1 -0
- package/build/server/chunks/_error.svelte-DKd7sQzn.js +27 -0
- package/build/server/chunks/_error.svelte-DKd7sQzn.js.map +1 -0
- package/build/server/chunks/_layout.svelte-D5GiNGr6.js +108 -0
- package/build/server/chunks/_layout.svelte-D5GiNGr6.js.map +1 -0
- package/build/server/chunks/_layout.svelte-VX4FX0S-.js +795 -0
- package/build/server/chunks/_layout.svelte-VX4FX0S-.js.map +1 -0
- package/build/server/chunks/_layout.svelte-_enyp5QE.js +45 -0
- package/build/server/chunks/_layout.svelte-_enyp5QE.js.map +1 -0
- package/build/server/chunks/_layout.svelte-aflz8r6I.js +155 -0
- package/build/server/chunks/_layout.svelte-aflz8r6I.js.map +1 -0
- package/build/server/chunks/_page.svelte-B8iLZ6Zi.js +119 -0
- package/build/server/chunks/_page.svelte-B8iLZ6Zi.js.map +1 -0
- package/build/server/chunks/_page.svelte-BDLD6pXj.js +11756 -0
- package/build/server/chunks/_page.svelte-BDLD6pXj.js.map +1 -0
- package/build/server/chunks/_page.svelte-BHM2cdfQ.js +119 -0
- package/build/server/chunks/_page.svelte-BHM2cdfQ.js.map +1 -0
- package/build/server/chunks/_page.svelte-BYPZuYIR.js +276 -0
- package/build/server/chunks/_page.svelte-BYPZuYIR.js.map +1 -0
- package/build/server/chunks/_page.svelte-Big1I6tL.js +274 -0
- package/build/server/chunks/_page.svelte-Big1I6tL.js.map +1 -0
- package/build/server/chunks/_page.svelte-Bqwz1thf.js +146 -0
- package/build/server/chunks/_page.svelte-Bqwz1thf.js.map +1 -0
- package/build/server/chunks/_page.svelte-By_vYfbZ.js +525 -0
- package/build/server/chunks/_page.svelte-By_vYfbZ.js.map +1 -0
- package/build/server/chunks/_page.svelte-CD9Uvy6Y.js +595 -0
- package/build/server/chunks/_page.svelte-CD9Uvy6Y.js.map +1 -0
- package/build/server/chunks/_page.svelte-CIO9nnXR.js +557 -0
- package/build/server/chunks/_page.svelte-CIO9nnXR.js.map +1 -0
- package/build/server/chunks/_page.svelte-CaoALwvK.js +453 -0
- package/build/server/chunks/_page.svelte-CaoALwvK.js.map +1 -0
- package/build/server/chunks/_page.svelte-CjvLz8T1.js +906 -0
- package/build/server/chunks/_page.svelte-CjvLz8T1.js.map +1 -0
- package/build/server/chunks/_page.svelte-DIn0gOqM.js +609 -0
- package/build/server/chunks/_page.svelte-DIn0gOqM.js.map +1 -0
- package/build/server/chunks/_page.svelte-DKPG7AbM.js +113 -0
- package/build/server/chunks/_page.svelte-DKPG7AbM.js.map +1 -0
- package/build/server/chunks/_page.svelte-DM3XizZi.js +3858 -0
- package/build/server/chunks/_page.svelte-DM3XizZi.js.map +1 -0
- package/build/server/chunks/_page.svelte-DamuIhBm.js +168 -0
- package/build/server/chunks/_page.svelte-DamuIhBm.js.map +1 -0
- package/build/server/chunks/_page.svelte-DcbxsxZM.js +330 -0
- package/build/server/chunks/_page.svelte-DcbxsxZM.js.map +1 -0
- package/build/server/chunks/_page.svelte-DcqwTr6T.js +250 -0
- package/build/server/chunks/_page.svelte-DcqwTr6T.js.map +1 -0
- package/build/server/chunks/_page.svelte-DtVw2EYT.js +56 -0
- package/build/server/chunks/_page.svelte-DtVw2EYT.js.map +1 -0
- package/build/server/chunks/_page.svelte-DyTTCbrh.js +83 -0
- package/build/server/chunks/_page.svelte-DyTTCbrh.js.map +1 -0
- package/build/server/chunks/_page.svelte-FDFRMnmB.js +333 -0
- package/build/server/chunks/_page.svelte-FDFRMnmB.js.map +1 -0
- package/build/server/chunks/_page.svelte-Ut97NZhy.js +714 -0
- package/build/server/chunks/_page.svelte-Ut97NZhy.js.map +1 -0
- package/build/server/chunks/_page.svelte-ZxjqrKH8.js +72 -0
- package/build/server/chunks/_page.svelte-ZxjqrKH8.js.map +1 -0
- package/build/server/chunks/_page.svelte-jwU-46Sj.js +301 -0
- package/build/server/chunks/_page.svelte-jwU-46Sj.js.map +1 -0
- package/build/server/chunks/_page.svelte-lAupF47l.js +424 -0
- package/build/server/chunks/_page.svelte-lAupF47l.js.map +1 -0
- package/build/server/chunks/_server.ts-13_Xvown.js +32 -0
- package/build/server/chunks/_server.ts-13_Xvown.js.map +1 -0
- package/build/server/chunks/_server.ts-6F3Ly0cY.js +88 -0
- package/build/server/chunks/_server.ts-6F3Ly0cY.js.map +1 -0
- package/build/server/chunks/_server.ts-AtxnIvMI.js +398 -0
- package/build/server/chunks/_server.ts-AtxnIvMI.js.map +1 -0
- package/build/server/chunks/_server.ts-B9uegfmw.js +75 -0
- package/build/server/chunks/_server.ts-B9uegfmw.js.map +1 -0
- package/build/server/chunks/_server.ts-BQetLy9N.js +39 -0
- package/build/server/chunks/_server.ts-BQetLy9N.js.map +1 -0
- package/build/server/chunks/_server.ts-BZH3tqYX.js +490 -0
- package/build/server/chunks/_server.ts-BZH3tqYX.js.map +1 -0
- package/build/server/chunks/_server.ts-Be15h83H.js +84 -0
- package/build/server/chunks/_server.ts-Be15h83H.js.map +1 -0
- package/build/server/chunks/_server.ts-BgVWq0qd.js +41 -0
- package/build/server/chunks/_server.ts-BgVWq0qd.js.map +1 -0
- package/build/server/chunks/_server.ts-Bke_K04J.js +105 -0
- package/build/server/chunks/_server.ts-Bke_K04J.js.map +1 -0
- package/build/server/chunks/_server.ts-BlVwuKrl.js +111 -0
- package/build/server/chunks/_server.ts-BlVwuKrl.js.map +1 -0
- package/build/server/chunks/_server.ts-BmjNVAmP.js +60 -0
- package/build/server/chunks/_server.ts-BmjNVAmP.js.map +1 -0
- package/build/server/chunks/_server.ts-BwMyXo0z.js +65 -0
- package/build/server/chunks/_server.ts-BwMyXo0z.js.map +1 -0
- package/build/server/chunks/_server.ts-CF65FKS6.js +63 -0
- package/build/server/chunks/_server.ts-CF65FKS6.js.map +1 -0
- package/build/server/chunks/_server.ts-CMHRHdCD.js +83 -0
- package/build/server/chunks/_server.ts-CMHRHdCD.js.map +1 -0
- package/build/server/chunks/_server.ts-Ca_OJJFC.js +39 -0
- package/build/server/chunks/_server.ts-Ca_OJJFC.js.map +1 -0
- package/build/server/chunks/_server.ts-Cc-srcRk.js +33 -0
- package/build/server/chunks/_server.ts-Cc-srcRk.js.map +1 -0
- package/build/server/chunks/_server.ts-Cg1WgIQn.js +31 -0
- package/build/server/chunks/_server.ts-Cg1WgIQn.js.map +1 -0
- package/build/server/chunks/_server.ts-Cg6LtEov.js +36 -0
- package/build/server/chunks/_server.ts-Cg6LtEov.js.map +1 -0
- package/build/server/chunks/_server.ts-CpsGzKQC.js +83 -0
- package/build/server/chunks/_server.ts-CpsGzKQC.js.map +1 -0
- package/build/server/chunks/_server.ts-D2Qb9pui.js +51 -0
- package/build/server/chunks/_server.ts-D2Qb9pui.js.map +1 -0
- package/build/server/chunks/_server.ts-DEBzt_W2.js +86 -0
- package/build/server/chunks/_server.ts-DEBzt_W2.js.map +1 -0
- package/build/server/chunks/_server.ts-DRlwVsIx.js +162 -0
- package/build/server/chunks/_server.ts-DRlwVsIx.js.map +1 -0
- package/build/server/chunks/_server.ts-DVOhTvBf.js +108 -0
- package/build/server/chunks/_server.ts-DVOhTvBf.js.map +1 -0
- package/build/server/chunks/_server.ts-DW-Oh4l9.js +31 -0
- package/build/server/chunks/_server.ts-DW-Oh4l9.js.map +1 -0
- package/build/server/chunks/_server.ts-DamGRKop.js +101 -0
- package/build/server/chunks/_server.ts-DamGRKop.js.map +1 -0
- package/build/server/chunks/_server.ts-DethCmgs.js +77 -0
- package/build/server/chunks/_server.ts-DethCmgs.js.map +1 -0
- package/build/server/chunks/_server.ts-DlN3WSBT.js +39 -0
- package/build/server/chunks/_server.ts-DlN3WSBT.js.map +1 -0
- package/build/server/chunks/_server.ts-DvBucq0r.js +40 -0
- package/build/server/chunks/_server.ts-DvBucq0r.js.map +1 -0
- package/build/server/chunks/_server.ts-Dz4bsmph.js +60 -0
- package/build/server/chunks/_server.ts-Dz4bsmph.js.map +1 -0
- package/build/server/chunks/_server.ts-DzA4EZf_.js +43 -0
- package/build/server/chunks/_server.ts-DzA4EZf_.js.map +1 -0
- package/build/server/chunks/_server.ts-L9PpKy16.js +41 -0
- package/build/server/chunks/_server.ts-L9PpKy16.js.map +1 -0
- package/build/server/chunks/_server.ts-Oi8Qzh0h.js +44 -0
- package/build/server/chunks/_server.ts-Oi8Qzh0h.js.map +1 -0
- package/build/server/chunks/_server.ts-PKbOblCf.js +96 -0
- package/build/server/chunks/_server.ts-PKbOblCf.js.map +1 -0
- package/build/server/chunks/_server.ts-XAkApigL.js +80 -0
- package/build/server/chunks/_server.ts-XAkApigL.js.map +1 -0
- package/build/server/chunks/_server.ts-bhjqtBoy.js +37 -0
- package/build/server/chunks/_server.ts-bhjqtBoy.js.map +1 -0
- package/build/server/chunks/_server.ts-cHSphefi.js +50 -0
- package/build/server/chunks/_server.ts-cHSphefi.js.map +1 -0
- package/build/server/chunks/_server.ts-d6YTPmkY.js +40 -0
- package/build/server/chunks/_server.ts-d6YTPmkY.js.map +1 -0
- package/build/server/chunks/_server.ts-kTCuqLDl.js +73 -0
- package/build/server/chunks/_server.ts-kTCuqLDl.js.map +1 -0
- package/build/server/chunks/_server.ts-kbr9HYg5.js +72 -0
- package/build/server/chunks/_server.ts-kbr9HYg5.js.map +1 -0
- package/build/server/chunks/access.server-CAPd1-c2.js +408 -0
- package/build/server/chunks/access.server-CAPd1-c2.js.map +1 -0
- package/build/server/chunks/activity-Dmfe6yjx.js +18 -0
- package/build/server/chunks/activity-Dmfe6yjx.js.map +1 -0
- package/build/server/chunks/alert-description-BNqu-OG5.js +59 -0
- package/build/server/chunks/alert-description-BNqu-OG5.js.map +1 -0
- package/build/server/chunks/api-errors-BhVEeWTV.js +101 -0
- package/build/server/chunks/api-errors-BhVEeWTV.js.map +1 -0
- package/build/server/chunks/arrow-left-KOVWDB5k.js +14 -0
- package/build/server/chunks/arrow-left-KOVWDB5k.js.map +1 -0
- package/build/server/chunks/arrow-right-D0bOY6-5.js +14 -0
- package/build/server/chunks/arrow-right-D0bOY6-5.js.map +1 -0
- package/build/server/chunks/auth-bootstrap.server-DKc9-3PR.js +99 -0
- package/build/server/chunks/auth-bootstrap.server-DKc9-3PR.js.map +1 -0
- package/build/server/chunks/badge-DIEbxzcl.js +50 -0
- package/build/server/chunks/badge-DIEbxzcl.js.map +1 -0
- package/build/server/chunks/bootHealth.server-IPXOSqqQ.js +58 -0
- package/build/server/chunks/bootHealth.server-IPXOSqqQ.js.map +1 -0
- package/build/server/chunks/building-2-DFvNRZpq.js +22 -0
- package/build/server/chunks/building-2-DFvNRZpq.js.map +1 -0
- package/build/server/chunks/button-DPLE4Wc8.js +73 -0
- package/build/server/chunks/button-DPLE4Wc8.js.map +1 -0
- package/build/server/chunks/card-content-BlxX1RCO.js +46 -0
- package/build/server/chunks/card-content-BlxX1RCO.js.map +1 -0
- package/build/server/chunks/card-description-Z3TpzjmK.js +26 -0
- package/build/server/chunks/card-description-Z3TpzjmK.js.map +1 -0
- package/build/server/chunks/card-title-CdR5nQ-b.js +46 -0
- package/build/server/chunks/card-title-CdR5nQ-b.js.map +1 -0
- package/build/server/chunks/check-382jBCB9.js +11 -0
- package/build/server/chunks/check-382jBCB9.js.map +1 -0
- package/build/server/chunks/chevron-down-D5-hWbt-.js +11 -0
- package/build/server/chunks/chevron-down-D5-hWbt-.js.map +1 -0
- package/build/server/chunks/chevron-right-BqxmmxHL.js +11 -0
- package/build/server/chunks/chevron-right-BqxmmxHL.js.map +1 -0
- package/build/server/chunks/chevron-up-StZ3cnZ8.js +11 -0
- package/build/server/chunks/chevron-up-StZ3cnZ8.js.map +1 -0
- package/build/server/chunks/circle-DgRVKHBX.js +11 -0
- package/build/server/chunks/circle-DgRVKHBX.js.map +1 -0
- package/build/server/chunks/circle-alert-DVGjYRbM.js +18 -0
- package/build/server/chunks/circle-alert-DVGjYRbM.js.map +1 -0
- package/build/server/chunks/computeLimits-DPO6NbLd.js +27 -0
- package/build/server/chunks/computeLimits-DPO6NbLd.js.map +1 -0
- package/build/server/chunks/context-BJnJL8ES.js +141 -0
- package/build/server/chunks/context-BJnJL8ES.js.map +1 -0
- package/build/server/chunks/copy-ifB2TUP_.js +29 -0
- package/build/server/chunks/copy-ifB2TUP_.js.map +1 -0
- package/build/server/chunks/dialog-description-DJ0KQ8Zk.js +523 -0
- package/build/server/chunks/dialog-description-DJ0KQ8Zk.js.map +1 -0
- package/build/server/chunks/dialog-footer-CuTfgUTD.js +62 -0
- package/build/server/chunks/dialog-footer-CuTfgUTD.js.map +1 -0
- package/build/server/chunks/folder-kanban-pqeOGmPr.js +21 -0
- package/build/server/chunks/folder-kanban-pqeOGmPr.js.map +1 -0
- package/build/server/chunks/folder-open-Cs60gLjJ.js +18 -0
- package/build/server/chunks/folder-open-Cs60gLjJ.js.map +1 -0
- package/build/server/chunks/folders-u0VAZ6yv.js +24 -0
- package/build/server/chunks/folders-u0VAZ6yv.js.map +1 -0
- package/build/server/chunks/footerContext.svelte-DM5rvLUk.js +531 -0
- package/build/server/chunks/footerContext.svelte-DM5rvLUk.js.map +1 -0
- package/build/server/chunks/hooks.server-BEbVZKxO.js +225 -0
- package/build/server/chunks/hooks.server-BEbVZKxO.js.map +1 -0
- package/build/server/chunks/index2-DDc7DI9Y.js +430 -0
- package/build/server/chunks/index2-DDc7DI9Y.js.map +1 -0
- package/build/server/chunks/index3-BVw88ujT.js +4246 -0
- package/build/server/chunks/index3-BVw88ujT.js.map +1 -0
- package/build/server/chunks/index4-CCteFD1i.js +541 -0
- package/build/server/chunks/index4-CCteFD1i.js.map +1 -0
- package/build/server/chunks/input-BEmCftCF.js +53 -0
- package/build/server/chunks/input-BEmCftCF.js.map +1 -0
- package/build/server/chunks/label-D8oB1018.js +112 -0
- package/build/server/chunks/label-D8oB1018.js.map +1 -0
- package/build/server/chunks/link-2-D3b2vEpL.js +15 -0
- package/build/server/chunks/link-2-D3b2vEpL.js.map +1 -0
- package/build/server/chunks/mail-DZDbJkFt.js +17 -0
- package/build/server/chunks/mail-DZDbJkFt.js.map +1 -0
- package/build/server/chunks/minus-B7toCaYG.js +20 -0
- package/build/server/chunks/minus-B7toCaYG.js.map +1 -0
- package/build/server/chunks/noop-BGJY5rVY.js +270 -0
- package/build/server/chunks/noop-BGJY5rVY.js.map +1 -0
- package/build/server/chunks/package-MTlbeZ-4.js +29 -0
- package/build/server/chunks/package-MTlbeZ-4.js.map +1 -0
- package/build/server/chunks/permissions-compat.server-G174xsh6.js +19 -0
- package/build/server/chunks/permissions-compat.server-G174xsh6.js.map +1 -0
- package/build/server/chunks/permissions.server-zs4XAJCp.js +8 -0
- package/build/server/chunks/permissions.server-zs4XAJCp.js.map +1 -0
- package/build/server/chunks/plus-j_UY0EeU.js +11 -0
- package/build/server/chunks/plus-j_UY0EeU.js.map +1 -0
- package/build/server/chunks/providers.server-C3fQ7ZdV.js +4456 -0
- package/build/server/chunks/providers.server-C3fQ7ZdV.js.map +1 -0
- package/build/server/chunks/refresh-cw-B6mwsgBf.js +22 -0
- package/build/server/chunks/refresh-cw-B6mwsgBf.js.map +1 -0
- package/build/server/chunks/resolve.server-t1UmqtsG.js +50 -0
- package/build/server/chunks/resolve.server-t1UmqtsG.js.map +1 -0
- package/build/server/chunks/rotate-ccw-DChq5zhW.js +17 -0
- package/build/server/chunks/rotate-ccw-DChq5zhW.js.map +1 -0
- package/build/server/chunks/scroll-lock-NAQO_S1Y.js +2853 -0
- package/build/server/chunks/scroll-lock-NAQO_S1Y.js.map +1 -0
- package/build/server/chunks/scroll-text-Bi9gx_V4.js +21 -0
- package/build/server/chunks/scroll-text-Bi9gx_V4.js.map +1 -0
- package/build/server/chunks/server-LxtlHpuz.js +36 -0
- package/build/server/chunks/server-LxtlHpuz.js.map +1 -0
- package/build/server/chunks/settings-Cr5mYyBS.js +19 -0
- package/build/server/chunks/settings-Cr5mYyBS.js.map +1 -0
- package/build/server/chunks/shield-BSf8CVMT.js +18 -0
- package/build/server/chunks/shield-BSf8CVMT.js.map +1 -0
- package/build/server/chunks/shield-check-OpZRvDHN.js +19 -0
- package/build/server/chunks/shield-check-OpZRvDHN.js.map +1 -0
- package/build/server/chunks/star-KGWYAeYA.js +18 -0
- package/build/server/chunks/star-KGWYAeYA.js.map +1 -0
- package/build/server/chunks/textarea-DmyNBs20.js +559 -0
- package/build/server/chunks/textarea-DmyNBs20.js.map +1 -0
- package/build/server/chunks/trash-2-DmuR9br3.js +17 -0
- package/build/server/chunks/trash-2-DmuR9br3.js.map +1 -0
- package/build/server/chunks/triangle-alert-Dig9abwC.js +20 -0
- package/build/server/chunks/triangle-alert-Dig9abwC.js.map +1 -0
- package/build/server/chunks/user-plus-Jn4Z4v9g.js +16 -0
- package/build/server/chunks/user-plus-Jn4Z4v9g.js.map +1 -0
- package/build/server/chunks/users-BgwwwoIh.js +16 -0
- package/build/server/chunks/users-BgwwwoIh.js.map +1 -0
- package/build/server/chunks/utils2-BqfluBM6.js +50 -0
- package/build/server/chunks/utils2-BqfluBM6.js.map +1 -0
- package/build/server/chunks/utils3-Dv2HSd2-.js +80 -0
- package/build/server/chunks/utils3-Dv2HSd2-.js.map +1 -0
- package/build/server/chunks/x-B1q8Cpoy.js +14 -0
- package/build/server/chunks/x-B1q8Cpoy.js.map +1 -0
- package/build/server/index.js +2 -2
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +71 -71
- package/build/server/manifest.js.map +1 -1
- package/package.json +9 -8
- package/templates/.env.example +11 -0
- package/build/client/_app/immutable/assets/0.-MS0dTYb.css +0 -1
- package/build/client/_app/immutable/assets/0.-MS0dTYb.css.br +0 -0
- package/build/client/_app/immutable/assets/0.-MS0dTYb.css.gz +0 -0
- package/build/client/_app/immutable/chunks/3Ah3Y6N9.js +0 -1
- package/build/client/_app/immutable/chunks/3Ah3Y6N9.js.br +0 -0
- package/build/client/_app/immutable/chunks/3Ah3Y6N9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B-Kuf-9K.js +0 -1
- package/build/client/_app/immutable/chunks/B-Kuf-9K.js.br +0 -0
- package/build/client/_app/immutable/chunks/B-Kuf-9K.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B1z9if9B.js +0 -1
- package/build/client/_app/immutable/chunks/B1z9if9B.js.br +0 -0
- package/build/client/_app/immutable/chunks/B1z9if9B.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B21bldal.js +0 -1
- package/build/client/_app/immutable/chunks/B21bldal.js.br +0 -0
- package/build/client/_app/immutable/chunks/B21bldal.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B6_AR4oL.js +0 -1
- package/build/client/_app/immutable/chunks/B6_AR4oL.js.br +0 -0
- package/build/client/_app/immutable/chunks/B6_AR4oL.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BEFa4rcT.js +0 -1
- package/build/client/_app/immutable/chunks/BEFa4rcT.js.br +0 -4
- package/build/client/_app/immutable/chunks/BEFa4rcT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BGIAOk5X.js +0 -1
- package/build/client/_app/immutable/chunks/BGIAOk5X.js.br +0 -0
- package/build/client/_app/immutable/chunks/BGIAOk5X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BHhjUaik.js +0 -1
- package/build/client/_app/immutable/chunks/BHhjUaik.js.br +0 -0
- package/build/client/_app/immutable/chunks/BHhjUaik.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BIIXkPqf.js +0 -1
- package/build/client/_app/immutable/chunks/BIIXkPqf.js.br +0 -0
- package/build/client/_app/immutable/chunks/BIIXkPqf.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BLZ195WY.js +0 -1
- package/build/client/_app/immutable/chunks/BLZ195WY.js.br +0 -0
- package/build/client/_app/immutable/chunks/BLZ195WY.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BM8Ok6pJ.js +0 -1
- package/build/client/_app/immutable/chunks/BM8Ok6pJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/BM8Ok6pJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BOsr6E_d.js +0 -1
- package/build/client/_app/immutable/chunks/BOsr6E_d.js.br +0 -1
- package/build/client/_app/immutable/chunks/BOsr6E_d.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BX0gN9mF.js +0 -1
- package/build/client/_app/immutable/chunks/BX0gN9mF.js.br +0 -0
- package/build/client/_app/immutable/chunks/BX0gN9mF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BaMCSMGs.js +0 -1
- package/build/client/_app/immutable/chunks/BaMCSMGs.js.br +0 -0
- package/build/client/_app/immutable/chunks/BaMCSMGs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bg5E8RaK.js +0 -1
- package/build/client/_app/immutable/chunks/Bg5E8RaK.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bg5E8RaK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BihI7LuH.js +0 -1
- package/build/client/_app/immutable/chunks/BihI7LuH.js.br +0 -0
- package/build/client/_app/immutable/chunks/BihI7LuH.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bma65zV8.js +0 -1
- package/build/client/_app/immutable/chunks/Bma65zV8.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bma65zV8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BnzoF81W.js +0 -1
- package/build/client/_app/immutable/chunks/BnzoF81W.js.br +0 -1
- package/build/client/_app/immutable/chunks/BnzoF81W.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BpQqe39G.js +0 -1
- package/build/client/_app/immutable/chunks/BpQqe39G.js.br +0 -0
- package/build/client/_app/immutable/chunks/BpQqe39G.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BsKtJj5h.js +0 -1
- package/build/client/_app/immutable/chunks/BsKtJj5h.js.br +0 -0
- package/build/client/_app/immutable/chunks/BsKtJj5h.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BtugGMaj.js +0 -1
- package/build/client/_app/immutable/chunks/BtugGMaj.js.br +0 -0
- package/build/client/_app/immutable/chunks/BtugGMaj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Buy2icyP.js +0 -9
- package/build/client/_app/immutable/chunks/Buy2icyP.js.br +0 -0
- package/build/client/_app/immutable/chunks/Buy2icyP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bwn5lHBl.js +0 -1
- package/build/client/_app/immutable/chunks/Bwn5lHBl.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bwn5lHBl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C3WxOOC9.js +0 -1
- package/build/client/_app/immutable/chunks/C3WxOOC9.js.br +0 -0
- package/build/client/_app/immutable/chunks/C3WxOOC9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CDmA5ezP.js +0 -1
- package/build/client/_app/immutable/chunks/CDmA5ezP.js.br +0 -0
- package/build/client/_app/immutable/chunks/CDmA5ezP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CEKDr1yG.js +0 -1
- package/build/client/_app/immutable/chunks/CEKDr1yG.js.br +0 -0
- package/build/client/_app/immutable/chunks/CEKDr1yG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CE_gxD9G.js +0 -1
- package/build/client/_app/immutable/chunks/CE_gxD9G.js.br +0 -0
- package/build/client/_app/immutable/chunks/CE_gxD9G.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CEmcKzrr.js +0 -2
- package/build/client/_app/immutable/chunks/CEmcKzrr.js.br +0 -0
- package/build/client/_app/immutable/chunks/CEmcKzrr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CGKZc1Kf.js +0 -1
- package/build/client/_app/immutable/chunks/CGKZc1Kf.js.br +0 -0
- package/build/client/_app/immutable/chunks/CGKZc1Kf.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CIHhLSqA.js +0 -1
- package/build/client/_app/immutable/chunks/CIHhLSqA.js.br +0 -0
- package/build/client/_app/immutable/chunks/CIHhLSqA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CNC_mCsM.js +0 -1
- package/build/client/_app/immutable/chunks/CNC_mCsM.js.br +0 -0
- package/build/client/_app/immutable/chunks/CNC_mCsM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CYyqq9B1.js +0 -1
- package/build/client/_app/immutable/chunks/CYyqq9B1.js.br +0 -0
- package/build/client/_app/immutable/chunks/CYyqq9B1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CeS0GhtF.js +0 -1
- package/build/client/_app/immutable/chunks/CeS0GhtF.js.br +0 -0
- package/build/client/_app/immutable/chunks/CeS0GhtF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CnXIg_dO.js +0 -1
- package/build/client/_app/immutable/chunks/CnXIg_dO.js.br +0 -0
- package/build/client/_app/immutable/chunks/CnXIg_dO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CqKeVIM6.js +0 -4108
- package/build/client/_app/immutable/chunks/CqKeVIM6.js.br +0 -0
- package/build/client/_app/immutable/chunks/CqKeVIM6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cqb2-zYs.js +0 -1
- package/build/client/_app/immutable/chunks/Cqb2-zYs.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cqb2-zYs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CumXBEVX.js +0 -1
- package/build/client/_app/immutable/chunks/CumXBEVX.js.br +0 -0
- package/build/client/_app/immutable/chunks/CumXBEVX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CzCTTv8Q.js +0 -1
- package/build/client/_app/immutable/chunks/CzCTTv8Q.js.br +0 -0
- package/build/client/_app/immutable/chunks/CzCTTv8Q.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-3pI9hp.js +0 -1
- package/build/client/_app/immutable/chunks/D-3pI9hp.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-3pI9hp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-Fls4-N.js +0 -1
- package/build/client/_app/immutable/chunks/D-Fls4-N.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-Fls4-N.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D1xkoVpc.js +0 -1
- package/build/client/_app/immutable/chunks/D1xkoVpc.js.br +0 -3
- package/build/client/_app/immutable/chunks/D1xkoVpc.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DRpLcVdK.js +0 -1
- package/build/client/_app/immutable/chunks/DRpLcVdK.js.br +0 -0
- package/build/client/_app/immutable/chunks/DRpLcVdK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DYfK6Plb.js +0 -1
- package/build/client/_app/immutable/chunks/DYfK6Plb.js.br +0 -1
- package/build/client/_app/immutable/chunks/DYfK6Plb.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DZM0bVcB.js +0 -1
- package/build/client/_app/immutable/chunks/DZM0bVcB.js.br +0 -0
- package/build/client/_app/immutable/chunks/DZM0bVcB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D_gPt7DB.js +0 -1
- package/build/client/_app/immutable/chunks/D_gPt7DB.js.br +0 -0
- package/build/client/_app/immutable/chunks/D_gPt7DB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DdEmEaOR.js +0 -1
- package/build/client/_app/immutable/chunks/DdEmEaOR.js.br +0 -0
- package/build/client/_app/immutable/chunks/DdEmEaOR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/De-Sjn1V.js +0 -1
- package/build/client/_app/immutable/chunks/De-Sjn1V.js.br +0 -0
- package/build/client/_app/immutable/chunks/De-Sjn1V.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DeMiewtM.js +0 -1
- package/build/client/_app/immutable/chunks/DeMiewtM.js.br +0 -0
- package/build/client/_app/immutable/chunks/DeMiewtM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DfbumCTS.js +0 -1
- package/build/client/_app/immutable/chunks/DfbumCTS.js.br +0 -0
- package/build/client/_app/immutable/chunks/DfbumCTS.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Diyh8RG-.js +0 -1
- package/build/client/_app/immutable/chunks/Diyh8RG-.js.br +0 -0
- package/build/client/_app/immutable/chunks/Diyh8RG-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkVLFJC2.js +0 -64
- package/build/client/_app/immutable/chunks/DkVLFJC2.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkVLFJC2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dl_x4ktn.js +0 -1
- package/build/client/_app/immutable/chunks/Dl_x4ktn.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dl_x4ktn.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dn_lJm0y.js +0 -1
- package/build/client/_app/immutable/chunks/Dn_lJm0y.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dn_lJm0y.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DoPqAbzy.js +0 -3
- package/build/client/_app/immutable/chunks/DoPqAbzy.js.br +0 -0
- package/build/client/_app/immutable/chunks/DoPqAbzy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DqK2lhM-.js +0 -1
- package/build/client/_app/immutable/chunks/DqK2lhM-.js.br +0 -0
- package/build/client/_app/immutable/chunks/DqK2lhM-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dr-qnmvi.js +0 -1
- package/build/client/_app/immutable/chunks/Dr-qnmvi.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dr-qnmvi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DtFVCiwY.js +0 -1
- package/build/client/_app/immutable/chunks/DtFVCiwY.js.br +0 -0
- package/build/client/_app/immutable/chunks/DtFVCiwY.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Q9Y439fs.js +0 -1
- package/build/client/_app/immutable/chunks/Q9Y439fs.js.br +0 -0
- package/build/client/_app/immutable/chunks/Q9Y439fs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Uv0-HWHq.js +0 -1
- package/build/client/_app/immutable/chunks/Uv0-HWHq.js.br +0 -0
- package/build/client/_app/immutable/chunks/Uv0-HWHq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/cFjfoNla.js +0 -1
- package/build/client/_app/immutable/chunks/cFjfoNla.js.br +0 -0
- package/build/client/_app/immutable/chunks/cFjfoNla.js.gz +0 -0
- package/build/client/_app/immutable/chunks/snZK0J5H.js +0 -1
- package/build/client/_app/immutable/chunks/snZK0J5H.js.br +0 -0
- package/build/client/_app/immutable/chunks/snZK0J5H.js.gz +0 -0
- package/build/client/_app/immutable/chunks/vyYztzrZ.js +0 -1
- package/build/client/_app/immutable/chunks/vyYztzrZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/vyYztzrZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/wRsUbPEe.js +0 -1
- package/build/client/_app/immutable/chunks/wRsUbPEe.js.br +0 -0
- package/build/client/_app/immutable/chunks/wRsUbPEe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/xQ1SsCxk.js +0 -1
- package/build/client/_app/immutable/chunks/xQ1SsCxk.js.br +0 -0
- package/build/client/_app/immutable/chunks/xQ1SsCxk.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.09tvh6Se.js +0 -2
- package/build/client/_app/immutable/entry/app.09tvh6Se.js.br +0 -0
- package/build/client/_app/immutable/entry/app.09tvh6Se.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CdsoeaJo.js +0 -1
- package/build/client/_app/immutable/entry/start.CdsoeaJo.js.br +0 -2
- package/build/client/_app/immutable/entry/start.CdsoeaJo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Dr3zOYh5.js +0 -9
- package/build/client/_app/immutable/nodes/0.Dr3zOYh5.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Dr3zOYh5.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BinGQMTR.js +0 -1
- package/build/client/_app/immutable/nodes/1.BinGQMTR.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BinGQMTR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.lZeHcKpq.js +0 -3
- package/build/client/_app/immutable/nodes/10.lZeHcKpq.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.lZeHcKpq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.B3nd7eqm.js +0 -2
- package/build/client/_app/immutable/nodes/11.B3nd7eqm.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.B3nd7eqm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/12.C3AQ8jKc.js +0 -1
- package/build/client/_app/immutable/nodes/12.C3AQ8jKc.js.br +0 -0
- package/build/client/_app/immutable/nodes/12.C3AQ8jKc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/13.BhyPMdJg.js +0 -2
- package/build/client/_app/immutable/nodes/13.BhyPMdJg.js.br +0 -0
- package/build/client/_app/immutable/nodes/13.BhyPMdJg.js.gz +0 -0
- package/build/client/_app/immutable/nodes/14.BCskMTTX.js +0 -2
- package/build/client/_app/immutable/nodes/14.BCskMTTX.js.br +0 -0
- package/build/client/_app/immutable/nodes/14.BCskMTTX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/15.DFduRu8X.js +0 -13
- package/build/client/_app/immutable/nodes/15.DFduRu8X.js.br +0 -0
- package/build/client/_app/immutable/nodes/15.DFduRu8X.js.gz +0 -0
- package/build/client/_app/immutable/nodes/16.k2eOyLft.js +0 -6
- package/build/client/_app/immutable/nodes/16.k2eOyLft.js.br +0 -0
- package/build/client/_app/immutable/nodes/16.k2eOyLft.js.gz +0 -0
- package/build/client/_app/immutable/nodes/17.UOq3rPnW.js +0 -2
- package/build/client/_app/immutable/nodes/17.UOq3rPnW.js.br +0 -0
- package/build/client/_app/immutable/nodes/17.UOq3rPnW.js.gz +0 -0
- package/build/client/_app/immutable/nodes/19.BfnNfjHd.js +0 -2
- package/build/client/_app/immutable/nodes/19.BfnNfjHd.js.br +0 -0
- package/build/client/_app/immutable/nodes/19.BfnNfjHd.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BVRyhmlu.js +0 -1
- package/build/client/_app/immutable/nodes/2.BVRyhmlu.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BVRyhmlu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/20.BwMu_6dK.js +0 -11
- package/build/client/_app/immutable/nodes/20.BwMu_6dK.js.br +0 -0
- package/build/client/_app/immutable/nodes/20.BwMu_6dK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/21.BlFRuNYB.js +0 -6
- package/build/client/_app/immutable/nodes/21.BlFRuNYB.js.br +0 -0
- package/build/client/_app/immutable/nodes/21.BlFRuNYB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/23.lhoyDlwS.js +0 -16
- package/build/client/_app/immutable/nodes/23.lhoyDlwS.js.br +0 -0
- package/build/client/_app/immutable/nodes/23.lhoyDlwS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/24.BHPSI0Zf.js +0 -6
- package/build/client/_app/immutable/nodes/24.BHPSI0Zf.js.br +0 -0
- package/build/client/_app/immutable/nodes/24.BHPSI0Zf.js.gz +0 -0
- package/build/client/_app/immutable/nodes/25.Boy7QQiW.js +0 -1
- package/build/client/_app/immutable/nodes/25.Boy7QQiW.js.br +0 -0
- package/build/client/_app/immutable/nodes/25.Boy7QQiW.js.gz +0 -0
- package/build/client/_app/immutable/nodes/26.JTwwV2O8.js +0 -3
- package/build/client/_app/immutable/nodes/26.JTwwV2O8.js.br +0 -0
- package/build/client/_app/immutable/nodes/26.JTwwV2O8.js.gz +0 -0
- package/build/client/_app/immutable/nodes/27.C1icpz7z.js +0 -4
- package/build/client/_app/immutable/nodes/27.C1icpz7z.js.br +0 -0
- package/build/client/_app/immutable/nodes/27.C1icpz7z.js.gz +0 -0
- package/build/client/_app/immutable/nodes/28.BJgC5M9K.js +0 -4
- package/build/client/_app/immutable/nodes/28.BJgC5M9K.js.br +0 -0
- package/build/client/_app/immutable/nodes/28.BJgC5M9K.js.gz +0 -0
- package/build/client/_app/immutable/nodes/29.D1pyBnax.js +0 -2
- package/build/client/_app/immutable/nodes/29.D1pyBnax.js.br +0 -0
- package/build/client/_app/immutable/nodes/29.D1pyBnax.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.CUC4G9Nh.js +0 -1
- package/build/client/_app/immutable/nodes/3.CUC4G9Nh.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CUC4G9Nh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/30.D0ffyv91.js +0 -2
- package/build/client/_app/immutable/nodes/30.D0ffyv91.js.br +0 -0
- package/build/client/_app/immutable/nodes/30.D0ffyv91.js.gz +0 -0
- package/build/client/_app/immutable/nodes/31.SiZTd523.js +0 -2
- package/build/client/_app/immutable/nodes/31.SiZTd523.js.br +0 -0
- package/build/client/_app/immutable/nodes/31.SiZTd523.js.gz +0 -0
- package/build/client/_app/immutable/nodes/32.9Ta_FSIA.js +0 -3
- package/build/client/_app/immutable/nodes/32.9Ta_FSIA.js.br +0 -0
- package/build/client/_app/immutable/nodes/32.9Ta_FSIA.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.Ch3zJDwF.js +0 -1
- package/build/client/_app/immutable/nodes/5.Ch3zJDwF.js.br +0 -2
- package/build/client/_app/immutable/nodes/5.Ch3zJDwF.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.BMl8cugB.js +0 -1
- package/build/client/_app/immutable/nodes/6.BMl8cugB.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BMl8cugB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.DKBYaDzG.js +0 -1
- package/build/client/_app/immutable/nodes/7.DKBYaDzG.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DKBYaDzG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.oeqwCIb7.js +0 -2
- package/build/client/_app/immutable/nodes/8.oeqwCIb7.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.oeqwCIb7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.JvVEXEt0.js +0 -1
- package/build/client/_app/immutable/nodes/9.JvVEXEt0.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.JvVEXEt0.js.gz +0 -0
- package/build/server/chunks/0-BGZb7GUr.js +0 -55
- package/build/server/chunks/0-BGZb7GUr.js.map +0 -1
- package/build/server/chunks/1-Bd5_Ktti.js +0 -9
- package/build/server/chunks/1-Bd5_Ktti.js.map +0 -1
- package/build/server/chunks/10-CoNR8vNf.js +0 -222
- package/build/server/chunks/10-CoNR8vNf.js.map +0 -1
- package/build/server/chunks/11-B8XsWabi.js +0 -59
- package/build/server/chunks/11-B8XsWabi.js.map +0 -1
- package/build/server/chunks/12-DVPsHnBz.js +0 -52
- package/build/server/chunks/12-DVPsHnBz.js.map +0 -1
- package/build/server/chunks/13-Dty8h7LU.js +0 -57
- package/build/server/chunks/13-Dty8h7LU.js.map +0 -1
- package/build/server/chunks/14-CcmFTDWx.js +0 -100
- package/build/server/chunks/14-CcmFTDWx.js.map +0 -1
- package/build/server/chunks/15-BCheyPjA.js +0 -45
- package/build/server/chunks/15-BCheyPjA.js.map +0 -1
- package/build/server/chunks/16-lCMAMYzW.js +0 -85
- package/build/server/chunks/16-lCMAMYzW.js.map +0 -1
- package/build/server/chunks/17-Buj0oOA7.js +0 -9
- package/build/server/chunks/17-Buj0oOA7.js.map +0 -1
- package/build/server/chunks/18-CM6a70dY.js +0 -74
- package/build/server/chunks/18-CM6a70dY.js.map +0 -1
- package/build/server/chunks/19-BHix_hIK.js +0 -94
- package/build/server/chunks/19-BHix_hIK.js.map +0 -1
- package/build/server/chunks/2-D8n52e0g.js +0 -43
- package/build/server/chunks/2-D8n52e0g.js.map +0 -1
- package/build/server/chunks/20-BZTRaWqF.js +0 -196
- package/build/server/chunks/20-BZTRaWqF.js.map +0 -1
- package/build/server/chunks/21-WEKNoho-.js +0 -83
- package/build/server/chunks/21-WEKNoho-.js.map +0 -1
- package/build/server/chunks/23-q81fbwUe.js +0 -137
- package/build/server/chunks/23-q81fbwUe.js.map +0 -1
- package/build/server/chunks/24-CLAHERAA.js +0 -145
- package/build/server/chunks/24-CLAHERAA.js.map +0 -1
- package/build/server/chunks/25-BzYQ8Zk-.js +0 -51
- package/build/server/chunks/25-BzYQ8Zk-.js.map +0 -1
- package/build/server/chunks/26-Bfl58Hw3.js +0 -9
- package/build/server/chunks/26-Bfl58Hw3.js.map +0 -1
- package/build/server/chunks/27-14n2oeiY.js +0 -57
- package/build/server/chunks/27-14n2oeiY.js.map +0 -1
- package/build/server/chunks/28-BYTHjf77.js +0 -69
- package/build/server/chunks/28-BYTHjf77.js.map +0 -1
- package/build/server/chunks/29-Uu9LAkQF.js +0 -55
- package/build/server/chunks/29-Uu9LAkQF.js.map +0 -1
- package/build/server/chunks/3-Czbm8V7y.js +0 -9
- package/build/server/chunks/3-Czbm8V7y.js.map +0 -1
- package/build/server/chunks/30-BBBNNzbB.js +0 -57
- package/build/server/chunks/30-BBBNNzbB.js.map +0 -1
- package/build/server/chunks/31-CQ-8s0Lw.js +0 -42
- package/build/server/chunks/31-CQ-8s0Lw.js.map +0 -1
- package/build/server/chunks/32-BmsICS9-.js +0 -34
- package/build/server/chunks/32-BmsICS9-.js.map +0 -1
- package/build/server/chunks/5-DHnM6DGt.js +0 -32
- package/build/server/chunks/5-DHnM6DGt.js.map +0 -1
- package/build/server/chunks/6-C4Vpgjrj.js +0 -40
- package/build/server/chunks/6-C4Vpgjrj.js.map +0 -1
- package/build/server/chunks/7-ZQ9mLTB2.js +0 -19
- package/build/server/chunks/7-ZQ9mLTB2.js.map +0 -1
- package/build/server/chunks/8-BS2o-Nbt.js +0 -130
- package/build/server/chunks/8-BS2o-Nbt.js.map +0 -1
- package/build/server/chunks/9-Cjc4QJfo.js +0 -48
- package/build/server/chunks/9-Cjc4QJfo.js.map +0 -1
- package/build/server/chunks/AppHeader-6ioAYhJf.js +0 -271
- package/build/server/chunks/AppHeader-6ioAYhJf.js.map +0 -1
- package/build/server/chunks/DefinitionCard-O1k8h5Pi.js +0 -258
- package/build/server/chunks/DefinitionCard-O1k8h5Pi.js.map +0 -1
- package/build/server/chunks/ErrorScreen-SHK14bo6.js +0 -131
- package/build/server/chunks/ErrorScreen-SHK14bo6.js.map +0 -1
- package/build/server/chunks/FilterableDropdown-W9AjyS0M.js +0 -61
- package/build/server/chunks/FilterableDropdown-W9AjyS0M.js.map +0 -1
- package/build/server/chunks/Icon-DbkwAOkZ.js +0 -76
- package/build/server/chunks/Icon-DbkwAOkZ.js.map +0 -1
- package/build/server/chunks/Icon2-DbkwAOkZ.js +0 -76
- package/build/server/chunks/Icon2-DbkwAOkZ.js.map +0 -1
- package/build/server/chunks/StatCard-Cj4i7_IY.js +0 -50
- package/build/server/chunks/StatCard-Cj4i7_IY.js.map +0 -1
- package/build/server/chunks/UserChip-fy4ucYa9.js +0 -1241
- package/build/server/chunks/UserChip-fy4ucYa9.js.map +0 -1
- package/build/server/chunks/_error.svelte-B_dLYaey.js +0 -30
- package/build/server/chunks/_error.svelte-B_dLYaey.js.map +0 -1
- package/build/server/chunks/_error.svelte-D_qATeW2.js +0 -28
- package/build/server/chunks/_error.svelte-D_qATeW2.js.map +0 -1
- package/build/server/chunks/_layout.svelte-BNCiY_LE.js +0 -107
- package/build/server/chunks/_layout.svelte-BNCiY_LE.js.map +0 -1
- package/build/server/chunks/_layout.svelte-BR1971uC.js +0 -154
- package/build/server/chunks/_layout.svelte-BR1971uC.js.map +0 -1
- package/build/server/chunks/_layout.svelte-CHTfYN7Z.js +0 -44
- package/build/server/chunks/_layout.svelte-CHTfYN7Z.js.map +0 -1
- package/build/server/chunks/_layout.svelte-DtDfOje_.js +0 -1042
- package/build/server/chunks/_layout.svelte-DtDfOje_.js.map +0 -1
- package/build/server/chunks/_page.svelte-2a3XlI01.js +0 -120
- package/build/server/chunks/_page.svelte-2a3XlI01.js.map +0 -1
- package/build/server/chunks/_page.svelte-6m8-zq9_.js +0 -10755
- package/build/server/chunks/_page.svelte-6m8-zq9_.js.map +0 -1
- package/build/server/chunks/_page.svelte-B-BjG1Bg.js +0 -332
- package/build/server/chunks/_page.svelte-B-BjG1Bg.js.map +0 -1
- package/build/server/chunks/_page.svelte-B2tNMNfb.js +0 -454
- package/build/server/chunks/_page.svelte-B2tNMNfb.js.map +0 -1
- package/build/server/chunks/_page.svelte-BEuBbeea.js +0 -920
- package/build/server/chunks/_page.svelte-BEuBbeea.js.map +0 -1
- package/build/server/chunks/_page.svelte-BLjq8C2p.js +0 -611
- package/build/server/chunks/_page.svelte-BLjq8C2p.js.map +0 -1
- package/build/server/chunks/_page.svelte-BX7fFjm7.js +0 -251
- package/build/server/chunks/_page.svelte-BX7fFjm7.js.map +0 -1
- package/build/server/chunks/_page.svelte-BXQInprQ.js +0 -275
- package/build/server/chunks/_page.svelte-BXQInprQ.js.map +0 -1
- package/build/server/chunks/_page.svelte-Bc3Ab2ud.js +0 -169
- package/build/server/chunks/_page.svelte-Bc3Ab2ud.js.map +0 -1
- package/build/server/chunks/_page.svelte-BziMQdom.js +0 -114
- package/build/server/chunks/_page.svelte-BziMQdom.js.map +0 -1
- package/build/server/chunks/_page.svelte-CYxHCZCC.js +0 -84
- package/build/server/chunks/_page.svelte-CYxHCZCC.js.map +0 -1
- package/build/server/chunks/_page.svelte-CckrYvJ6.js +0 -147
- package/build/server/chunks/_page.svelte-CckrYvJ6.js.map +0 -1
- package/build/server/chunks/_page.svelte-Cf8MfOXQ.js +0 -596
- package/build/server/chunks/_page.svelte-Cf8MfOXQ.js.map +0 -1
- package/build/server/chunks/_page.svelte-CxuFmBkK.js +0 -302
- package/build/server/chunks/_page.svelte-CxuFmBkK.js.map +0 -1
- package/build/server/chunks/_page.svelte-DphuJQee.js +0 -331
- package/build/server/chunks/_page.svelte-DphuJQee.js.map +0 -1
- package/build/server/chunks/_page.svelte-Dun4nEWl.js +0 -277
- package/build/server/chunks/_page.svelte-Dun4nEWl.js.map +0 -1
- package/build/server/chunks/_page.svelte-ELmid_qG.js +0 -120
- package/build/server/chunks/_page.svelte-ELmid_qG.js.map +0 -1
- package/build/server/chunks/_page.svelte-HTC9yaHJ.js +0 -71
- package/build/server/chunks/_page.svelte-HTC9yaHJ.js.map +0 -1
- package/build/server/chunks/_page.svelte-UNbcR5Wq.js +0 -715
- package/build/server/chunks/_page.svelte-UNbcR5Wq.js.map +0 -1
- package/build/server/chunks/_page.svelte-W41FvlvN.js +0 -530
- package/build/server/chunks/_page.svelte-W41FvlvN.js.map +0 -1
- package/build/server/chunks/_page.svelte-XLccLxWu.js +0 -558
- package/build/server/chunks/_page.svelte-XLccLxWu.js.map +0 -1
- package/build/server/chunks/_page.svelte-YVHQEj69.js +0 -3899
- package/build/server/chunks/_page.svelte-YVHQEj69.js.map +0 -1
- package/build/server/chunks/_page.svelte-pEdM2bKS.js +0 -57
- package/build/server/chunks/_page.svelte-pEdM2bKS.js.map +0 -1
- package/build/server/chunks/_page.svelte-tQDqmoYu.js +0 -425
- package/build/server/chunks/_page.svelte-tQDqmoYu.js.map +0 -1
- package/build/server/chunks/_server.ts-A3y5hBxw.js +0 -40
- package/build/server/chunks/_server.ts-A3y5hBxw.js.map +0 -1
- package/build/server/chunks/_server.ts-B5jlr2iF.js +0 -72
- package/build/server/chunks/_server.ts-B5jlr2iF.js.map +0 -1
- package/build/server/chunks/_server.ts-BLhNqGtF.js +0 -39
- package/build/server/chunks/_server.ts-BLhNqGtF.js.map +0 -1
- package/build/server/chunks/_server.ts-BON6qGm5.js +0 -77
- package/build/server/chunks/_server.ts-BON6qGm5.js.map +0 -1
- package/build/server/chunks/_server.ts-BO_GwwIE.js +0 -43
- package/build/server/chunks/_server.ts-BO_GwwIE.js.map +0 -1
- package/build/server/chunks/_server.ts-BUw5lGMN.js +0 -111
- package/build/server/chunks/_server.ts-BUw5lGMN.js.map +0 -1
- package/build/server/chunks/_server.ts-Bhj8Vx5l.js +0 -108
- package/build/server/chunks/_server.ts-Bhj8Vx5l.js.map +0 -1
- package/build/server/chunks/_server.ts-BqlGKKaE.js +0 -50
- package/build/server/chunks/_server.ts-BqlGKKaE.js.map +0 -1
- package/build/server/chunks/_server.ts-C-MiDKCm.js +0 -31
- package/build/server/chunks/_server.ts-C-MiDKCm.js.map +0 -1
- package/build/server/chunks/_server.ts-C05I9RVA.js +0 -40
- package/build/server/chunks/_server.ts-C05I9RVA.js.map +0 -1
- package/build/server/chunks/_server.ts-C4BZG5kA.js +0 -32
- package/build/server/chunks/_server.ts-C4BZG5kA.js.map +0 -1
- package/build/server/chunks/_server.ts-CEE9G3nv.js +0 -80
- package/build/server/chunks/_server.ts-CEE9G3nv.js.map +0 -1
- package/build/server/chunks/_server.ts-CJ37R1sx.js +0 -37
- package/build/server/chunks/_server.ts-CJ37R1sx.js.map +0 -1
- package/build/server/chunks/_server.ts-CJ5P0_cD.js +0 -75
- package/build/server/chunks/_server.ts-CJ5P0_cD.js.map +0 -1
- package/build/server/chunks/_server.ts-Ca-ZnaSe.js +0 -51
- package/build/server/chunks/_server.ts-Ca-ZnaSe.js.map +0 -1
- package/build/server/chunks/_server.ts-CewoOx5S.js +0 -60
- package/build/server/chunks/_server.ts-CewoOx5S.js.map +0 -1
- package/build/server/chunks/_server.ts-ChhV0tlQ.js +0 -86
- package/build/server/chunks/_server.ts-ChhV0tlQ.js.map +0 -1
- package/build/server/chunks/_server.ts-Ck5KHS5f.js +0 -36
- package/build/server/chunks/_server.ts-Ck5KHS5f.js.map +0 -1
- package/build/server/chunks/_server.ts-ClymRXMH.js +0 -44
- package/build/server/chunks/_server.ts-ClymRXMH.js.map +0 -1
- package/build/server/chunks/_server.ts-CsWWoLDJ.js +0 -41
- package/build/server/chunks/_server.ts-CsWWoLDJ.js.map +0 -1
- package/build/server/chunks/_server.ts-CtJaazcE.js +0 -83
- package/build/server/chunks/_server.ts-CtJaazcE.js.map +0 -1
- package/build/server/chunks/_server.ts-CyjVVRiT.js +0 -84
- package/build/server/chunks/_server.ts-CyjVVRiT.js.map +0 -1
- package/build/server/chunks/_server.ts-D0PjwFdv.js +0 -39
- package/build/server/chunks/_server.ts-D0PjwFdv.js.map +0 -1
- package/build/server/chunks/_server.ts-D7tgGcoB.js +0 -33
- package/build/server/chunks/_server.ts-D7tgGcoB.js.map +0 -1
- package/build/server/chunks/_server.ts-D9Jw0wGY.js +0 -398
- package/build/server/chunks/_server.ts-D9Jw0wGY.js.map +0 -1
- package/build/server/chunks/_server.ts-DBV9dA6A.js +0 -105
- package/build/server/chunks/_server.ts-DBV9dA6A.js.map +0 -1
- package/build/server/chunks/_server.ts-DLUA6_Lf.js +0 -327
- package/build/server/chunks/_server.ts-DLUA6_Lf.js.map +0 -1
- package/build/server/chunks/_server.ts-DM01ishT.js +0 -88
- package/build/server/chunks/_server.ts-DM01ishT.js.map +0 -1
- package/build/server/chunks/_server.ts-DNeVKrs8.js +0 -96
- package/build/server/chunks/_server.ts-DNeVKrs8.js.map +0 -1
- package/build/server/chunks/_server.ts-DWCKRNAG.js +0 -60
- package/build/server/chunks/_server.ts-DWCKRNAG.js.map +0 -1
- package/build/server/chunks/_server.ts-Db9C0y4L.js +0 -162
- package/build/server/chunks/_server.ts-Db9C0y4L.js.map +0 -1
- package/build/server/chunks/_server.ts-DlDf2qpE.js +0 -41
- package/build/server/chunks/_server.ts-DlDf2qpE.js.map +0 -1
- package/build/server/chunks/_server.ts-DmqbBNeI.js +0 -65
- package/build/server/chunks/_server.ts-DmqbBNeI.js.map +0 -1
- package/build/server/chunks/_server.ts-DwqLpa_k.js +0 -73
- package/build/server/chunks/_server.ts-DwqLpa_k.js.map +0 -1
- package/build/server/chunks/_server.ts-IVIAzAUs.js +0 -63
- package/build/server/chunks/_server.ts-IVIAzAUs.js.map +0 -1
- package/build/server/chunks/_server.ts-Xpgu2-ka.js +0 -101
- package/build/server/chunks/_server.ts-Xpgu2-ka.js.map +0 -1
- package/build/server/chunks/_server.ts-eVLE380Q.js +0 -31
- package/build/server/chunks/_server.ts-eVLE380Q.js.map +0 -1
- package/build/server/chunks/_server.ts-kighVn_d.js +0 -39
- package/build/server/chunks/_server.ts-kighVn_d.js.map +0 -1
- package/build/server/chunks/_server.ts-r2RVYvYA.js +0 -83
- package/build/server/chunks/_server.ts-r2RVYvYA.js.map +0 -1
- package/build/server/chunks/access.server-ByXvmWyF.js +0 -413
- package/build/server/chunks/access.server-ByXvmWyF.js.map +0 -1
- package/build/server/chunks/activity-BjtW_4T-.js +0 -18
- package/build/server/chunks/activity-BjtW_4T-.js.map +0 -1
- package/build/server/chunks/alert-description-CUK0afmb.js +0 -59
- package/build/server/chunks/alert-description-CUK0afmb.js.map +0 -1
- package/build/server/chunks/api-errors-C0SWfJj5.js +0 -101
- package/build/server/chunks/api-errors-C0SWfJj5.js.map +0 -1
- package/build/server/chunks/arrow-left-D0KqaESK.js +0 -14
- package/build/server/chunks/arrow-left-D0KqaESK.js.map +0 -1
- package/build/server/chunks/arrow-right-Bi1uWbBU.js +0 -14
- package/build/server/chunks/arrow-right-Bi1uWbBU.js.map +0 -1
- package/build/server/chunks/auth-bootstrap.server-zvPqZ6kt.js +0 -99
- package/build/server/chunks/auth-bootstrap.server-zvPqZ6kt.js.map +0 -1
- package/build/server/chunks/badge-Dslv369v.js +0 -50
- package/build/server/chunks/badge-Dslv369v.js.map +0 -1
- package/build/server/chunks/bootHealth.server-CLLVkbZZ.js +0 -58
- package/build/server/chunks/bootHealth.server-CLLVkbZZ.js.map +0 -1
- package/build/server/chunks/building-2-DJFEkZmC.js +0 -22
- package/build/server/chunks/building-2-DJFEkZmC.js.map +0 -1
- package/build/server/chunks/button-1GF48U_O.js +0 -73
- package/build/server/chunks/button-1GF48U_O.js.map +0 -1
- package/build/server/chunks/card-content-CLRedGcu.js +0 -46
- package/build/server/chunks/card-content-CLRedGcu.js.map +0 -1
- package/build/server/chunks/card-description-Cvz9Nl_J.js +0 -26
- package/build/server/chunks/card-description-Cvz9Nl_J.js.map +0 -1
- package/build/server/chunks/card-title-f3nMy4Cc.js +0 -46
- package/build/server/chunks/card-title-f3nMy4Cc.js.map +0 -1
- package/build/server/chunks/chevron-down-Cqowxf45.js +0 -11
- package/build/server/chunks/chevron-down-Cqowxf45.js.map +0 -1
- package/build/server/chunks/chevron-down2-HiD5tdXf.js +0 -11
- package/build/server/chunks/chevron-down2-HiD5tdXf.js.map +0 -1
- package/build/server/chunks/chevron-right-BZ8x48EK.js +0 -11
- package/build/server/chunks/chevron-right-BZ8x48EK.js.map +0 -1
- package/build/server/chunks/circle-CeSIvmWj.js +0 -11
- package/build/server/chunks/circle-CeSIvmWj.js.map +0 -1
- package/build/server/chunks/circle-alert-DNsq-x3V.js +0 -18
- package/build/server/chunks/circle-alert-DNsq-x3V.js.map +0 -1
- package/build/server/chunks/circle-alert2-Doldk45G.js +0 -18
- package/build/server/chunks/circle-alert2-Doldk45G.js.map +0 -1
- package/build/server/chunks/computeLimits-5PaWFMYq.js +0 -27
- package/build/server/chunks/computeLimits-5PaWFMYq.js.map +0 -1
- package/build/server/chunks/context-DKDYb-He.js +0 -138
- package/build/server/chunks/context-DKDYb-He.js.map +0 -1
- package/build/server/chunks/copy-DbzeZFQN.js +0 -29
- package/build/server/chunks/copy-DbzeZFQN.js.map +0 -1
- package/build/server/chunks/dialog-description-8UnYiAMX.js +0 -523
- package/build/server/chunks/dialog-description-8UnYiAMX.js.map +0 -1
- package/build/server/chunks/dialog-footer-CqbKEEQ-.js +0 -36
- package/build/server/chunks/dialog-footer-CqbKEEQ-.js.map +0 -1
- package/build/server/chunks/folder-kanban-BzEQk9ZB.js +0 -21
- package/build/server/chunks/folder-kanban-BzEQk9ZB.js.map +0 -1
- package/build/server/chunks/folder-open-CUgA3M22.js +0 -18
- package/build/server/chunks/folder-open-CUgA3M22.js.map +0 -1
- package/build/server/chunks/folders-DPJPvoz3.js +0 -24
- package/build/server/chunks/folders-DPJPvoz3.js.map +0 -1
- package/build/server/chunks/footerContext.svelte-oP__zW9C.js +0 -495
- package/build/server/chunks/footerContext.svelte-oP__zW9C.js.map +0 -1
- package/build/server/chunks/gauge-Az8B3w7C.js +0 -87
- package/build/server/chunks/gauge-Az8B3w7C.js.map +0 -1
- package/build/server/chunks/hooks.server-CwDixHQc.js +0 -218
- package/build/server/chunks/hooks.server-CwDixHQc.js.map +0 -1
- package/build/server/chunks/index2-D4a590Kd.js +0 -438
- package/build/server/chunks/index2-D4a590Kd.js.map +0 -1
- package/build/server/chunks/index3-0Z1R1__W.js +0 -4388
- package/build/server/chunks/index3-0Z1R1__W.js.map +0 -1
- package/build/server/chunks/index4-DWNr0jIL.js +0 -541
- package/build/server/chunks/index4-DWNr0jIL.js.map +0 -1
- package/build/server/chunks/input-DGJ03-ia.js +0 -53
- package/build/server/chunks/input-DGJ03-ia.js.map +0 -1
- package/build/server/chunks/label-Df1UqzdF.js +0 -112
- package/build/server/chunks/label-Df1UqzdF.js.map +0 -1
- package/build/server/chunks/link-2-dxaDBYcR.js +0 -15
- package/build/server/chunks/link-2-dxaDBYcR.js.map +0 -1
- package/build/server/chunks/mail-CXBfO_C7.js +0 -17
- package/build/server/chunks/mail-CXBfO_C7.js.map +0 -1
- package/build/server/chunks/noop-iPXixleG.js +0 -138
- package/build/server/chunks/noop-iPXixleG.js.map +0 -1
- package/build/server/chunks/package-CpyjZQq6.js +0 -29
- package/build/server/chunks/package-CpyjZQq6.js.map +0 -1
- package/build/server/chunks/permissions-compat.server-BDkKxDeZ.js +0 -19
- package/build/server/chunks/permissions-compat.server-BDkKxDeZ.js.map +0 -1
- package/build/server/chunks/permissions.server-7AwAAkY9.js +0 -8
- package/build/server/chunks/permissions.server-7AwAAkY9.js.map +0 -1
- package/build/server/chunks/plus-CY_pTStn.js +0 -11
- package/build/server/chunks/plus-CY_pTStn.js.map +0 -1
- package/build/server/chunks/providers.server-wGknM6CR.js +0 -4584
- package/build/server/chunks/providers.server-wGknM6CR.js.map +0 -1
- package/build/server/chunks/refresh-cw-CiiW6Kvx.js +0 -22
- package/build/server/chunks/refresh-cw-CiiW6Kvx.js.map +0 -1
- package/build/server/chunks/resolve.server-CvfANFCn.js +0 -50
- package/build/server/chunks/resolve.server-CvfANFCn.js.map +0 -1
- package/build/server/chunks/rotate-ccw-1C53L417.js +0 -17
- package/build/server/chunks/rotate-ccw-1C53L417.js.map +0 -1
- package/build/server/chunks/scroll-lock-BIz_BXmd.js +0 -1547
- package/build/server/chunks/scroll-lock-BIz_BXmd.js.map +0 -1
- package/build/server/chunks/scroll-text-CxtFfkcE.js +0 -21
- package/build/server/chunks/scroll-text-CxtFfkcE.js.map +0 -1
- package/build/server/chunks/server-C3-deqCy.js +0 -36
- package/build/server/chunks/server-C3-deqCy.js.map +0 -1
- package/build/server/chunks/settings-NH5q_HtF.js +0 -19
- package/build/server/chunks/settings-NH5q_HtF.js.map +0 -1
- package/build/server/chunks/shield-CHAFruKV.js +0 -18
- package/build/server/chunks/shield-CHAFruKV.js.map +0 -1
- package/build/server/chunks/shield-check-06Iz62as.js +0 -19
- package/build/server/chunks/shield-check-06Iz62as.js.map +0 -1
- package/build/server/chunks/star-bmBg3IsG.js +0 -18
- package/build/server/chunks/star-bmBg3IsG.js.map +0 -1
- package/build/server/chunks/textarea-A5IYPwqe.js +0 -685
- package/build/server/chunks/textarea-A5IYPwqe.js.map +0 -1
- package/build/server/chunks/trash-2-swQcKEqX.js +0 -17
- package/build/server/chunks/trash-2-swQcKEqX.js.map +0 -1
- package/build/server/chunks/triangle-alert-DuaFagu6.js +0 -20
- package/build/server/chunks/triangle-alert-DuaFagu6.js.map +0 -1
- package/build/server/chunks/user-plus-CPEDH5dh.js +0 -16
- package/build/server/chunks/user-plus-CPEDH5dh.js.map +0 -1
- package/build/server/chunks/users-Bd7CKN4d.js +0 -16
- package/build/server/chunks/users-Bd7CKN4d.js.map +0 -1
- package/build/server/chunks/utils2-BsnrDRob.js +0 -56
- package/build/server/chunks/utils2-BsnrDRob.js.map +0 -1
- package/build/server/chunks/utils3-B05Dmz_H.js +0 -9
- package/build/server/chunks/utils3-B05Dmz_H.js.map +0 -1
- package/build/server/chunks/x-e5o-84bg.js +0 -14
- package/build/server/chunks/x-e5o-84bg.js.map +0 -1
|
@@ -0,0 +1,4456 @@
|
|
|
1
|
+
import { p as private_env } from './shared-server-C3WdcJCQ.js';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { D as DEFAULT_ORG_PERMISSIONS, S as SYSTEM_CONTEXT, j as hasPermission, P as PlatformPermissionSchema } from './context-BJnJL8ES.js';
|
|
7
|
+
import * as crypto from 'node:crypto';
|
|
8
|
+
import { createDecipheriv, randomBytes, createCipheriv, randomUUID, createHmac, timingSafeEqual } from 'node:crypto';
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import { createClient } from '@supabase/supabase-js';
|
|
11
|
+
|
|
12
|
+
function assertSafeKey(value, label) {
|
|
13
|
+
if (!value || !/^[A-Za-z0-9._-]+$/.test(value) || value === "." || value === "..") {
|
|
14
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(value)}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const definitionPaths = {
|
|
18
|
+
version: (guid, versionNumber, ext) => {
|
|
19
|
+
assertSafeKey(guid, "guid");
|
|
20
|
+
if (!Number.isInteger(versionNumber) || versionNumber < 1) {
|
|
21
|
+
throw new Error(`Unsafe versionNumber: ${versionNumber}`);
|
|
22
|
+
}
|
|
23
|
+
return `definitions/${guid}/versions/v${versionNumber}.${ext}`;
|
|
24
|
+
},
|
|
25
|
+
image: (guid) => {
|
|
26
|
+
assertSafeKey(guid, "guid");
|
|
27
|
+
return `definitions/${guid}/cover.webp`;
|
|
28
|
+
},
|
|
29
|
+
prefix: (guid) => {
|
|
30
|
+
assertSafeKey(guid, "guid");
|
|
31
|
+
return `definitions/${guid}/`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const IMAGE_MAX_WIDTH = 1200;
|
|
35
|
+
const IMAGE_WEBP_QUALITY = 85;
|
|
36
|
+
function isImageUpload(contentType, storagePath) {
|
|
37
|
+
if (contentType?.startsWith("image/")) return true;
|
|
38
|
+
return /\.(webp|png|jpe?g|gif|bmp|tif?f)$/i.test(storagePath);
|
|
39
|
+
}
|
|
40
|
+
function toWebpPath(storagePath) {
|
|
41
|
+
return storagePath.replace(/\.(png|jpe?g|gif|bmp|tif?f)$/i, ".webp");
|
|
42
|
+
}
|
|
43
|
+
async function transcodeImageIfNeeded(data, contentType, storagePath) {
|
|
44
|
+
if (!isImageUpload(contentType, storagePath)) {
|
|
45
|
+
return { data, contentType: contentType ?? "application/octet-stream", path: storagePath };
|
|
46
|
+
}
|
|
47
|
+
const sharp = await loadSharp();
|
|
48
|
+
const compressed = await sharp(Buffer.from(data)).resize({ width: IMAGE_MAX_WIDTH, withoutEnlargement: true }).webp({ quality: IMAGE_WEBP_QUALITY }).toBuffer();
|
|
49
|
+
return {
|
|
50
|
+
data: new Uint8Array(compressed),
|
|
51
|
+
contentType: "image/webp",
|
|
52
|
+
path: toWebpPath(storagePath)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
let cachedSharp = null;
|
|
56
|
+
async function loadSharp() {
|
|
57
|
+
if (cachedSharp) return cachedSharp;
|
|
58
|
+
try {
|
|
59
|
+
const mod = await import('sharp');
|
|
60
|
+
cachedSharp = mod.default ?? mod;
|
|
61
|
+
return cachedSharp;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Image transcoding requires `sharp` to be installed. Add it as a dependency in your provider package.",
|
|
65
|
+
{ cause: err }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function isPlatformServer(s) {
|
|
70
|
+
return s.scope === "platform";
|
|
71
|
+
}
|
|
72
|
+
function isOrgServer(s) {
|
|
73
|
+
return s.scope === "org";
|
|
74
|
+
}
|
|
75
|
+
function actorFrom(ctx) {
|
|
76
|
+
return ctx.userId || "system";
|
|
77
|
+
}
|
|
78
|
+
class NoopEventSink {
|
|
79
|
+
async emit(_event) {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
class NoopSolveMetricSink {
|
|
83
|
+
async record(_ctx, _metric) {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
87
|
+
function auditUpdate(ctx, fallbackActor) {
|
|
88
|
+
return {
|
|
89
|
+
updatedAt: nowIso(),
|
|
90
|
+
updatedBy: ctx.userId || fallbackActor || ""
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function auditSoftDelete(ctx, fallbackActor) {
|
|
94
|
+
const now = nowIso();
|
|
95
|
+
return {
|
|
96
|
+
deletedAt: now,
|
|
97
|
+
updatedAt: now,
|
|
98
|
+
updatedBy: ctx.userId || fallbackActor || ""
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
102
|
+
const MAX_PAGE_LIMIT = 200;
|
|
103
|
+
function isFlagEnabled(config, flag2) {
|
|
104
|
+
return Boolean(config.flags?.[flag2]);
|
|
105
|
+
}
|
|
106
|
+
function defineConfig(config) {
|
|
107
|
+
return config;
|
|
108
|
+
}
|
|
109
|
+
class ProviderError extends Error {
|
|
110
|
+
statusCode;
|
|
111
|
+
constructor(message, statusCode = 400) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "ProviderError";
|
|
114
|
+
this.statusCode = statusCode;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const DEFAULT_MAX_AGE_MS = 8 * 60 * 60 * 1e3;
|
|
118
|
+
function signHmacToken(secret, userId, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
119
|
+
const expiry = Date.now() + maxAgeMs;
|
|
120
|
+
const payload = Buffer.from(`${userId}:${expiry}`).toString("base64url");
|
|
121
|
+
const sig = createHmac("sha256", secret).update(payload).digest("base64url");
|
|
122
|
+
return `${payload}.${sig}`;
|
|
123
|
+
}
|
|
124
|
+
function verifyHmacToken(token, secret) {
|
|
125
|
+
const dot = token.lastIndexOf(".");
|
|
126
|
+
if (dot === -1) return { userId: "", valid: false };
|
|
127
|
+
const payload = token.slice(0, dot);
|
|
128
|
+
const sig = token.slice(dot + 1);
|
|
129
|
+
const expectedSig = createHmac("sha256", secret).update(payload).digest("base64url");
|
|
130
|
+
const a = Buffer.from(sig);
|
|
131
|
+
const b = Buffer.from(expectedSig);
|
|
132
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) return { userId: "", valid: false };
|
|
133
|
+
const decoded = Buffer.from(payload, "base64url").toString();
|
|
134
|
+
const colon = decoded.lastIndexOf(":");
|
|
135
|
+
if (colon === -1) return { userId: "", valid: false };
|
|
136
|
+
const userId = decoded.slice(0, colon);
|
|
137
|
+
const expiry = parseInt(decoded.slice(colon + 1), 10);
|
|
138
|
+
if (!Number.isFinite(expiry) || Date.now() >= expiry) return { userId: "", valid: false };
|
|
139
|
+
return { userId, valid: true };
|
|
140
|
+
}
|
|
141
|
+
async function readJsonFile(filePath, fallback) {
|
|
142
|
+
try {
|
|
143
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
144
|
+
return JSON.parse(raw);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err.code === "ENOENT") return fallback;
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function writeJsonFile(filePath, data) {
|
|
151
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
152
|
+
const tmp = `${filePath}.tmp`;
|
|
153
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, " "), "utf-8");
|
|
154
|
+
await fs.rename(tmp, filePath);
|
|
155
|
+
}
|
|
156
|
+
const LAST_LOGIN_DEBOUNCE_MS$1 = 6e4;
|
|
157
|
+
const PBKDF2_ITERATIONS = 1e5;
|
|
158
|
+
const PBKDF2_KEYLEN = 32;
|
|
159
|
+
const PBKDF2_DIGEST = "sha256";
|
|
160
|
+
async function hashPassword(password) {
|
|
161
|
+
const salt = crypto.randomBytes(16).toString("base64url");
|
|
162
|
+
const hash = await new Promise(
|
|
163
|
+
(resolve2, reject) => crypto.pbkdf2(
|
|
164
|
+
password,
|
|
165
|
+
salt,
|
|
166
|
+
PBKDF2_ITERATIONS,
|
|
167
|
+
PBKDF2_KEYLEN,
|
|
168
|
+
PBKDF2_DIGEST,
|
|
169
|
+
(err, key) => err ? reject(err) : resolve2(key)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
return `pbkdf2:${PBKDF2_DIGEST}:${PBKDF2_ITERATIONS}:${salt}:${hash.toString("base64url")}`;
|
|
173
|
+
}
|
|
174
|
+
async function verifyPasswordHash(password, storedHash) {
|
|
175
|
+
const parts = storedHash.split(":");
|
|
176
|
+
if (parts.length !== 5 || parts[0] !== "pbkdf2") return false;
|
|
177
|
+
const [, digest, iterStr, salt, expectedHashB64] = parts;
|
|
178
|
+
const iterations = parseInt(iterStr, 10);
|
|
179
|
+
if (!Number.isFinite(iterations) || iterations <= 0) return false;
|
|
180
|
+
const expected = Buffer.from(expectedHashB64, "base64url");
|
|
181
|
+
const actual = await new Promise(
|
|
182
|
+
(resolve2, reject) => crypto.pbkdf2(
|
|
183
|
+
password,
|
|
184
|
+
salt,
|
|
185
|
+
iterations,
|
|
186
|
+
PBKDF2_KEYLEN,
|
|
187
|
+
digest,
|
|
188
|
+
(err, key) => err ? reject(err) : resolve2(key)
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
if (actual.length !== expected.length) return false;
|
|
192
|
+
return crypto.timingSafeEqual(actual, expected);
|
|
193
|
+
}
|
|
194
|
+
const empty$5 = () => ({ users: [] });
|
|
195
|
+
function createLocalAuthUserStore(usersFilePath) {
|
|
196
|
+
return {
|
|
197
|
+
async findByEmail(email) {
|
|
198
|
+
const { users } = await readJsonFile(usersFilePath, empty$5());
|
|
199
|
+
return users.find((u) => u.email.toLowerCase() === email.toLowerCase()) ?? null;
|
|
200
|
+
},
|
|
201
|
+
async findById(id) {
|
|
202
|
+
const { users } = await readJsonFile(usersFilePath, empty$5());
|
|
203
|
+
return users.find((u) => u.id === id) ?? null;
|
|
204
|
+
},
|
|
205
|
+
async listUsers() {
|
|
206
|
+
const { users } = await readJsonFile(usersFilePath, empty$5());
|
|
207
|
+
return users.map(({ passwordHash: _ph, ...rest }) => rest);
|
|
208
|
+
},
|
|
209
|
+
async createUser(email, password) {
|
|
210
|
+
const file = await readJsonFile(usersFilePath, empty$5());
|
|
211
|
+
if (file.users.some((u) => u.email.toLowerCase() === email.toLowerCase())) {
|
|
212
|
+
throw new ProviderError(`User with email "${email}" already exists`, 409);
|
|
213
|
+
}
|
|
214
|
+
const user = {
|
|
215
|
+
id: randomUUID(),
|
|
216
|
+
email,
|
|
217
|
+
passwordHash: password !== null ? await hashPassword(password) : null,
|
|
218
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
};
|
|
220
|
+
file.users.push(user);
|
|
221
|
+
await writeJsonFile(usersFilePath, file);
|
|
222
|
+
return user;
|
|
223
|
+
},
|
|
224
|
+
async setDisabled(id, disabled) {
|
|
225
|
+
const file = await readJsonFile(usersFilePath, empty$5());
|
|
226
|
+
const user = file.users.find((u) => u.id === id);
|
|
227
|
+
if (!user) throw new ProviderError(`User "${id}" not found`, 404);
|
|
228
|
+
user.disabled = disabled;
|
|
229
|
+
await writeJsonFile(usersFilePath, file);
|
|
230
|
+
},
|
|
231
|
+
async touchLastLogin(id) {
|
|
232
|
+
const file = await readJsonFile(usersFilePath, empty$5());
|
|
233
|
+
const user = file.users.find((u) => u.id === id);
|
|
234
|
+
if (!user) return;
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
if (user.lastLoginAt) {
|
|
237
|
+
const prev = Date.parse(user.lastLoginAt);
|
|
238
|
+
if (Number.isFinite(prev) && now - prev < LAST_LOGIN_DEBOUNCE_MS$1) return;
|
|
239
|
+
}
|
|
240
|
+
user.lastLoginAt = new Date(now).toISOString();
|
|
241
|
+
await writeJsonFile(usersFilePath, file);
|
|
242
|
+
},
|
|
243
|
+
async deleteUser(id) {
|
|
244
|
+
const file = await readJsonFile(usersFilePath, empty$5());
|
|
245
|
+
const before = file.users.length;
|
|
246
|
+
file.users = file.users.filter((u) => u.id !== id);
|
|
247
|
+
if (file.users.length === before) throw new ProviderError(`User "${id}" not found`, 404);
|
|
248
|
+
await writeJsonFile(usersFilePath, file);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function paginate(items, opts) {
|
|
253
|
+
const limit = Math.min(Math.max(1, opts?.limit ?? DEFAULT_PAGE_LIMIT), MAX_PAGE_LIMIT);
|
|
254
|
+
const offset = opts?.cursor ? parseInt(opts.cursor, 10) || 0 : 0;
|
|
255
|
+
const slice = items.slice(offset, offset + limit);
|
|
256
|
+
const nextOffset = offset + slice.length;
|
|
257
|
+
return {
|
|
258
|
+
items: slice,
|
|
259
|
+
nextCursor: nextOffset < items.length ? String(nextOffset) : void 0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function applyOrder(items, opts, keyFn = (item, field) => item[field]) {
|
|
263
|
+
const field = opts?.orderBy ?? "createdAt";
|
|
264
|
+
const dir = opts?.orderDir ?? "desc";
|
|
265
|
+
const mul = dir === "asc" ? 1 : -1;
|
|
266
|
+
items.sort((a, b) => {
|
|
267
|
+
const av = keyFn(a, field) ?? "";
|
|
268
|
+
const bv = keyFn(b, field) ?? "";
|
|
269
|
+
if (av < bv) return -1 * mul;
|
|
270
|
+
if (av > bv) return 1 * mul;
|
|
271
|
+
return 0;
|
|
272
|
+
});
|
|
273
|
+
return items;
|
|
274
|
+
}
|
|
275
|
+
const SESSION_MAX_AGE_MS = 8 * 60 * 60 * 1e3;
|
|
276
|
+
function toAuthUser$1(u) {
|
|
277
|
+
return {
|
|
278
|
+
id: u.id,
|
|
279
|
+
email: u.email,
|
|
280
|
+
createdAt: u.createdAt,
|
|
281
|
+
lastLoginAt: u.lastLoginAt,
|
|
282
|
+
disabled: u.disabled
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
class LocalPasswordAuth {
|
|
286
|
+
constructor(users, mintToken) {
|
|
287
|
+
this.users = users;
|
|
288
|
+
this.mintToken = mintToken;
|
|
289
|
+
}
|
|
290
|
+
users;
|
|
291
|
+
mintToken;
|
|
292
|
+
async verifyLogin(email, password) {
|
|
293
|
+
if (!this.users) return { kind: "failed", reason: "invalid_credentials" };
|
|
294
|
+
const user = await this.users.findByEmail(email);
|
|
295
|
+
if (!user) return { kind: "failed", reason: "invalid_credentials" };
|
|
296
|
+
if (user.disabled) return { kind: "failed", reason: "disabled" };
|
|
297
|
+
if (!user.passwordHash || !await verifyPasswordHash(password, user.passwordHash)) {
|
|
298
|
+
return { kind: "failed", reason: "invalid_credentials" };
|
|
299
|
+
}
|
|
300
|
+
await this.users.touchLastLogin(user.id).catch(() => {
|
|
301
|
+
});
|
|
302
|
+
const auth = toAuthUser$1(user);
|
|
303
|
+
return { kind: "success", user: auth, sessionToken: this.mintToken(auth) };
|
|
304
|
+
}
|
|
305
|
+
async createUserWithPassword(email, password) {
|
|
306
|
+
if (!this.users) {
|
|
307
|
+
throw new ProviderError(
|
|
308
|
+
"createUserWithPassword requires a users.json backend (DATA_PATH)",
|
|
309
|
+
500
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return toAuthUser$1(await this.users.createUser(email, password));
|
|
313
|
+
}
|
|
314
|
+
async registerUser(email, password) {
|
|
315
|
+
if (!this.users) return null;
|
|
316
|
+
return toAuthUser$1(await this.users.createUser(email, password));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
class LocalAuthProvider {
|
|
320
|
+
hmacSecret;
|
|
321
|
+
users;
|
|
322
|
+
name = "Local";
|
|
323
|
+
passwordAuth;
|
|
324
|
+
constructor(config) {
|
|
325
|
+
this.hmacSecret = config.hmacSecret;
|
|
326
|
+
if (config.usersFilePath) {
|
|
327
|
+
this.users = createLocalAuthUserStore(config.usersFilePath);
|
|
328
|
+
}
|
|
329
|
+
this.passwordAuth = new LocalPasswordAuth(
|
|
330
|
+
this.users,
|
|
331
|
+
(user) => signHmacToken(this.hmacSecret, user.id, SESSION_MAX_AGE_MS)
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
static fromEnv(env) {
|
|
335
|
+
const hmacSecret = env.SELVA_HMAC_KEY;
|
|
336
|
+
if (!hmacSecret) throw new Error("Missing required env var: SELVA_HMAC_KEY");
|
|
337
|
+
return new LocalAuthProvider({
|
|
338
|
+
hmacSecret,
|
|
339
|
+
usersFilePath: env.DATA_PATH ? path.join(env.DATA_PATH, "auth-users.json") : void 0
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/** Verify an HMAC session token and return the live user record. */
|
|
343
|
+
async verifyToken(token) {
|
|
344
|
+
const { valid, userId } = verifyHmacToken(token, this.hmacSecret);
|
|
345
|
+
if (!valid) return null;
|
|
346
|
+
if (!this.users) return null;
|
|
347
|
+
const u = await this.users.findById(userId);
|
|
348
|
+
if (!u || u.disabled) return null;
|
|
349
|
+
await this.users.touchLastLogin(u.id).catch(() => {
|
|
350
|
+
});
|
|
351
|
+
return toAuthUser$1(u);
|
|
352
|
+
}
|
|
353
|
+
async touchLastLogin(id) {
|
|
354
|
+
if (!this.users) return;
|
|
355
|
+
await this.users.touchLastLogin(id);
|
|
356
|
+
}
|
|
357
|
+
async getUser(id) {
|
|
358
|
+
if (!this.users) return null;
|
|
359
|
+
const u = await this.users.findById(id);
|
|
360
|
+
return u ? toAuthUser$1(u) : null;
|
|
361
|
+
}
|
|
362
|
+
async listUsers(opts) {
|
|
363
|
+
if (!this.users) return null;
|
|
364
|
+
const users = await this.users.listUsers();
|
|
365
|
+
return paginate(users.map(toAuthUser$1), opts);
|
|
366
|
+
}
|
|
367
|
+
async deleteUser(id) {
|
|
368
|
+
if (!this.users) return "not_supported";
|
|
369
|
+
const target = await this.users.findById(id);
|
|
370
|
+
if (!target) return "not_found";
|
|
371
|
+
try {
|
|
372
|
+
await this.users.deleteUser(id);
|
|
373
|
+
return "ok";
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async disableUser(id) {
|
|
380
|
+
if (!this.users) return "not_supported";
|
|
381
|
+
const target = await this.users.findById(id);
|
|
382
|
+
if (!target) return "not_found";
|
|
383
|
+
try {
|
|
384
|
+
await this.users.setDisabled(id, true);
|
|
385
|
+
return "ok";
|
|
386
|
+
} catch (err) {
|
|
387
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
388
|
+
throw err;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const EMPTY$1 = { invites: [] };
|
|
393
|
+
class LocalInviteStore {
|
|
394
|
+
constructor(dataPath, events = new NoopEventSink()) {
|
|
395
|
+
this.events = events;
|
|
396
|
+
this.filePath = path.join(dataPath, "invites.json");
|
|
397
|
+
}
|
|
398
|
+
events;
|
|
399
|
+
filePath;
|
|
400
|
+
static fromEnv(env, events = new NoopEventSink()) {
|
|
401
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
402
|
+
return new LocalInviteStore(env.DATA_PATH, events);
|
|
403
|
+
}
|
|
404
|
+
async load() {
|
|
405
|
+
return readJsonFile(this.filePath, EMPTY$1);
|
|
406
|
+
}
|
|
407
|
+
async save(file) {
|
|
408
|
+
await writeJsonFile(this.filePath, file);
|
|
409
|
+
}
|
|
410
|
+
async create(ctx, invite) {
|
|
411
|
+
const file = await this.load();
|
|
412
|
+
file.invites.push(invite);
|
|
413
|
+
await this.save(file);
|
|
414
|
+
await this.events.emit({
|
|
415
|
+
type: "invite.created",
|
|
416
|
+
inviteId: invite.id,
|
|
417
|
+
orgId: invite.orgId,
|
|
418
|
+
email: invite.email,
|
|
419
|
+
actorId: actorFrom(ctx)
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async getByTokenHash(_ctx, tokenHash) {
|
|
423
|
+
const { invites } = await this.load();
|
|
424
|
+
const invite = invites.find((i) => i.tokenHash === tokenHash);
|
|
425
|
+
if (!invite) return null;
|
|
426
|
+
if (invite.acceptedAt) return null;
|
|
427
|
+
if (Date.parse(invite.expiresAt) <= Date.now()) return null;
|
|
428
|
+
return invite;
|
|
429
|
+
}
|
|
430
|
+
async listByOrg(_ctx, orgId, opts) {
|
|
431
|
+
const { invites } = await this.load();
|
|
432
|
+
const filtered = invites.filter((i) => i.orgId === orgId);
|
|
433
|
+
return paginate(applyOrder(filtered, opts), opts);
|
|
434
|
+
}
|
|
435
|
+
async markAccepted(ctx, id, userId) {
|
|
436
|
+
const file = await this.load();
|
|
437
|
+
const invite = file.invites.find((i) => i.id === id);
|
|
438
|
+
if (!invite || invite.acceptedAt) return;
|
|
439
|
+
invite.acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
440
|
+
invite.acceptedByUserId = userId;
|
|
441
|
+
await this.save(file);
|
|
442
|
+
await this.events.emit({
|
|
443
|
+
type: "invite.accepted",
|
|
444
|
+
inviteId: invite.id,
|
|
445
|
+
orgId: invite.orgId,
|
|
446
|
+
userId,
|
|
447
|
+
actorId: actorFrom(ctx)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async revoke(ctx, id) {
|
|
451
|
+
const file = await this.load();
|
|
452
|
+
const target = file.invites.find((i) => i.id === id && !i.acceptedAt);
|
|
453
|
+
if (!target) return;
|
|
454
|
+
file.invites = file.invites.filter((i) => i.id !== id || i.acceptedAt);
|
|
455
|
+
await this.save(file);
|
|
456
|
+
await this.events.emit({
|
|
457
|
+
type: "invite.revoked",
|
|
458
|
+
inviteId: id,
|
|
459
|
+
orgId: target.orgId,
|
|
460
|
+
actorId: actorFrom(ctx)
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async deleteByOrg(_ctx, orgId) {
|
|
464
|
+
const file = await this.load();
|
|
465
|
+
const before = file.invites.length;
|
|
466
|
+
file.invites = file.invites.filter((i) => i.orgId !== orgId);
|
|
467
|
+
if (file.invites.length === before) return;
|
|
468
|
+
await this.save(file);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const ALGO = "aes-256-gcm";
|
|
472
|
+
const IV_BYTES = 12;
|
|
473
|
+
const TAG_BYTES = 16;
|
|
474
|
+
const PREFIX = "enc:v1:";
|
|
475
|
+
function isEncryptedSecret(value) {
|
|
476
|
+
return value.startsWith(PREFIX);
|
|
477
|
+
}
|
|
478
|
+
function encryptSecret(plaintext, key) {
|
|
479
|
+
if (key.length !== 32) {
|
|
480
|
+
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
481
|
+
}
|
|
482
|
+
if (isEncryptedSecret(plaintext)) {
|
|
483
|
+
throw new Error("Refusing to encrypt a value that is already encrypted");
|
|
484
|
+
}
|
|
485
|
+
const iv = randomBytes(IV_BYTES);
|
|
486
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
487
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
488
|
+
const tag = cipher.getAuthTag();
|
|
489
|
+
return PREFIX + Buffer.concat([iv, tag, ciphertext]).toString("base64");
|
|
490
|
+
}
|
|
491
|
+
function decryptSecret(envelope, key) {
|
|
492
|
+
if (key.length !== 32) {
|
|
493
|
+
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
494
|
+
}
|
|
495
|
+
if (!isEncryptedSecret(envelope)) {
|
|
496
|
+
throw new Error("Value is not an encrypted secret envelope");
|
|
497
|
+
}
|
|
498
|
+
const buf = Buffer.from(envelope.slice(PREFIX.length), "base64");
|
|
499
|
+
const iv = buf.subarray(0, IV_BYTES);
|
|
500
|
+
const tag = buf.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
|
|
501
|
+
const ciphertext = buf.subarray(IV_BYTES + TAG_BYTES);
|
|
502
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
503
|
+
decipher.setAuthTag(tag);
|
|
504
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
505
|
+
}
|
|
506
|
+
function decodeSecretKey(raw) {
|
|
507
|
+
if (/^[0-9a-fA-F]{64}$/.test(raw)) return Buffer.from(raw, "hex");
|
|
508
|
+
const buf = Buffer.from(raw, "base64");
|
|
509
|
+
if (buf.length === 32) return buf;
|
|
510
|
+
throw new Error(
|
|
511
|
+
`SELVA_AT_REST_KEY must be 32 bytes encoded as 64-char hex or base64. Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const EMPTY = { servers: [], orgDefaults: {} };
|
|
515
|
+
class LocalComputeServerStore {
|
|
516
|
+
constructor(configFilePath, secretKey) {
|
|
517
|
+
this.configFilePath = configFilePath;
|
|
518
|
+
this.secretKey = secretKey;
|
|
519
|
+
}
|
|
520
|
+
configFilePath;
|
|
521
|
+
secretKey;
|
|
522
|
+
static fromEnv(env) {
|
|
523
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
524
|
+
if (!env.SELVA_AT_REST_KEY) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Missing required env var: SELVA_AT_REST_KEY (32-byte hex or base64). Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
return new LocalComputeServerStore(
|
|
530
|
+
path.join(env.DATA_PATH, "compute.config.json"),
|
|
531
|
+
decodeSecretKey(env.SELVA_AT_REST_KEY)
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
async readAll() {
|
|
535
|
+
const raw = await readJsonFile(this.configFilePath, EMPTY);
|
|
536
|
+
return {
|
|
537
|
+
servers: raw.servers ?? [],
|
|
538
|
+
defaultServerId: raw.defaultServerId,
|
|
539
|
+
orgDefaults: raw.orgDefaults ?? {}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Per-row tolerant decrypt. A row whose ciphertext can't be authenticated
|
|
544
|
+
* under the current `SELVA_AT_REST_KEY` is returned with `apiKey: undefined`
|
|
545
|
+
* and a warning logged once. The page that loaded the config keeps
|
|
546
|
+
* rendering; solves against that server will fail later when Rhino.Compute
|
|
547
|
+
* rejects the missing key.
|
|
548
|
+
*
|
|
549
|
+
* Boot-time `verifySecrets()` is the strict counterpart — call that from
|
|
550
|
+
* the app entrypoint to refuse to start when this state is detected.
|
|
551
|
+
*
|
|
552
|
+
* Plaintext-on-disk is still hard-fail. That state is never produced by
|
|
553
|
+
* the store itself (every write goes through `encryptApiKeys`), so seeing
|
|
554
|
+
* it means someone hand-edited the file with a real secret in plaintext —
|
|
555
|
+
* which is a security issue we should surface loudly, not paper over.
|
|
556
|
+
*/
|
|
557
|
+
decryptApiKeys(servers) {
|
|
558
|
+
return servers.map((s) => {
|
|
559
|
+
if (!s.apiKey) return s;
|
|
560
|
+
if (!isEncryptedSecret(s.apiKey)) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`compute.config.json contains an unencrypted apiKey for server "${s.label}" (${s.id}). Re-enter the key via /admin/compute so it is stored encrypted.`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
return { ...s, apiKey: decryptSecret(s.apiKey, this.secretKey) };
|
|
567
|
+
} catch (cause) {
|
|
568
|
+
console.warn(
|
|
569
|
+
`[selva] Could not decrypt apiKey for compute server "${s.label}" (${s.id}). The stored ciphertext does not match the current SELVA_AT_REST_KEY. This server will be returned without an apiKey; solves against it will fail. Re-enter the key via /admin/compute, or restore the original SELVA_AT_REST_KEY. See docs/Troubleshooting.md.`,
|
|
570
|
+
cause
|
|
571
|
+
);
|
|
572
|
+
return { ...s, apiKey: void 0 };
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Boot-time integrity check. Reads every server row and attempts to
|
|
578
|
+
* decrypt each encrypted `apiKey`. Returns a structured report — does NOT
|
|
579
|
+
* throw. The caller decides what to do (refuse boot, log + degrade, etc.).
|
|
580
|
+
*
|
|
581
|
+
* Use this from app startup (`hooks.server.ts`) so a key mismatch fails
|
|
582
|
+
* loudly at deploy time instead of as a blank page when a user first hits
|
|
583
|
+
* a route that loads compute config.
|
|
584
|
+
*/
|
|
585
|
+
async verifySecrets() {
|
|
586
|
+
const all = await this.readAll();
|
|
587
|
+
const failures = [];
|
|
588
|
+
let plaintextFound = false;
|
|
589
|
+
for (const s of all.servers) {
|
|
590
|
+
if (!s.apiKey) continue;
|
|
591
|
+
if (!isEncryptedSecret(s.apiKey)) {
|
|
592
|
+
plaintextFound = true;
|
|
593
|
+
failures.push({
|
|
594
|
+
serverId: s.id,
|
|
595
|
+
serverLabel: s.label,
|
|
596
|
+
reason: "plaintext_on_disk"
|
|
597
|
+
});
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
try {
|
|
601
|
+
decryptSecret(s.apiKey, this.secretKey);
|
|
602
|
+
} catch (cause) {
|
|
603
|
+
failures.push({
|
|
604
|
+
serverId: s.id,
|
|
605
|
+
serverLabel: s.label,
|
|
606
|
+
reason: "key_mismatch",
|
|
607
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return { ok: failures.length === 0, failures, plaintextFound };
|
|
612
|
+
}
|
|
613
|
+
encryptApiKeys(servers) {
|
|
614
|
+
return servers.map((s) => {
|
|
615
|
+
if (!s.apiKey) return s;
|
|
616
|
+
if (isEncryptedSecret(s.apiKey)) return s;
|
|
617
|
+
return { ...s, apiKey: encryptSecret(s.apiKey, this.secretKey) };
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
async getConfig(_ctx) {
|
|
621
|
+
const all = await this.readAll();
|
|
622
|
+
return {
|
|
623
|
+
servers: this.decryptApiKeys(all.servers),
|
|
624
|
+
defaultServerId: all.defaultServerId,
|
|
625
|
+
orgDefaults: all.orgDefaults
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
async savePlatformServers(_ctx, servers, defaultServerId) {
|
|
629
|
+
const all = await this.readAll();
|
|
630
|
+
const orgRows = all.servers.filter(isOrgServer);
|
|
631
|
+
const platformRows = this.encryptApiKeys(servers.filter(isPlatformServer));
|
|
632
|
+
await writeJsonFile(this.configFilePath, {
|
|
633
|
+
servers: [...platformRows, ...orgRows],
|
|
634
|
+
defaultServerId,
|
|
635
|
+
orgDefaults: all.orgDefaults
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async saveOrgServers(_ctx, orgId, servers, defaultServerId) {
|
|
639
|
+
const all = await this.readAll();
|
|
640
|
+
const platformRows = all.servers.filter(isPlatformServer);
|
|
641
|
+
const otherOrgRows = all.servers.filter((s) => isOrgServer(s) && s.ownerOrgId !== orgId);
|
|
642
|
+
const thisOrgRows = this.encryptApiKeys(
|
|
643
|
+
servers.filter((_s) => true).map(
|
|
644
|
+
(s) => isOrgServer(s) ? { ...s, ownerOrgId: orgId } : (
|
|
645
|
+
// Coerce — caller passed something with the wrong/missing scope.
|
|
646
|
+
{ ...s, scope: "org", ownerOrgId: orgId }
|
|
647
|
+
)
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
const orgDefaults = { ...all.orgDefaults ?? {} };
|
|
651
|
+
if (defaultServerId === null) {
|
|
652
|
+
delete orgDefaults[orgId];
|
|
653
|
+
} else if (typeof defaultServerId === "string") {
|
|
654
|
+
orgDefaults[orgId] = defaultServerId;
|
|
655
|
+
}
|
|
656
|
+
await writeJsonFile(this.configFilePath, {
|
|
657
|
+
servers: [...platformRows, ...otherOrgRows, ...thisOrgRows],
|
|
658
|
+
defaultServerId: all.defaultServerId,
|
|
659
|
+
orgDefaults
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async setOrgDefault(_ctx, orgId, serverId) {
|
|
663
|
+
const all = await this.readAll();
|
|
664
|
+
const orgDefaults = { ...all.orgDefaults ?? {} };
|
|
665
|
+
if (serverId === null) {
|
|
666
|
+
delete orgDefaults[orgId];
|
|
667
|
+
} else {
|
|
668
|
+
orgDefaults[orgId] = serverId;
|
|
669
|
+
}
|
|
670
|
+
await writeJsonFile(this.configFilePath, { ...all, orgDefaults });
|
|
671
|
+
}
|
|
672
|
+
async deleteByOrg(_ctx, orgId) {
|
|
673
|
+
const all = await this.readAll();
|
|
674
|
+
const remaining = all.servers.filter((s) => !(isOrgServer(s) && s.ownerOrgId === orgId));
|
|
675
|
+
const cleaned = remaining.map((s) => {
|
|
676
|
+
if (!isPlatformServer(s)) return s;
|
|
677
|
+
if (s.sharedWith === "all") return s;
|
|
678
|
+
if (!s.sharedWith.includes(orgId)) return s;
|
|
679
|
+
const next = {
|
|
680
|
+
...s,
|
|
681
|
+
sharedWith: s.sharedWith.filter((id) => id !== orgId)
|
|
682
|
+
};
|
|
683
|
+
return next;
|
|
684
|
+
});
|
|
685
|
+
const orgDefaults = { ...all.orgDefaults ?? {} };
|
|
686
|
+
const hadDefault = orgId in orgDefaults;
|
|
687
|
+
delete orgDefaults[orgId];
|
|
688
|
+
const changed = cleaned.length !== all.servers.length || hadDefault || cleaned.some((c, i) => c !== all.servers[i]);
|
|
689
|
+
if (!changed) return;
|
|
690
|
+
await writeJsonFile(this.configFilePath, {
|
|
691
|
+
servers: cleaned,
|
|
692
|
+
defaultServerId: all.defaultServerId,
|
|
693
|
+
orgDefaults
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const empty$4 = () => ({ grants: [] });
|
|
698
|
+
class LocalPlatformProjectGrantStore {
|
|
699
|
+
filePath;
|
|
700
|
+
static fromEnv(env) {
|
|
701
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
702
|
+
return new LocalPlatformProjectGrantStore(
|
|
703
|
+
path.join(env.DATA_PATH, "platform-project-grants.json")
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
constructor(filePath) {
|
|
707
|
+
this.filePath = filePath;
|
|
708
|
+
}
|
|
709
|
+
async read() {
|
|
710
|
+
return readJsonFile(this.filePath, empty$4());
|
|
711
|
+
}
|
|
712
|
+
async write(data) {
|
|
713
|
+
await writeJsonFile(this.filePath, data);
|
|
714
|
+
}
|
|
715
|
+
async listByProject(_ctx, projectId) {
|
|
716
|
+
const { grants } = await this.read();
|
|
717
|
+
return grants.filter((g) => g.projectId === projectId);
|
|
718
|
+
}
|
|
719
|
+
async create(_ctx, grant) {
|
|
720
|
+
const data = await this.read();
|
|
721
|
+
if (data.grants.some((g) => g.id === grant.id)) {
|
|
722
|
+
throw new ProviderError(`Grant '${grant.id}' already exists`, 409);
|
|
723
|
+
}
|
|
724
|
+
const duplicate = data.grants.find(
|
|
725
|
+
(g) => g.projectId === grant.projectId && g.granteeType === grant.granteeType && g.granteeId === grant.granteeId
|
|
726
|
+
);
|
|
727
|
+
if (duplicate) {
|
|
728
|
+
throw new ProviderError(
|
|
729
|
+
`A grant for this ${grant.granteeType} already exists on this project`,
|
|
730
|
+
409
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
data.grants.push(grant);
|
|
734
|
+
await this.write(data);
|
|
735
|
+
}
|
|
736
|
+
async delete(_ctx, id) {
|
|
737
|
+
const data = await this.read();
|
|
738
|
+
const idx = data.grants.findIndex((g) => g.id === id);
|
|
739
|
+
if (idx === -1) throw new ProviderError(`Grant '${id}' not found`, 404);
|
|
740
|
+
data.grants.splice(idx, 1);
|
|
741
|
+
await this.write(data);
|
|
742
|
+
}
|
|
743
|
+
async deleteByProject(_ctx, projectId) {
|
|
744
|
+
const data = await this.read();
|
|
745
|
+
const before = data.grants.length;
|
|
746
|
+
data.grants = data.grants.filter((g) => g.projectId !== projectId);
|
|
747
|
+
if (data.grants.length !== before) await this.write(data);
|
|
748
|
+
}
|
|
749
|
+
async deleteByGranteeOrg(_ctx, orgId) {
|
|
750
|
+
const data = await this.read();
|
|
751
|
+
const before = data.grants.length;
|
|
752
|
+
data.grants = data.grants.filter((g) => !(g.granteeType === "org" && g.granteeId === orgId));
|
|
753
|
+
if (data.grants.length !== before) await this.write(data);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function isLive$1(row) {
|
|
757
|
+
return row.deletedAt == null;
|
|
758
|
+
}
|
|
759
|
+
class LocalOrgStoreLoader {
|
|
760
|
+
storePath;
|
|
761
|
+
store = null;
|
|
762
|
+
loading = null;
|
|
763
|
+
constructor(dataPath) {
|
|
764
|
+
this.storePath = path.join(dataPath, "local-org.json");
|
|
765
|
+
}
|
|
766
|
+
async get() {
|
|
767
|
+
if (this.store) return this.store;
|
|
768
|
+
this.loading ??= readJsonFile(this.storePath, {
|
|
769
|
+
orgs: [],
|
|
770
|
+
projects: [],
|
|
771
|
+
orgMembers: [],
|
|
772
|
+
projectMembers: []
|
|
773
|
+
}).then((data) => {
|
|
774
|
+
this.store = data;
|
|
775
|
+
this.loading = null;
|
|
776
|
+
return data;
|
|
777
|
+
});
|
|
778
|
+
return this.loading;
|
|
779
|
+
}
|
|
780
|
+
async write(store) {
|
|
781
|
+
this.store = store;
|
|
782
|
+
await writeJsonFile(this.storePath, store);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
class LocalOrgStore {
|
|
786
|
+
loader;
|
|
787
|
+
events;
|
|
788
|
+
invites;
|
|
789
|
+
computeServer;
|
|
790
|
+
grants;
|
|
791
|
+
static fromEnv(env) {
|
|
792
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
793
|
+
return new LocalOrgStore({
|
|
794
|
+
loader: new LocalOrgStoreLoader(env.DATA_PATH),
|
|
795
|
+
invites: LocalInviteStore.fromEnv(env),
|
|
796
|
+
computeServer: LocalComputeServerStore.fromEnv(env),
|
|
797
|
+
grants: LocalPlatformProjectGrantStore.fromEnv(env)
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
constructor(opts) {
|
|
801
|
+
this.loader = opts.loader;
|
|
802
|
+
this.invites = opts.invites;
|
|
803
|
+
this.computeServer = opts.computeServer;
|
|
804
|
+
this.grants = opts.grants;
|
|
805
|
+
this.events = opts.events ?? new NoopEventSink();
|
|
806
|
+
}
|
|
807
|
+
async listOrgs(_ctx, opts) {
|
|
808
|
+
const { orgs } = await this.loader.get();
|
|
809
|
+
return paginate(applyOrder(orgs.filter(isLive$1), opts), opts);
|
|
810
|
+
}
|
|
811
|
+
async getOrg(_ctx, id) {
|
|
812
|
+
const { orgs } = await this.loader.get();
|
|
813
|
+
const o = orgs.find((o2) => o2.id === id);
|
|
814
|
+
return o && isLive$1(o) ? o : null;
|
|
815
|
+
}
|
|
816
|
+
async getOrgBySlug(_ctx, slug) {
|
|
817
|
+
const { orgs } = await this.loader.get();
|
|
818
|
+
const o = orgs.find((o2) => o2.slug === slug);
|
|
819
|
+
return o && isLive$1(o) ? o : null;
|
|
820
|
+
}
|
|
821
|
+
async createOrg(ctx, org) {
|
|
822
|
+
const store = await this.loader.get();
|
|
823
|
+
if (store.orgs.some((o) => o.id === org.id && isLive$1(o))) {
|
|
824
|
+
throw new ProviderError(`Org '${org.id}' already exists`, 409);
|
|
825
|
+
}
|
|
826
|
+
if (store.orgs.some((o) => o.slug === org.slug && isLive$1(o))) {
|
|
827
|
+
throw new ProviderError(`Org slug '${org.slug}' already in use`, 409);
|
|
828
|
+
}
|
|
829
|
+
store.orgs.push({ ...org, deletedAt: null });
|
|
830
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
831
|
+
store.orgMembers.push({
|
|
832
|
+
orgId: org.id,
|
|
833
|
+
userId: org.ownerId,
|
|
834
|
+
role: "owner",
|
|
835
|
+
permissions: [...DEFAULT_ORG_PERMISSIONS.owner],
|
|
836
|
+
joinedAt: now,
|
|
837
|
+
...auditUpdate(ctx, org.ownerId),
|
|
838
|
+
deletedAt: null
|
|
839
|
+
});
|
|
840
|
+
await this.loader.write(store);
|
|
841
|
+
await this.events.emit({ type: "org.created", orgId: org.id, actorId: actorFrom(ctx) });
|
|
842
|
+
}
|
|
843
|
+
async updateOrg(ctx, id, patch) {
|
|
844
|
+
const store = await this.loader.get();
|
|
845
|
+
const idx = store.orgs.findIndex((o) => o.id === id && isLive$1(o));
|
|
846
|
+
if (idx === -1) throw new ProviderError(`Org '${id}' not found`, 404);
|
|
847
|
+
if (patch.slug && patch.slug !== store.orgs[idx].slug) {
|
|
848
|
+
if (store.orgs.some((o) => o.id !== id && o.slug === patch.slug && isLive$1(o))) {
|
|
849
|
+
throw new ProviderError(`Org slug '${patch.slug}' already in use`, 409);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
store.orgs[idx] = {
|
|
853
|
+
...store.orgs[idx],
|
|
854
|
+
...patch,
|
|
855
|
+
...auditUpdate(ctx, store.orgs[idx].updatedBy ?? store.orgs[idx].ownerId)
|
|
856
|
+
};
|
|
857
|
+
await this.loader.write(store);
|
|
858
|
+
}
|
|
859
|
+
async deleteOrg(ctx, id) {
|
|
860
|
+
const store = await this.loader.get();
|
|
861
|
+
const idx = store.orgs.findIndex((o) => o.id === id && isLive$1(o));
|
|
862
|
+
if (idx === -1) throw new ProviderError(`Org '${id}' not found`, 404);
|
|
863
|
+
const stamp = auditSoftDelete(ctx, store.orgs[idx].updatedBy ?? store.orgs[idx].ownerId);
|
|
864
|
+
store.orgs[idx] = { ...store.orgs[idx], ...stamp };
|
|
865
|
+
store.orgMembers = store.orgMembers.map(
|
|
866
|
+
(m) => m.orgId === id && isLive$1(m) ? { ...m, ...stamp } : m
|
|
867
|
+
);
|
|
868
|
+
const orgProjectIds = new Set(
|
|
869
|
+
store.projects.filter((p) => p.orgId === id && isLive$1(p)).map((p) => p.id)
|
|
870
|
+
);
|
|
871
|
+
store.projects = store.projects.map(
|
|
872
|
+
(p) => p.orgId === id && isLive$1(p) ? { ...p, ...stamp } : p
|
|
873
|
+
);
|
|
874
|
+
store.projectMembers = store.projectMembers.map(
|
|
875
|
+
(m) => orgProjectIds.has(m.projectId) && isLive$1(m) ? { ...m, ...stamp } : m
|
|
876
|
+
);
|
|
877
|
+
await this.loader.write(store);
|
|
878
|
+
await this.invites.deleteByOrg(ctx, id);
|
|
879
|
+
await this.computeServer.deleteByOrg(ctx, id);
|
|
880
|
+
for (const projectId of orgProjectIds) {
|
|
881
|
+
await this.grants.deleteByProject(ctx, projectId);
|
|
882
|
+
}
|
|
883
|
+
await this.grants.deleteByGranteeOrg(ctx, id);
|
|
884
|
+
await this.events.emit({ type: "org.deleted", orgId: id, actorId: actorFrom(ctx) });
|
|
885
|
+
}
|
|
886
|
+
async listOrgMembers(_ctx, orgId, opts) {
|
|
887
|
+
const { orgMembers } = await this.loader.get();
|
|
888
|
+
return paginate(
|
|
889
|
+
orgMembers.filter((m) => m.orgId === orgId && isLive$1(m)),
|
|
890
|
+
opts
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
async getOrgMember(_ctx, orgId, userId) {
|
|
894
|
+
const { orgMembers } = await this.loader.get();
|
|
895
|
+
const m = orgMembers.find((m2) => m2.orgId === orgId && m2.userId === userId);
|
|
896
|
+
return m && isLive$1(m) ? m : null;
|
|
897
|
+
}
|
|
898
|
+
async findUserMembership(_ctx, userId) {
|
|
899
|
+
const store = await this.loader.get();
|
|
900
|
+
const member = store.orgMembers.find((m) => m.userId === userId && isLive$1(m));
|
|
901
|
+
if (!member) return null;
|
|
902
|
+
const org = store.orgs.find((o) => o.id === member.orgId);
|
|
903
|
+
if (!org || !isLive$1(org)) return null;
|
|
904
|
+
return { org, member };
|
|
905
|
+
}
|
|
906
|
+
async addOrgMember(ctx, member) {
|
|
907
|
+
const store = await this.loader.get();
|
|
908
|
+
const existing = store.orgMembers.find(
|
|
909
|
+
(m) => m.orgId === member.orgId && m.userId === member.userId
|
|
910
|
+
);
|
|
911
|
+
if (existing) {
|
|
912
|
+
Object.assign(existing, member, {
|
|
913
|
+
...auditUpdate(ctx, member.userId),
|
|
914
|
+
deletedAt: null
|
|
915
|
+
});
|
|
916
|
+
} else {
|
|
917
|
+
store.orgMembers.push({ ...member, deletedAt: null });
|
|
918
|
+
}
|
|
919
|
+
await this.loader.write(store);
|
|
920
|
+
await this.events.emit({
|
|
921
|
+
type: "org_member.added",
|
|
922
|
+
orgId: member.orgId,
|
|
923
|
+
userId: member.userId,
|
|
924
|
+
actorId: actorFrom(ctx)
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
async updateOrgMemberRole(ctx, orgId, userId, role) {
|
|
928
|
+
const store = await this.loader.get();
|
|
929
|
+
const m = store.orgMembers.find((m2) => m2.orgId === orgId && m2.userId === userId && isLive$1(m2));
|
|
930
|
+
if (!m) throw new ProviderError(`Org member '${userId}' not found`, 404);
|
|
931
|
+
m.role = role;
|
|
932
|
+
m.permissions = [...DEFAULT_ORG_PERMISSIONS[role]];
|
|
933
|
+
Object.assign(m, auditUpdate(ctx, m.updatedBy));
|
|
934
|
+
await this.loader.write(store);
|
|
935
|
+
await this.events.emit({
|
|
936
|
+
type: "org_member.role_changed",
|
|
937
|
+
orgId,
|
|
938
|
+
userId,
|
|
939
|
+
role,
|
|
940
|
+
actorId: actorFrom(ctx)
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
async updateOrgMemberPermissions(ctx, orgId, userId, permissions) {
|
|
944
|
+
const store = await this.loader.get();
|
|
945
|
+
const m = store.orgMembers.find((m2) => m2.orgId === orgId && m2.userId === userId && isLive$1(m2));
|
|
946
|
+
if (!m) throw new ProviderError(`Org member '${userId}' not found`, 404);
|
|
947
|
+
m.permissions = [...permissions];
|
|
948
|
+
Object.assign(m, auditUpdate(ctx, m.updatedBy));
|
|
949
|
+
await this.loader.write(store);
|
|
950
|
+
await this.events.emit({
|
|
951
|
+
type: "org_member.permissions_changed",
|
|
952
|
+
orgId,
|
|
953
|
+
userId,
|
|
954
|
+
permissions: [...permissions],
|
|
955
|
+
actorId: actorFrom(ctx)
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
async removeOrgMember(ctx, orgId, userId) {
|
|
959
|
+
const store = await this.loader.get();
|
|
960
|
+
const m = store.orgMembers.find((m2) => m2.orgId === orgId && m2.userId === userId && isLive$1(m2));
|
|
961
|
+
if (!m) return;
|
|
962
|
+
const stamp = auditSoftDelete(ctx, m.updatedBy);
|
|
963
|
+
Object.assign(m, stamp);
|
|
964
|
+
const projectIdsInOrg = new Set(
|
|
965
|
+
store.projects.filter((p) => p.orgId === orgId).map((p) => p.id)
|
|
966
|
+
);
|
|
967
|
+
store.projectMembers = store.projectMembers.map(
|
|
968
|
+
(pm) => pm.userId === userId && projectIdsInOrg.has(pm.projectId) && isLive$1(pm) ? { ...pm, ...stamp } : pm
|
|
969
|
+
);
|
|
970
|
+
await this.loader.write(store);
|
|
971
|
+
await this.events.emit({
|
|
972
|
+
type: "org_member.removed",
|
|
973
|
+
orgId,
|
|
974
|
+
userId,
|
|
975
|
+
actorId: actorFrom(ctx)
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function isLive(row) {
|
|
980
|
+
return row.deletedAt == null;
|
|
981
|
+
}
|
|
982
|
+
class LocalProjectStore {
|
|
983
|
+
loader;
|
|
984
|
+
events;
|
|
985
|
+
grants;
|
|
986
|
+
constructor(opts) {
|
|
987
|
+
this.loader = opts.loader;
|
|
988
|
+
this.grants = opts.grants;
|
|
989
|
+
this.events = opts.events ?? new NoopEventSink();
|
|
990
|
+
}
|
|
991
|
+
async listProjects(_ctx, orgId, opts) {
|
|
992
|
+
const { projects } = await this.loader.get();
|
|
993
|
+
return paginate(
|
|
994
|
+
applyOrder(
|
|
995
|
+
projects.filter((p) => p.orgId === orgId && isLive(p)),
|
|
996
|
+
opts
|
|
997
|
+
),
|
|
998
|
+
opts
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
async getProject(_ctx, id) {
|
|
1002
|
+
const { projects } = await this.loader.get();
|
|
1003
|
+
const p = projects.find((p2) => p2.id === id);
|
|
1004
|
+
return p && isLive(p) ? p : null;
|
|
1005
|
+
}
|
|
1006
|
+
async getProjectBySlug(_ctx, orgId, slug) {
|
|
1007
|
+
const { projects } = await this.loader.get();
|
|
1008
|
+
const p = projects.find((p2) => p2.orgId === orgId && p2.slug === slug);
|
|
1009
|
+
return p && isLive(p) ? p : null;
|
|
1010
|
+
}
|
|
1011
|
+
async createProject(ctx, project) {
|
|
1012
|
+
const store = await this.loader.get();
|
|
1013
|
+
if (!store.orgs.some((o) => o.id === project.orgId && isLive(o))) {
|
|
1014
|
+
throw new ProviderError(`Org '${project.orgId}' not found`, 404);
|
|
1015
|
+
}
|
|
1016
|
+
if (store.projects.some((p) => p.id === project.id && isLive(p))) {
|
|
1017
|
+
throw new ProviderError(`Project '${project.id}' already exists`, 409);
|
|
1018
|
+
}
|
|
1019
|
+
const nameKey = project.name.toLowerCase();
|
|
1020
|
+
if (store.projects.some(
|
|
1021
|
+
(p) => p.orgId === project.orgId && isLive(p) && p.name.toLowerCase() === nameKey
|
|
1022
|
+
)) {
|
|
1023
|
+
throw new ProviderError("projects_org_name_unique: project name already in use", 409);
|
|
1024
|
+
}
|
|
1025
|
+
if (store.projects.some((p) => p.orgId === project.orgId && isLive(p) && p.slug === project.slug)) {
|
|
1026
|
+
throw new ProviderError("projects_org_id_slug_key: project slug already in use", 409);
|
|
1027
|
+
}
|
|
1028
|
+
store.projects.push({ ...project, deletedAt: null });
|
|
1029
|
+
store.projectMembers.push({
|
|
1030
|
+
projectId: project.id,
|
|
1031
|
+
userId: project.ownerId,
|
|
1032
|
+
role: "owner",
|
|
1033
|
+
joinedAt: project.createdAt,
|
|
1034
|
+
updatedAt: project.createdAt,
|
|
1035
|
+
updatedBy: project.ownerId,
|
|
1036
|
+
deletedAt: null
|
|
1037
|
+
});
|
|
1038
|
+
await this.loader.write(store);
|
|
1039
|
+
await this.events.emit({
|
|
1040
|
+
type: "project.created",
|
|
1041
|
+
projectId: project.id,
|
|
1042
|
+
orgId: project.orgId,
|
|
1043
|
+
actorId: actorFrom(ctx)
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
async updateProject(ctx, id, patch) {
|
|
1047
|
+
const store = await this.loader.get();
|
|
1048
|
+
const idx = store.projects.findIndex((p) => p.id === id && isLive(p));
|
|
1049
|
+
if (idx === -1) throw new ProviderError(`Project '${id}' not found`, 404);
|
|
1050
|
+
const current = store.projects[idx];
|
|
1051
|
+
if (patch.name && patch.name.toLowerCase() !== current.name.toLowerCase()) {
|
|
1052
|
+
const nameKey = patch.name.toLowerCase();
|
|
1053
|
+
if (store.projects.some(
|
|
1054
|
+
(p) => p.orgId === current.orgId && p.id !== id && isLive(p) && p.name.toLowerCase() === nameKey
|
|
1055
|
+
)) {
|
|
1056
|
+
throw new ProviderError("projects_org_name_unique: project name already in use", 409);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (patch.slug && patch.slug !== current.slug) {
|
|
1060
|
+
if (store.projects.some(
|
|
1061
|
+
(p) => p.orgId === current.orgId && p.slug === patch.slug && p.id !== id && isLive(p)
|
|
1062
|
+
)) {
|
|
1063
|
+
throw new ProviderError("projects_org_id_slug_key: project slug already in use", 409);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
store.projects[idx] = {
|
|
1067
|
+
...current,
|
|
1068
|
+
...patch,
|
|
1069
|
+
...auditUpdate(ctx, current.updatedBy ?? current.ownerId)
|
|
1070
|
+
};
|
|
1071
|
+
await this.loader.write(store);
|
|
1072
|
+
}
|
|
1073
|
+
async deleteProject(ctx, id) {
|
|
1074
|
+
const store = await this.loader.get();
|
|
1075
|
+
const idx = store.projects.findIndex((p) => p.id === id && isLive(p));
|
|
1076
|
+
if (idx === -1) throw new ProviderError(`Project '${id}' not found`, 404);
|
|
1077
|
+
const stamp = auditSoftDelete(
|
|
1078
|
+
ctx,
|
|
1079
|
+
store.projects[idx].updatedBy ?? store.projects[idx].ownerId
|
|
1080
|
+
);
|
|
1081
|
+
store.projects[idx] = { ...store.projects[idx], ...stamp };
|
|
1082
|
+
store.projectMembers = store.projectMembers.map(
|
|
1083
|
+
(m) => m.projectId === id && isLive(m) ? { ...m, ...stamp } : m
|
|
1084
|
+
);
|
|
1085
|
+
await this.loader.write(store);
|
|
1086
|
+
await this.grants.deleteByProject(ctx, id);
|
|
1087
|
+
await this.events.emit({ type: "project.deleted", projectId: id, actorId: actorFrom(ctx) });
|
|
1088
|
+
}
|
|
1089
|
+
async reactivateProject(ctx, orgId, slug) {
|
|
1090
|
+
const store = await this.loader.get();
|
|
1091
|
+
const idx = store.projects.findIndex((p) => p.orgId === orgId && p.slug === slug && !isLive(p));
|
|
1092
|
+
if (idx === -1) return null;
|
|
1093
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1094
|
+
store.projects[idx] = { ...store.projects[idx], deletedAt: null, updatedAt: now };
|
|
1095
|
+
const ownerId = store.projects[idx].ownerId;
|
|
1096
|
+
const pmIdx = store.projectMembers.findIndex(
|
|
1097
|
+
(m) => m.projectId === store.projects[idx].id && m.userId === ownerId && !isLive(m)
|
|
1098
|
+
);
|
|
1099
|
+
if (pmIdx !== -1) {
|
|
1100
|
+
store.projectMembers[pmIdx] = {
|
|
1101
|
+
...store.projectMembers[pmIdx],
|
|
1102
|
+
deletedAt: null,
|
|
1103
|
+
updatedAt: now
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
await this.loader.write(store);
|
|
1107
|
+
const project = { ...store.projects[idx] };
|
|
1108
|
+
await this.events.emit({
|
|
1109
|
+
type: "project.created",
|
|
1110
|
+
projectId: project.id,
|
|
1111
|
+
orgId: project.orgId,
|
|
1112
|
+
actorId: actorFrom(ctx)
|
|
1113
|
+
});
|
|
1114
|
+
return project;
|
|
1115
|
+
}
|
|
1116
|
+
async listProjectMembers(_ctx, projectId, opts) {
|
|
1117
|
+
const { projectMembers } = await this.loader.get();
|
|
1118
|
+
return paginate(
|
|
1119
|
+
projectMembers.filter((m) => m.projectId === projectId && isLive(m)),
|
|
1120
|
+
opts
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
async getProjectMember(_ctx, projectId, userId) {
|
|
1124
|
+
const { projectMembers } = await this.loader.get();
|
|
1125
|
+
const m = projectMembers.find((m2) => m2.projectId === projectId && m2.userId === userId);
|
|
1126
|
+
return m && isLive(m) ? m : null;
|
|
1127
|
+
}
|
|
1128
|
+
async addProjectMember(ctx, member) {
|
|
1129
|
+
const store = await this.loader.get();
|
|
1130
|
+
const existing = store.projectMembers.find(
|
|
1131
|
+
(m) => m.projectId === member.projectId && m.userId === member.userId
|
|
1132
|
+
);
|
|
1133
|
+
if (existing) {
|
|
1134
|
+
Object.assign(existing, member, {
|
|
1135
|
+
...auditUpdate(ctx, member.userId),
|
|
1136
|
+
deletedAt: null
|
|
1137
|
+
});
|
|
1138
|
+
} else {
|
|
1139
|
+
const stamp = auditUpdate(ctx, member.userId);
|
|
1140
|
+
store.projectMembers.push({
|
|
1141
|
+
...member,
|
|
1142
|
+
updatedAt: member.updatedAt ?? stamp.updatedAt,
|
|
1143
|
+
updatedBy: member.updatedBy ?? stamp.updatedBy,
|
|
1144
|
+
deletedAt: null
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
await this.loader.write(store);
|
|
1148
|
+
await this.events.emit({
|
|
1149
|
+
type: "project_member.added",
|
|
1150
|
+
projectId: member.projectId,
|
|
1151
|
+
userId: member.userId,
|
|
1152
|
+
actorId: actorFrom(ctx)
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
async updateProjectMemberRole(ctx, projectId, userId, role) {
|
|
1156
|
+
const store = await this.loader.get();
|
|
1157
|
+
const m = store.projectMembers.find(
|
|
1158
|
+
(m2) => m2.projectId === projectId && m2.userId === userId && isLive(m2)
|
|
1159
|
+
);
|
|
1160
|
+
if (!m) throw new ProviderError(`Project member '${userId}' not found`, 404);
|
|
1161
|
+
m.role = role;
|
|
1162
|
+
Object.assign(m, auditUpdate(ctx, m.updatedBy));
|
|
1163
|
+
await this.loader.write(store);
|
|
1164
|
+
await this.events.emit({
|
|
1165
|
+
type: "project_member.role_changed",
|
|
1166
|
+
projectId,
|
|
1167
|
+
userId,
|
|
1168
|
+
role,
|
|
1169
|
+
actorId: actorFrom(ctx)
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
async removeProjectMember(ctx, projectId, userId) {
|
|
1173
|
+
const store = await this.loader.get();
|
|
1174
|
+
const m = store.projectMembers.find(
|
|
1175
|
+
(m2) => m2.projectId === projectId && m2.userId === userId && isLive(m2)
|
|
1176
|
+
);
|
|
1177
|
+
if (!m) return;
|
|
1178
|
+
Object.assign(m, auditSoftDelete(ctx, m.updatedBy));
|
|
1179
|
+
await this.loader.write(store);
|
|
1180
|
+
await this.events.emit({
|
|
1181
|
+
type: "project_member.removed",
|
|
1182
|
+
projectId,
|
|
1183
|
+
userId,
|
|
1184
|
+
actorId: actorFrom(ctx)
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const empty$3 = () => ({ definitions: {}, definitionVersions: {} });
|
|
1189
|
+
class LocalDefinitionStore {
|
|
1190
|
+
configPath;
|
|
1191
|
+
events;
|
|
1192
|
+
projectProvider;
|
|
1193
|
+
static fromEnv(env) {
|
|
1194
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1195
|
+
return new LocalDefinitionStore(env.DATA_PATH);
|
|
1196
|
+
}
|
|
1197
|
+
constructor(definitionsPath, projectProvider, events = new NoopEventSink()) {
|
|
1198
|
+
this.configPath = path.join(definitionsPath, "definitions-config.json");
|
|
1199
|
+
this.projectProvider = projectProvider;
|
|
1200
|
+
this.events = events;
|
|
1201
|
+
}
|
|
1202
|
+
setProjectProvider(projectProvider) {
|
|
1203
|
+
this.projectProvider = projectProvider;
|
|
1204
|
+
}
|
|
1205
|
+
async readConfig() {
|
|
1206
|
+
return readJsonFile(this.configPath, empty$3());
|
|
1207
|
+
}
|
|
1208
|
+
live(record) {
|
|
1209
|
+
return Boolean(record && record.deletedAt == null);
|
|
1210
|
+
}
|
|
1211
|
+
async writeConfig(config) {
|
|
1212
|
+
await writeJsonFile(this.configPath, config);
|
|
1213
|
+
}
|
|
1214
|
+
sortedRecords(records, opts) {
|
|
1215
|
+
const defaulted = {
|
|
1216
|
+
...opts,
|
|
1217
|
+
orderBy: opts?.orderBy ?? "name",
|
|
1218
|
+
orderDir: opts?.orderDir ?? "asc"
|
|
1219
|
+
};
|
|
1220
|
+
return applyOrder([...records], defaulted, (r, field) => {
|
|
1221
|
+
if (field === "name") return r.displayName.toLowerCase();
|
|
1222
|
+
if (field === "solveCount") return r.solveCount ?? 0;
|
|
1223
|
+
return r[field];
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
visibleRecords(records, opts) {
|
|
1227
|
+
const filtered = records.filter((r) => r?.displayName && this.live(r));
|
|
1228
|
+
if (opts?.statuses?.length) {
|
|
1229
|
+
const allowed = new Set(opts.statuses);
|
|
1230
|
+
return filtered.filter((r) => allowed.has(r.status));
|
|
1231
|
+
}
|
|
1232
|
+
return filtered.filter((r) => {
|
|
1233
|
+
if (r.status === "pending" && !opts?.includePending) return false;
|
|
1234
|
+
if (r.status === "archived" && !opts?.includeArchived) return false;
|
|
1235
|
+
return true;
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
async list(_ctx, opts) {
|
|
1239
|
+
const config = await this.readConfig();
|
|
1240
|
+
const records = this.visibleRecords(Object.values(config.definitions), opts);
|
|
1241
|
+
return paginate(this.sortedRecords(records, opts), opts);
|
|
1242
|
+
}
|
|
1243
|
+
async listByProject(_ctx, projectId, opts) {
|
|
1244
|
+
const config = await this.readConfig();
|
|
1245
|
+
const records = this.visibleRecords(
|
|
1246
|
+
Object.values(config.definitions).filter((r) => r?.projectId === projectId),
|
|
1247
|
+
opts
|
|
1248
|
+
);
|
|
1249
|
+
return paginate(this.sortedRecords(records, opts), opts);
|
|
1250
|
+
}
|
|
1251
|
+
async listPublic(ctx, opts) {
|
|
1252
|
+
if (!this.projectProvider) {
|
|
1253
|
+
return this.list(ctx, opts);
|
|
1254
|
+
}
|
|
1255
|
+
const config = await this.readConfig();
|
|
1256
|
+
const records = Object.values(config.definitions).filter(
|
|
1257
|
+
(r) => Boolean(r?.displayName && this.live(r))
|
|
1258
|
+
);
|
|
1259
|
+
const projectIds = Array.from(new Set(records.map((r) => r.projectId)));
|
|
1260
|
+
const projects = await Promise.all(
|
|
1261
|
+
projectIds.map((id) => this.projectProvider.getProject(ctx, id))
|
|
1262
|
+
);
|
|
1263
|
+
const publicProjectIds = new Set(
|
|
1264
|
+
projects.filter(
|
|
1265
|
+
(p) => p !== null && p.visibility === "public" && (!opts?.orgId || p.orgId === opts.orgId)
|
|
1266
|
+
).map((p) => p.id)
|
|
1267
|
+
);
|
|
1268
|
+
const publicRecords = this.visibleRecords(
|
|
1269
|
+
records.filter((r) => publicProjectIds.has(r.projectId)),
|
|
1270
|
+
opts
|
|
1271
|
+
);
|
|
1272
|
+
return paginate(this.sortedRecords(publicRecords, opts), opts);
|
|
1273
|
+
}
|
|
1274
|
+
async get(_ctx, guid) {
|
|
1275
|
+
const config = await this.readConfig();
|
|
1276
|
+
const r = config.definitions[guid];
|
|
1277
|
+
return this.live(r) ? r : null;
|
|
1278
|
+
}
|
|
1279
|
+
async create(ctx, record) {
|
|
1280
|
+
const config = await this.readConfig();
|
|
1281
|
+
const actor = ctx.userId || record.ownerId;
|
|
1282
|
+
config.definitions[record.guid] = {
|
|
1283
|
+
...record,
|
|
1284
|
+
createdBy: record.createdBy || actor,
|
|
1285
|
+
updatedBy: record.updatedBy || actor,
|
|
1286
|
+
liveVersionId: record.liveVersionId ?? null,
|
|
1287
|
+
draftVersionId: record.draftVersionId ?? null,
|
|
1288
|
+
deletedAt: null
|
|
1289
|
+
};
|
|
1290
|
+
await this.writeConfig(config);
|
|
1291
|
+
await this.events.emit({
|
|
1292
|
+
type: "definition.created",
|
|
1293
|
+
definitionId: record.guid,
|
|
1294
|
+
projectId: record.projectId,
|
|
1295
|
+
actorId: actorFrom(ctx)
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
async update(ctx, guid, patch) {
|
|
1299
|
+
const config = await this.readConfig();
|
|
1300
|
+
const existing = config.definitions[guid];
|
|
1301
|
+
if (!this.live(existing)) throw new ProviderError(`Definition '${guid}' not found`, 404);
|
|
1302
|
+
const clearable = (v) => v === null ? void 0 : v;
|
|
1303
|
+
config.definitions[guid] = {
|
|
1304
|
+
...existing,
|
|
1305
|
+
...patch.displayName !== void 0 && { displayName: patch.displayName },
|
|
1306
|
+
...patch.description !== void 0 && {
|
|
1307
|
+
description: clearable(patch.description)
|
|
1308
|
+
},
|
|
1309
|
+
...patch.category !== void 0 && {
|
|
1310
|
+
category: clearable(patch.category)
|
|
1311
|
+
},
|
|
1312
|
+
...patch.tags !== void 0 && { tags: clearable(patch.tags) },
|
|
1313
|
+
...patch.coverImage !== void 0 && {
|
|
1314
|
+
coverImage: clearable(patch.coverImage)
|
|
1315
|
+
},
|
|
1316
|
+
...patch.projectId !== void 0 && { projectId: patch.projectId },
|
|
1317
|
+
...patch.computeServerId !== void 0 && {
|
|
1318
|
+
computeServerId: clearable(patch.computeServerId)
|
|
1319
|
+
},
|
|
1320
|
+
...patch.status !== void 0 && { status: patch.status },
|
|
1321
|
+
...patch.ownerId !== void 0 && { ownerId: patch.ownerId },
|
|
1322
|
+
...auditUpdate(ctx, existing.updatedBy ?? existing.ownerId)
|
|
1323
|
+
};
|
|
1324
|
+
await this.writeConfig(config);
|
|
1325
|
+
}
|
|
1326
|
+
async delete(ctx, guid) {
|
|
1327
|
+
const config = await this.readConfig();
|
|
1328
|
+
const existing = config.definitions[guid];
|
|
1329
|
+
if (!this.live(existing)) return;
|
|
1330
|
+
Object.assign(existing, auditSoftDelete(ctx, existing.updatedBy ?? existing.ownerId));
|
|
1331
|
+
await this.writeConfig(config);
|
|
1332
|
+
await this.events.emit({
|
|
1333
|
+
type: "definition.deleted",
|
|
1334
|
+
definitionId: guid,
|
|
1335
|
+
actorId: actorFrom(ctx)
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
async incrementSolveCount(_ctx, guid) {
|
|
1339
|
+
const config = await this.readConfig();
|
|
1340
|
+
const existing = config.definitions[guid];
|
|
1341
|
+
if (!this.live(existing)) return;
|
|
1342
|
+
existing.solveCount = (existing.solveCount ?? 0) + 1;
|
|
1343
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1344
|
+
await this.writeConfig(config);
|
|
1345
|
+
}
|
|
1346
|
+
// ============================================================================
|
|
1347
|
+
// Versions (spec §6)
|
|
1348
|
+
// ============================================================================
|
|
1349
|
+
async createVersion(ctx, version) {
|
|
1350
|
+
const config = await this.readConfig();
|
|
1351
|
+
const parent = config.definitions[version.definitionId];
|
|
1352
|
+
if (!this.live(parent)) {
|
|
1353
|
+
throw new ProviderError(`Definition '${version.definitionId}' not found`, 404);
|
|
1354
|
+
}
|
|
1355
|
+
if (config.definitionVersions[version.id]) {
|
|
1356
|
+
throw new ProviderError(`Version '${version.id}' already exists`, 409);
|
|
1357
|
+
}
|
|
1358
|
+
config.definitionVersions[version.id] = { ...version };
|
|
1359
|
+
await this.writeConfig(config);
|
|
1360
|
+
await this.events.emit({
|
|
1361
|
+
type: "definition_version.created",
|
|
1362
|
+
versionId: version.id,
|
|
1363
|
+
definitionId: version.definitionId,
|
|
1364
|
+
actorId: actorFrom(ctx)
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
async listVersions(_ctx, definitionId, opts) {
|
|
1368
|
+
const config = await this.readConfig();
|
|
1369
|
+
const parent = config.definitions[definitionId];
|
|
1370
|
+
if (!this.live(parent)) return paginate([], opts);
|
|
1371
|
+
const rows = Object.values(config.definitionVersions).filter((v) => v.definitionId === definitionId).sort((a, b) => b.versionNumber - a.versionNumber);
|
|
1372
|
+
return paginate(rows, opts);
|
|
1373
|
+
}
|
|
1374
|
+
async getVersion(_ctx, versionId) {
|
|
1375
|
+
const config = await this.readConfig();
|
|
1376
|
+
return config.definitionVersions[versionId] ?? null;
|
|
1377
|
+
}
|
|
1378
|
+
async setVersionSchema(_ctx, versionId, schema) {
|
|
1379
|
+
const config = await this.readConfig();
|
|
1380
|
+
const version = config.definitionVersions[versionId];
|
|
1381
|
+
if (!version) return;
|
|
1382
|
+
version.schema = schema;
|
|
1383
|
+
version.schemaExtractedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1384
|
+
await this.writeConfig(config);
|
|
1385
|
+
}
|
|
1386
|
+
async deleteVersion(ctx, versionId) {
|
|
1387
|
+
const config = await this.readConfig();
|
|
1388
|
+
const version = config.definitionVersions[versionId];
|
|
1389
|
+
if (!version) return;
|
|
1390
|
+
const parent = config.definitions[version.definitionId];
|
|
1391
|
+
if (parent && (parent.liveVersionId === versionId || parent.draftVersionId === versionId)) {
|
|
1392
|
+
throw new ProviderError(
|
|
1393
|
+
`Version '${versionId}' is referenced by liveVersionId or draftVersionId`,
|
|
1394
|
+
409
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
delete config.definitionVersions[versionId];
|
|
1398
|
+
await this.writeConfig(config);
|
|
1399
|
+
await this.events.emit({
|
|
1400
|
+
type: "definition_version.deleted",
|
|
1401
|
+
versionId,
|
|
1402
|
+
actorId: actorFrom(ctx)
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
async setLiveVersion(ctx, definitionId, versionId) {
|
|
1406
|
+
await this.repoint("live", ctx, definitionId, versionId);
|
|
1407
|
+
}
|
|
1408
|
+
async setDraftVersion(ctx, definitionId, versionId) {
|
|
1409
|
+
await this.repoint("draft", ctx, definitionId, versionId);
|
|
1410
|
+
}
|
|
1411
|
+
async attachInitialVersion(ctx, definitionId, versionId) {
|
|
1412
|
+
const config = await this.readConfig();
|
|
1413
|
+
const record = config.definitions[definitionId];
|
|
1414
|
+
if (!this.live(record)) throw new ProviderError(`Definition '${definitionId}' not found`, 404);
|
|
1415
|
+
const version = config.definitionVersions[versionId];
|
|
1416
|
+
if (!version || version.definitionId !== definitionId) {
|
|
1417
|
+
throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
|
|
1418
|
+
}
|
|
1419
|
+
record.liveVersionId = versionId;
|
|
1420
|
+
record.draftVersionId = versionId;
|
|
1421
|
+
record.status = "draft";
|
|
1422
|
+
Object.assign(record, auditUpdate(ctx, record.updatedBy ?? record.ownerId));
|
|
1423
|
+
await this.writeConfig(config);
|
|
1424
|
+
}
|
|
1425
|
+
async repoint(channel, ctx, definitionId, versionId) {
|
|
1426
|
+
const config = await this.readConfig();
|
|
1427
|
+
const record = config.definitions[definitionId];
|
|
1428
|
+
if (!this.live(record)) throw new ProviderError(`Definition '${definitionId}' not found`, 404);
|
|
1429
|
+
const version = config.definitionVersions[versionId];
|
|
1430
|
+
if (!version || version.definitionId !== definitionId) {
|
|
1431
|
+
throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
|
|
1432
|
+
}
|
|
1433
|
+
if (channel === "live") record.liveVersionId = versionId;
|
|
1434
|
+
else record.draftVersionId = versionId;
|
|
1435
|
+
Object.assign(record, auditUpdate(ctx, record.updatedBy ?? record.ownerId));
|
|
1436
|
+
await this.writeConfig(config);
|
|
1437
|
+
if (channel === "live") {
|
|
1438
|
+
await this.events.emit({
|
|
1439
|
+
type: "definition.published",
|
|
1440
|
+
definitionId,
|
|
1441
|
+
versionId,
|
|
1442
|
+
actorId: actorFrom(ctx)
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
const empty$2 = () => ({ links: {} });
|
|
1448
|
+
class LocalShareLinkStore {
|
|
1449
|
+
definitionProvider;
|
|
1450
|
+
events;
|
|
1451
|
+
configFilePath;
|
|
1452
|
+
static fromEnv(env) {
|
|
1453
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1454
|
+
return new LocalShareLinkStore({
|
|
1455
|
+
filePath: path.join(env.DATA_PATH, "share-links.json")
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
constructor(opts) {
|
|
1459
|
+
this.configFilePath = opts.filePath;
|
|
1460
|
+
this.events = opts.events ?? new NoopEventSink();
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Wire the definition store so token resolution can check the parent
|
|
1464
|
+
* definition's `deletedAt` (Permissions.md §7 cascade contract). Mirrors
|
|
1465
|
+
* Supabase, which performs the equivalent JOIN. Optional: when unset, the
|
|
1466
|
+
* store falls back to the local-only revoke check; the route layer in
|
|
1467
|
+
* the selva app does the parent lookup as a safety net either way.
|
|
1468
|
+
*/
|
|
1469
|
+
setDefinitionProvider(definitions) {
|
|
1470
|
+
this.definitionProvider = definitions;
|
|
1471
|
+
}
|
|
1472
|
+
async readAll() {
|
|
1473
|
+
return readJsonFile(this.configFilePath, empty$2());
|
|
1474
|
+
}
|
|
1475
|
+
async writeAll(data) {
|
|
1476
|
+
await writeJsonFile(this.configFilePath, data);
|
|
1477
|
+
}
|
|
1478
|
+
isLive(l) {
|
|
1479
|
+
return Boolean(l && l.revokedAt == null);
|
|
1480
|
+
}
|
|
1481
|
+
async create(ctx, link) {
|
|
1482
|
+
const all = await this.readAll();
|
|
1483
|
+
if (all.links[link.id]) {
|
|
1484
|
+
throw new ProviderError(`Share link '${link.id}' already exists`, 409);
|
|
1485
|
+
}
|
|
1486
|
+
all.links[link.id] = { ...link, revokedAt: null };
|
|
1487
|
+
await this.writeAll(all);
|
|
1488
|
+
await this.events.emit({
|
|
1489
|
+
type: "share_link.minted",
|
|
1490
|
+
linkId: link.id,
|
|
1491
|
+
definitionId: link.definitionId,
|
|
1492
|
+
actorId: actorFrom(ctx)
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
async listByDefinition(_ctx, definitionId, opts) {
|
|
1496
|
+
const all = await this.readAll();
|
|
1497
|
+
const rows = Object.values(all.links).filter((l) => l.definitionId === definitionId && this.isLive(l)).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1498
|
+
return paginate(rows, opts);
|
|
1499
|
+
}
|
|
1500
|
+
async getById(_ctx, id) {
|
|
1501
|
+
const all = await this.readAll();
|
|
1502
|
+
return all.links[id] ?? null;
|
|
1503
|
+
}
|
|
1504
|
+
async getByTokenHash(_ctx, tokenHash) {
|
|
1505
|
+
const all = await this.readAll();
|
|
1506
|
+
const found = Object.values(all.links).find((l) => l.tokenHash === tokenHash);
|
|
1507
|
+
if (!this.isLive(found)) return null;
|
|
1508
|
+
if (this.definitionProvider) {
|
|
1509
|
+
const parent = await this.definitionProvider.get(SYSTEM_CONTEXT, found.definitionId);
|
|
1510
|
+
if (!parent) return null;
|
|
1511
|
+
}
|
|
1512
|
+
return found;
|
|
1513
|
+
}
|
|
1514
|
+
async revoke(ctx, id) {
|
|
1515
|
+
const all = await this.readAll();
|
|
1516
|
+
const l = all.links[id];
|
|
1517
|
+
if (!l || !this.isLive(l)) return;
|
|
1518
|
+
l.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1519
|
+
await this.writeAll(all);
|
|
1520
|
+
await this.events.emit({ type: "share_link.revoked", linkId: id, actorId: actorFrom(ctx) });
|
|
1521
|
+
}
|
|
1522
|
+
async tryIncrementSolveCount(_ctx, id) {
|
|
1523
|
+
const all = await this.readAll();
|
|
1524
|
+
const l = all.links[id];
|
|
1525
|
+
if (!l || !this.isLive(l)) return null;
|
|
1526
|
+
if (l.maxSolves != null && l.solveCount >= l.maxSolves) return null;
|
|
1527
|
+
l.solveCount += 1;
|
|
1528
|
+
await this.writeAll(all);
|
|
1529
|
+
return l.solveCount;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const MAX_RECENT_RUNS$1 = 20;
|
|
1533
|
+
const empty$1 = () => ({ users: [] });
|
|
1534
|
+
function emptyRow(userId) {
|
|
1535
|
+
return {
|
|
1536
|
+
userId,
|
|
1537
|
+
platformPermissions: [],
|
|
1538
|
+
starredDefinitions: [],
|
|
1539
|
+
recentRuns: []
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
function createLocalUserDataStore(filePath) {
|
|
1543
|
+
async function findOrThrow(userId) {
|
|
1544
|
+
const file = await readJsonFile(filePath, empty$1());
|
|
1545
|
+
const row = file.users.find((u) => u.userId === userId);
|
|
1546
|
+
if (!row) throw new ProviderError(`User-data row "${userId}" not found`, 404);
|
|
1547
|
+
return { file, row };
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
async ensure(userId) {
|
|
1551
|
+
const file = await readJsonFile(filePath, empty$1());
|
|
1552
|
+
if (file.users.some((u) => u.userId === userId)) return;
|
|
1553
|
+
file.users.push(emptyRow(userId));
|
|
1554
|
+
await writeJsonFile(filePath, file);
|
|
1555
|
+
},
|
|
1556
|
+
async findById(userId) {
|
|
1557
|
+
const { users } = await readJsonFile(filePath, empty$1());
|
|
1558
|
+
return users.find((u) => u.userId === userId) ?? null;
|
|
1559
|
+
},
|
|
1560
|
+
async listAll() {
|
|
1561
|
+
const { users } = await readJsonFile(filePath, empty$1());
|
|
1562
|
+
return users;
|
|
1563
|
+
},
|
|
1564
|
+
async updatePermissions(userId, permissions) {
|
|
1565
|
+
const { file, row } = await findOrThrow(userId);
|
|
1566
|
+
row.platformPermissions = permissions;
|
|
1567
|
+
await writeJsonFile(filePath, file);
|
|
1568
|
+
},
|
|
1569
|
+
async updateDisplayName(userId, displayName) {
|
|
1570
|
+
const { file, row } = await findOrThrow(userId);
|
|
1571
|
+
if (displayName === void 0) {
|
|
1572
|
+
delete row.displayName;
|
|
1573
|
+
} else {
|
|
1574
|
+
row.displayName = displayName;
|
|
1575
|
+
}
|
|
1576
|
+
await writeJsonFile(filePath, file);
|
|
1577
|
+
},
|
|
1578
|
+
async starDefinition(userId, definitionId) {
|
|
1579
|
+
const { file, row } = await findOrThrow(userId);
|
|
1580
|
+
if (!row.starredDefinitions.includes(definitionId)) {
|
|
1581
|
+
row.starredDefinitions.push(definitionId);
|
|
1582
|
+
await writeJsonFile(filePath, file);
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
async unstarDefinition(userId, definitionId) {
|
|
1586
|
+
const { file, row } = await findOrThrow(userId);
|
|
1587
|
+
row.starredDefinitions = row.starredDefinitions.filter((d) => d !== definitionId);
|
|
1588
|
+
await writeJsonFile(filePath, file);
|
|
1589
|
+
},
|
|
1590
|
+
async recordRun(userId, run) {
|
|
1591
|
+
const { file, row } = await findOrThrow(userId);
|
|
1592
|
+
row.recentRuns = [
|
|
1593
|
+
run,
|
|
1594
|
+
...row.recentRuns.filter((r) => r.definitionId !== run.definitionId)
|
|
1595
|
+
].slice(0, MAX_RECENT_RUNS$1);
|
|
1596
|
+
await writeJsonFile(filePath, file);
|
|
1597
|
+
},
|
|
1598
|
+
async deleteUser(userId) {
|
|
1599
|
+
const file = await readJsonFile(filePath, empty$1());
|
|
1600
|
+
const before = file.users.length;
|
|
1601
|
+
file.users = file.users.filter((u) => u.userId !== userId);
|
|
1602
|
+
if (file.users.length === before) {
|
|
1603
|
+
throw new ProviderError(`User-data row "${userId}" not found`, 404);
|
|
1604
|
+
}
|
|
1605
|
+
await writeJsonFile(filePath, file);
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function toProfile(u) {
|
|
1610
|
+
return {
|
|
1611
|
+
userId: u.userId,
|
|
1612
|
+
displayName: u.displayName,
|
|
1613
|
+
starredDefinitions: u.starredDefinitions ?? [],
|
|
1614
|
+
recentRuns: u.recentRuns ?? []
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
class LocalUserProfileProvider {
|
|
1618
|
+
data;
|
|
1619
|
+
constructor(userDataFilePath) {
|
|
1620
|
+
this.data = createLocalUserDataStore(userDataFilePath);
|
|
1621
|
+
}
|
|
1622
|
+
static fromEnv(env) {
|
|
1623
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1624
|
+
return new LocalUserProfileProvider(path.join(env.DATA_PATH, "user-data.json"));
|
|
1625
|
+
}
|
|
1626
|
+
async getProfile(ctx, userId) {
|
|
1627
|
+
assertCanAccess$1(ctx, userId);
|
|
1628
|
+
const u = await this.data.findById(userId);
|
|
1629
|
+
return u ? toProfile(u) : null;
|
|
1630
|
+
}
|
|
1631
|
+
async getProfiles(_ctx, userIds) {
|
|
1632
|
+
const all = await this.data.listAll();
|
|
1633
|
+
const wanted = new Set(userIds);
|
|
1634
|
+
return all.filter((u) => wanted.has(u.userId)).map(toProfile);
|
|
1635
|
+
}
|
|
1636
|
+
async updateProfile(ctx, userId, patch) {
|
|
1637
|
+
assertCanAccess$1(ctx, userId);
|
|
1638
|
+
try {
|
|
1639
|
+
if (patch.displayName !== void 0) {
|
|
1640
|
+
await this.data.updateDisplayName(userId, patch.displayName);
|
|
1641
|
+
}
|
|
1642
|
+
return "ok";
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
1645
|
+
throw err;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async starDefinition(ctx, userId, definitionId) {
|
|
1649
|
+
assertCanAccess$1(ctx, userId);
|
|
1650
|
+
try {
|
|
1651
|
+
await this.data.starDefinition(userId, definitionId);
|
|
1652
|
+
return "ok";
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
1655
|
+
throw err;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
async unstarDefinition(ctx, userId, definitionId) {
|
|
1659
|
+
assertCanAccess$1(ctx, userId);
|
|
1660
|
+
try {
|
|
1661
|
+
await this.data.unstarDefinition(userId, definitionId);
|
|
1662
|
+
return "ok";
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
1665
|
+
throw err;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
async recordRun(ctx, userId, run) {
|
|
1669
|
+
assertCanAccess$1(ctx, userId);
|
|
1670
|
+
try {
|
|
1671
|
+
await this.data.recordRun(userId, run);
|
|
1672
|
+
return "ok";
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
1675
|
+
throw err;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function assertCanAccess$1(ctx, userId) {
|
|
1680
|
+
if (ctx.system) return;
|
|
1681
|
+
if (ctx.userId === userId) return;
|
|
1682
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
1683
|
+
throw new ProviderError("Forbidden: cannot access another user’s profile", 403);
|
|
1684
|
+
}
|
|
1685
|
+
class LocalPlatformPermissionStore {
|
|
1686
|
+
data;
|
|
1687
|
+
static fromEnv(env) {
|
|
1688
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1689
|
+
return new LocalPlatformPermissionStore(path.join(env.DATA_PATH, "user-data.json"));
|
|
1690
|
+
}
|
|
1691
|
+
constructor(userDataFilePath) {
|
|
1692
|
+
this.data = createLocalUserDataStore(userDataFilePath);
|
|
1693
|
+
}
|
|
1694
|
+
async getFor(ctx, userId) {
|
|
1695
|
+
assertCanRead$1(ctx, userId);
|
|
1696
|
+
const row = await this.data.findById(userId);
|
|
1697
|
+
return row?.platformPermissions ?? [];
|
|
1698
|
+
}
|
|
1699
|
+
async getForBatch(ctx, userIds) {
|
|
1700
|
+
assertCanReadBatch(ctx);
|
|
1701
|
+
const all = await this.data.listAll();
|
|
1702
|
+
const wanted = new Set(userIds);
|
|
1703
|
+
const out = /* @__PURE__ */ new Map();
|
|
1704
|
+
for (const u of all) {
|
|
1705
|
+
if (wanted.has(u.userId)) out.set(u.userId, u.platformPermissions ?? []);
|
|
1706
|
+
}
|
|
1707
|
+
return out;
|
|
1708
|
+
}
|
|
1709
|
+
async set(ctx, userId, permissions) {
|
|
1710
|
+
assertAdmin$1(ctx);
|
|
1711
|
+
const target = await this.data.findById(userId);
|
|
1712
|
+
if (!target) return "not_found";
|
|
1713
|
+
const wasAdmin = target.platformPermissions.includes("instance_admin");
|
|
1714
|
+
const willBeAdmin = permissions.includes("instance_admin");
|
|
1715
|
+
if (wasAdmin && !willBeAdmin) {
|
|
1716
|
+
const others = await this.countOtherAdmins(userId);
|
|
1717
|
+
if (others === 0) return "last_admin";
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
await this.data.updatePermissions(userId, [...permissions]);
|
|
1721
|
+
return "ok";
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
1724
|
+
throw err;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
async hasInstanceAdmin(_ctx) {
|
|
1728
|
+
const all = await this.data.listAll();
|
|
1729
|
+
return all.some((u) => u.platformPermissions.includes("instance_admin"));
|
|
1730
|
+
}
|
|
1731
|
+
async countInstanceAdminsExcluding(_ctx, excludeUserId) {
|
|
1732
|
+
return this.countOtherAdmins(excludeUserId);
|
|
1733
|
+
}
|
|
1734
|
+
async countOtherAdmins(excludeUserId) {
|
|
1735
|
+
const all = await this.data.listAll();
|
|
1736
|
+
return all.filter(
|
|
1737
|
+
(u) => u.userId !== excludeUserId && u.platformPermissions.includes("instance_admin")
|
|
1738
|
+
).length;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function assertCanRead$1(ctx, userId) {
|
|
1742
|
+
if (ctx.system) return;
|
|
1743
|
+
if (ctx.userId === userId) return;
|
|
1744
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
1745
|
+
throw new ProviderError("Forbidden: cannot read another user’s permissions", 403);
|
|
1746
|
+
}
|
|
1747
|
+
function assertAdmin$1(ctx) {
|
|
1748
|
+
if (ctx.system) return;
|
|
1749
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
1750
|
+
throw new ProviderError("Forbidden: instance admin required", 403);
|
|
1751
|
+
}
|
|
1752
|
+
function assertCanReadBatch(ctx) {
|
|
1753
|
+
if (ctx.system) return;
|
|
1754
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
1755
|
+
if (hasPermission(ctx, "manage_instance_users")) return;
|
|
1756
|
+
throw new ProviderError("Forbidden: instance admin or manage_instance_users required", 403);
|
|
1757
|
+
}
|
|
1758
|
+
class LocalDataProvider {
|
|
1759
|
+
orgs;
|
|
1760
|
+
projects;
|
|
1761
|
+
definitions;
|
|
1762
|
+
computeServer;
|
|
1763
|
+
invites;
|
|
1764
|
+
shareLinks;
|
|
1765
|
+
userProfile;
|
|
1766
|
+
permissions;
|
|
1767
|
+
platformProjectGrants;
|
|
1768
|
+
userData;
|
|
1769
|
+
constructor(stores, userData) {
|
|
1770
|
+
this.orgs = stores.orgs;
|
|
1771
|
+
this.projects = stores.projects;
|
|
1772
|
+
this.definitions = stores.definitions;
|
|
1773
|
+
this.computeServer = stores.computeServer;
|
|
1774
|
+
this.invites = stores.invites;
|
|
1775
|
+
this.shareLinks = stores.shareLinks;
|
|
1776
|
+
this.userProfile = stores.userProfile;
|
|
1777
|
+
this.permissions = stores.permissions;
|
|
1778
|
+
this.platformProjectGrants = stores.platformProjectGrants;
|
|
1779
|
+
this.userData = userData;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Idempotently register a user in the data layer. Called from
|
|
1783
|
+
* `hooks.server.ts` on every authed request — the local equivalent of
|
|
1784
|
+
* Supabase's `handle_new_auth_user` trigger. After this completes the
|
|
1785
|
+
* user has an empty row in `user-data.json` that the permissions and
|
|
1786
|
+
* profile stores can read and update.
|
|
1787
|
+
*
|
|
1788
|
+
* `ctx` is unused — registration runs as a system operation regardless of
|
|
1789
|
+
* the calling user. Argument is kept for interface symmetry with adapters
|
|
1790
|
+
* that need it.
|
|
1791
|
+
*/
|
|
1792
|
+
async ensureUser(_ctx, userId) {
|
|
1793
|
+
await this.userData.ensure(userId);
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Cascade hook called after the auth provider deletes a user. Removes the
|
|
1797
|
+
* matching `user-data.json` row so the data layer doesn't accumulate
|
|
1798
|
+
* orphans. Tolerates missing rows.
|
|
1799
|
+
*/
|
|
1800
|
+
async onUserDeleted(_ctx, userId) {
|
|
1801
|
+
try {
|
|
1802
|
+
await this.userData.deleteUser(userId);
|
|
1803
|
+
} catch {
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
static fromEnv(env, events = new NoopEventSink()) {
|
|
1807
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1808
|
+
const dataPath = env.DATA_PATH;
|
|
1809
|
+
const userDataFilePath = path.join(dataPath, "user-data.json");
|
|
1810
|
+
const loader = new LocalOrgStoreLoader(dataPath);
|
|
1811
|
+
const platformProjectGrants = LocalPlatformProjectGrantStore.fromEnv(env);
|
|
1812
|
+
const invites = LocalInviteStore.fromEnv(env, events);
|
|
1813
|
+
const computeServer = LocalComputeServerStore.fromEnv(env);
|
|
1814
|
+
const projects = new LocalProjectStore({ loader, grants: platformProjectGrants, events });
|
|
1815
|
+
const definitions = new LocalDefinitionStore(dataPath, void 0, events);
|
|
1816
|
+
const shareLinks = new LocalShareLinkStore({
|
|
1817
|
+
filePath: path.join(dataPath, "share-links.json"),
|
|
1818
|
+
events
|
|
1819
|
+
});
|
|
1820
|
+
const orgs = new LocalOrgStore({
|
|
1821
|
+
loader,
|
|
1822
|
+
invites,
|
|
1823
|
+
computeServer,
|
|
1824
|
+
grants: platformProjectGrants,
|
|
1825
|
+
events
|
|
1826
|
+
});
|
|
1827
|
+
definitions.setProjectProvider(projects);
|
|
1828
|
+
shareLinks.setDefinitionProvider(definitions);
|
|
1829
|
+
const userData = createLocalUserDataStore(userDataFilePath);
|
|
1830
|
+
return new LocalDataProvider(
|
|
1831
|
+
{
|
|
1832
|
+
orgs,
|
|
1833
|
+
projects,
|
|
1834
|
+
definitions,
|
|
1835
|
+
computeServer,
|
|
1836
|
+
invites,
|
|
1837
|
+
shareLinks,
|
|
1838
|
+
userProfile: new LocalUserProfileProvider(userDataFilePath),
|
|
1839
|
+
permissions: new LocalPlatformPermissionStore(userDataFilePath),
|
|
1840
|
+
platformProjectGrants
|
|
1841
|
+
},
|
|
1842
|
+
userData
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
class LocalStorageProvider {
|
|
1847
|
+
basePath;
|
|
1848
|
+
publicUrlBase;
|
|
1849
|
+
static fromEnv(env) {
|
|
1850
|
+
if (!env.DATA_PATH) throw new Error("Missing required env var: DATA_PATH");
|
|
1851
|
+
return new LocalStorageProvider(env.DATA_PATH, "/api/files");
|
|
1852
|
+
}
|
|
1853
|
+
constructor(basePath, publicUrlBase = "/api/files") {
|
|
1854
|
+
this.basePath = basePath;
|
|
1855
|
+
this.publicUrlBase = publicUrlBase;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Resolve a caller-provided path under basePath, rejecting anything that
|
|
1859
|
+
* would escape the root. Last line of defense against traversal — while
|
|
1860
|
+
* platform-side helpers (definitionPaths) also assert safe keys, this
|
|
1861
|
+
* adapter is reached by any IStorageProvider caller.
|
|
1862
|
+
*/
|
|
1863
|
+
resolvePath(storagePath) {
|
|
1864
|
+
const base = path.resolve(this.basePath);
|
|
1865
|
+
const full = path.resolve(base, storagePath);
|
|
1866
|
+
if (full !== base && !full.startsWith(base + path.sep)) {
|
|
1867
|
+
throw new Error(`Path escapes base: ${storagePath}`);
|
|
1868
|
+
}
|
|
1869
|
+
return full;
|
|
1870
|
+
}
|
|
1871
|
+
async get(storagePath) {
|
|
1872
|
+
try {
|
|
1873
|
+
const buffer = await fs.readFile(this.resolvePath(storagePath));
|
|
1874
|
+
return new Uint8Array(buffer);
|
|
1875
|
+
} catch (err) {
|
|
1876
|
+
if (err.code === "ENOENT") return null;
|
|
1877
|
+
throw err;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
async put(storagePath, data, contentType) {
|
|
1881
|
+
const transcoded = await transcodeImageIfNeeded(data, contentType, storagePath);
|
|
1882
|
+
const fullPath = this.resolvePath(transcoded.path);
|
|
1883
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
1884
|
+
await fs.writeFile(fullPath, Buffer.from(transcoded.data));
|
|
1885
|
+
}
|
|
1886
|
+
async delete(storagePath) {
|
|
1887
|
+
try {
|
|
1888
|
+
await fs.unlink(this.resolvePath(storagePath));
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
if (err.code !== "ENOENT") throw err;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
async deletePrefix(prefix) {
|
|
1894
|
+
const fullPath = this.resolvePath(prefix);
|
|
1895
|
+
await fs.rm(fullPath, { recursive: true, force: true });
|
|
1896
|
+
}
|
|
1897
|
+
getPublicUrl(storagePath) {
|
|
1898
|
+
return `${this.publicUrlBase}/${storagePath}`;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
class SupabaseStorageProvider {
|
|
1902
|
+
client;
|
|
1903
|
+
publicBucket;
|
|
1904
|
+
privateBucket;
|
|
1905
|
+
publicBaseUrl;
|
|
1906
|
+
privateUrlPrefix;
|
|
1907
|
+
static fromEnv(env) {
|
|
1908
|
+
const supabaseUrl = env.SUPABASE_URL;
|
|
1909
|
+
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
1910
|
+
if (!supabaseUrl) throw new Error("Missing required env var: SUPABASE_URL");
|
|
1911
|
+
if (!serviceRoleKey) throw new Error("Missing required env var: SUPABASE_SERVICE_ROLE_KEY");
|
|
1912
|
+
return new SupabaseStorageProvider({
|
|
1913
|
+
supabaseUrl,
|
|
1914
|
+
serviceRoleKey,
|
|
1915
|
+
publicBucket: env.SUPABASE_PUBLIC_BUCKET,
|
|
1916
|
+
privateBucket: env.SUPABASE_PRIVATE_BUCKET,
|
|
1917
|
+
privateUrlPrefix: env.SUPABASE_PRIVATE_URL_PREFIX
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
constructor(config) {
|
|
1921
|
+
this.client = createClient(config.supabaseUrl, config.serviceRoleKey, {
|
|
1922
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
1923
|
+
});
|
|
1924
|
+
this.publicBucket = config.publicBucket ?? "selva-public";
|
|
1925
|
+
this.privateBucket = config.privateBucket ?? "selva-private";
|
|
1926
|
+
this.publicBaseUrl = `${config.supabaseUrl.replace(/\/$/, "")}/storage/v1/object/public/${this.publicBucket}`;
|
|
1927
|
+
this.privateUrlPrefix = (config.privateUrlPrefix ?? "/api/files").replace(/\/$/, "");
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Route a storage path to a bucket. Any `.gh`/`.ghx` source file is
|
|
1931
|
+
* private — confidentiality is determined by extension, not location, so
|
|
1932
|
+
* a path-scheme rename (e.g. `definition.gh` → `versions/v1.gh`) can't
|
|
1933
|
+
* silently move source files into the public bucket. Everything else
|
|
1934
|
+
* (covers, archives, thumbnails) is public.
|
|
1935
|
+
*/
|
|
1936
|
+
bucketFor(storagePath) {
|
|
1937
|
+
return /\.(gh|ghx)$/i.test(storagePath) ? this.privateBucket : this.publicBucket;
|
|
1938
|
+
}
|
|
1939
|
+
async get(storagePath) {
|
|
1940
|
+
const bucket = this.bucketFor(storagePath);
|
|
1941
|
+
const { data, error } = await this.client.storage.from(bucket).download(storagePath);
|
|
1942
|
+
if (error) {
|
|
1943
|
+
const msg = error.message ?? "";
|
|
1944
|
+
if (/not found/i.test(msg) || /no such key/i.test(msg)) return null;
|
|
1945
|
+
throw error;
|
|
1946
|
+
}
|
|
1947
|
+
const buffer = await data.arrayBuffer();
|
|
1948
|
+
return new Uint8Array(buffer);
|
|
1949
|
+
}
|
|
1950
|
+
async put(storagePath, data, contentType) {
|
|
1951
|
+
const transcoded = await transcodeImageIfNeeded(data, contentType, storagePath);
|
|
1952
|
+
const bucket = this.bucketFor(transcoded.path);
|
|
1953
|
+
const { error } = await this.client.storage.from(bucket).upload(transcoded.path, transcoded.data, {
|
|
1954
|
+
contentType: transcoded.contentType,
|
|
1955
|
+
upsert: true
|
|
1956
|
+
});
|
|
1957
|
+
if (error) throw error;
|
|
1958
|
+
}
|
|
1959
|
+
async delete(storagePath) {
|
|
1960
|
+
const bucket = this.bucketFor(storagePath);
|
|
1961
|
+
const { error } = await this.client.storage.from(bucket).remove([storagePath]);
|
|
1962
|
+
if (error) throw error;
|
|
1963
|
+
}
|
|
1964
|
+
async deletePrefix(prefix) {
|
|
1965
|
+
await Promise.all([
|
|
1966
|
+
this.deletePrefixInBucket(this.publicBucket, prefix),
|
|
1967
|
+
this.deletePrefixInBucket(this.privateBucket, prefix)
|
|
1968
|
+
]);
|
|
1969
|
+
}
|
|
1970
|
+
async deletePrefixInBucket(bucket, prefix) {
|
|
1971
|
+
const normalized = prefix.replace(/\/+$/, "");
|
|
1972
|
+
const keys = await this.listAllKeys(bucket, normalized);
|
|
1973
|
+
if (keys.length === 0) return;
|
|
1974
|
+
for (let i = 0; i < keys.length; i += 1e3) {
|
|
1975
|
+
const slice = keys.slice(i, i + 1e3);
|
|
1976
|
+
const { error } = await this.client.storage.from(bucket).remove(slice);
|
|
1977
|
+
if (error) throw error;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Recursively list every key under a prefix. `storage-js`'s `list` API
|
|
1982
|
+
* only returns the immediate children of a "folder", so we DFS.
|
|
1983
|
+
*/
|
|
1984
|
+
async listAllKeys(bucket, prefix) {
|
|
1985
|
+
const out = [];
|
|
1986
|
+
const stack = [prefix];
|
|
1987
|
+
while (stack.length > 0) {
|
|
1988
|
+
const dir = stack.pop();
|
|
1989
|
+
const { data, error } = await this.client.storage.from(bucket).list(dir, {
|
|
1990
|
+
limit: 1e3,
|
|
1991
|
+
sortBy: { column: "name", order: "asc" }
|
|
1992
|
+
});
|
|
1993
|
+
if (error) throw error;
|
|
1994
|
+
for (const entry of data ?? []) {
|
|
1995
|
+
const full = dir ? `${dir}/${entry.name}` : entry.name;
|
|
1996
|
+
if (entry.id === null) {
|
|
1997
|
+
stack.push(full);
|
|
1998
|
+
} else {
|
|
1999
|
+
out.push(full);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return out;
|
|
2004
|
+
}
|
|
2005
|
+
getPublicUrl(storagePath) {
|
|
2006
|
+
if (this.bucketFor(storagePath) === this.publicBucket) {
|
|
2007
|
+
return `${this.publicBaseUrl}/${storagePath}`;
|
|
2008
|
+
}
|
|
2009
|
+
return `${this.privateUrlPrefix}/${storagePath}`;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function decodeCursor(cursor) {
|
|
2013
|
+
if (!cursor) return 0;
|
|
2014
|
+
const n = parseInt(cursor, 10);
|
|
2015
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
2016
|
+
}
|
|
2017
|
+
function encodeCursor(offset) {
|
|
2018
|
+
return String(offset);
|
|
2019
|
+
}
|
|
2020
|
+
function toRange(opts) {
|
|
2021
|
+
const limit = Math.min(Math.max(opts?.limit ?? DEFAULT_PAGE_LIMIT, 1), MAX_PAGE_LIMIT);
|
|
2022
|
+
const from = decodeCursor(opts?.cursor);
|
|
2023
|
+
return { from, to: from + limit - 1, limit };
|
|
2024
|
+
}
|
|
2025
|
+
function nextCursorFromRange(range, returnedCount, totalCount) {
|
|
2026
|
+
const consumed = range.from + returnedCount;
|
|
2027
|
+
if (totalCount != null) {
|
|
2028
|
+
return consumed < totalCount ? encodeCursor(consumed) : void 0;
|
|
2029
|
+
}
|
|
2030
|
+
return returnedCount >= range.limit ? encodeCursor(consumed) : void 0;
|
|
2031
|
+
}
|
|
2032
|
+
function orderColumn(orderBy) {
|
|
2033
|
+
switch (orderBy) {
|
|
2034
|
+
case "name":
|
|
2035
|
+
return "name";
|
|
2036
|
+
case "updatedAt":
|
|
2037
|
+
return "updated_at";
|
|
2038
|
+
case "createdAt":
|
|
2039
|
+
default:
|
|
2040
|
+
return "created_at";
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
class SupabaseOrgStore {
|
|
2044
|
+
constructor(clients, events = new NoopEventSink()) {
|
|
2045
|
+
this.clients = clients;
|
|
2046
|
+
this.events = events;
|
|
2047
|
+
}
|
|
2048
|
+
clients;
|
|
2049
|
+
events;
|
|
2050
|
+
async listOrgs(ctx, opts) {
|
|
2051
|
+
const range = toRange(opts);
|
|
2052
|
+
const direction = opts?.orderDir ?? "desc";
|
|
2053
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("orgs").select("*", { count: "exact" }).is("deleted_at", null).order(orderColumn(opts?.orderBy), { ascending: direction === "asc" }).range(range.from, range.to);
|
|
2054
|
+
if (error) throw mapError$7(error);
|
|
2055
|
+
const items = (data ?? []).map(rowToOrg);
|
|
2056
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2057
|
+
}
|
|
2058
|
+
async getOrg(ctx, id) {
|
|
2059
|
+
const { data, error } = await this.clients.forRequest(ctx).from("orgs").select("*").eq("id", id).is("deleted_at", null).maybeSingle();
|
|
2060
|
+
if (error) throw mapError$7(error);
|
|
2061
|
+
return data ? rowToOrg(data) : null;
|
|
2062
|
+
}
|
|
2063
|
+
async getOrgBySlug(ctx, slug) {
|
|
2064
|
+
const { data, error } = await this.clients.forRequest(ctx).from("orgs").select("*").eq("slug", slug).is("deleted_at", null).maybeSingle();
|
|
2065
|
+
if (error) throw mapError$7(error);
|
|
2066
|
+
return data ? rowToOrg(data) : null;
|
|
2067
|
+
}
|
|
2068
|
+
async createOrg(ctx, org) {
|
|
2069
|
+
const client = this.clients.forRequest(ctx);
|
|
2070
|
+
const { error } = await client.from("orgs").insert(orgToRow(org));
|
|
2071
|
+
if (error) throw mapError$7(error);
|
|
2072
|
+
const { error: memberError } = await client.from("org_members").upsert(
|
|
2073
|
+
{
|
|
2074
|
+
org_id: org.id,
|
|
2075
|
+
user_id: org.ownerId,
|
|
2076
|
+
role: "owner",
|
|
2077
|
+
permissions: [...DEFAULT_ORG_PERMISSIONS.owner],
|
|
2078
|
+
joined_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2079
|
+
deleted_at: null
|
|
2080
|
+
},
|
|
2081
|
+
{ onConflict: "org_id,user_id" }
|
|
2082
|
+
);
|
|
2083
|
+
if (memberError) throw mapError$7(memberError);
|
|
2084
|
+
await this.events.emit({ type: "org.created", orgId: org.id, actorId: actorFrom(ctx) });
|
|
2085
|
+
}
|
|
2086
|
+
async updateOrg(ctx, id, patch) {
|
|
2087
|
+
const row = {};
|
|
2088
|
+
if (patch.name !== void 0) row.name = patch.name;
|
|
2089
|
+
if (patch.slug !== void 0) row.slug = patch.slug;
|
|
2090
|
+
if (Object.keys(row).length === 0) return;
|
|
2091
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2092
|
+
const { error, data } = await this.clients.forRequest(ctx).from("orgs").update(row).eq("id", id).is("deleted_at", null).select("id");
|
|
2093
|
+
if (error) throw mapError$7(error);
|
|
2094
|
+
if (!data || data.length === 0) throw new ProviderError(`Org '${id}' not found`, 404);
|
|
2095
|
+
}
|
|
2096
|
+
async deleteOrg(ctx, id) {
|
|
2097
|
+
const client = this.clients.forRequest(ctx);
|
|
2098
|
+
const stamp = auditSoftDelete(ctx, ctx.userId);
|
|
2099
|
+
const stampRow = stampToRow$1(stamp);
|
|
2100
|
+
const { error: orgErr, data: orgData } = await client.from("orgs").update(stampRow).eq("id", id).is("deleted_at", null).select("id");
|
|
2101
|
+
if (orgErr) throw mapError$7(orgErr);
|
|
2102
|
+
if (!orgData || orgData.length === 0) throw new ProviderError(`Org '${id}' not found`, 404);
|
|
2103
|
+
const { error: omErr } = await client.from("org_members").update(stampRow).eq("org_id", id).is("deleted_at", null);
|
|
2104
|
+
if (omErr) throw mapError$7(omErr);
|
|
2105
|
+
const { data: orgProjects, error: projFetchErr } = await client.from("projects").select("id").eq("org_id", id).is("deleted_at", null);
|
|
2106
|
+
if (projFetchErr) throw mapError$7(projFetchErr);
|
|
2107
|
+
const projectIds = (orgProjects ?? []).map((p) => p.id);
|
|
2108
|
+
const { error: projErr } = await client.from("projects").update(stampRow).eq("org_id", id).is("deleted_at", null);
|
|
2109
|
+
if (projErr) throw mapError$7(projErr);
|
|
2110
|
+
if (projectIds.length > 0) {
|
|
2111
|
+
const { error: pmErr } = await client.from("project_members").update(stampRow).in("project_id", projectIds).is("deleted_at", null);
|
|
2112
|
+
if (pmErr) throw mapError$7(pmErr);
|
|
2113
|
+
const { error: defErr } = await client.from("definitions").update(stampRow).in("project_id", projectIds).is("deleted_at", null);
|
|
2114
|
+
if (defErr) throw mapError$7(defErr);
|
|
2115
|
+
}
|
|
2116
|
+
const { error: invErr } = await client.from("invites").delete().eq("org_id", id);
|
|
2117
|
+
if (invErr) throw mapError$7(invErr);
|
|
2118
|
+
const { error: cdErr } = await client.from("compute_server_org_defaults").delete().eq("org_id", id);
|
|
2119
|
+
if (cdErr) throw mapError$7(cdErr);
|
|
2120
|
+
const { error: shErr } = await client.from("compute_server_shares").delete().eq("org_id", id);
|
|
2121
|
+
if (shErr) throw mapError$7(shErr);
|
|
2122
|
+
const { error: csErr } = await client.from("compute_servers").delete().eq("scope", "org").eq("owner_org_id", id);
|
|
2123
|
+
if (csErr) throw mapError$7(csErr);
|
|
2124
|
+
await this.events.emit({ type: "org.deleted", orgId: id, actorId: actorFrom(ctx) });
|
|
2125
|
+
}
|
|
2126
|
+
// ============================================================================
|
|
2127
|
+
// Org members
|
|
2128
|
+
// ============================================================================
|
|
2129
|
+
async listOrgMembers(ctx, orgId, opts) {
|
|
2130
|
+
const range = toRange(opts);
|
|
2131
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("org_members").select("*", { count: "exact" }).eq("org_id", orgId).is("deleted_at", null).order("joined_at", { ascending: (opts?.orderDir ?? "desc") === "asc" }).range(range.from, range.to);
|
|
2132
|
+
if (error) throw mapError$7(error);
|
|
2133
|
+
const items = (data ?? []).map(rowToOrgMember);
|
|
2134
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2135
|
+
}
|
|
2136
|
+
async getOrgMember(ctx, orgId, userId) {
|
|
2137
|
+
const { data, error } = await this.clients.forRequest(ctx).from("org_members").select("*").eq("org_id", orgId).eq("user_id", userId).is("deleted_at", null).maybeSingle();
|
|
2138
|
+
if (error) throw mapError$7(error);
|
|
2139
|
+
return data ? rowToOrgMember(data) : null;
|
|
2140
|
+
}
|
|
2141
|
+
async findUserMembership(ctx, userId) {
|
|
2142
|
+
const { data, error } = await this.clients.forRequest(ctx).from("org_members").select("*, org:orgs!inner(*)").eq("user_id", userId).is("deleted_at", null).is("org.deleted_at", null).order("joined_at", { ascending: true }).limit(1).maybeSingle();
|
|
2143
|
+
if (error) throw mapError$7(error);
|
|
2144
|
+
if (!data) return null;
|
|
2145
|
+
const row = data;
|
|
2146
|
+
const orgRow = Array.isArray(row.org) ? row.org[0] : row.org;
|
|
2147
|
+
if (!orgRow) return null;
|
|
2148
|
+
const { org: _omit, ...memberRow } = row;
|
|
2149
|
+
return {
|
|
2150
|
+
org: rowToOrg(orgRow),
|
|
2151
|
+
member: rowToOrgMember(memberRow)
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
async addOrgMember(ctx, member) {
|
|
2155
|
+
const row = {
|
|
2156
|
+
...memberToRow(member),
|
|
2157
|
+
deleted_at: null
|
|
2158
|
+
};
|
|
2159
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2160
|
+
const { error } = await this.clients.forRequest(ctx).from("org_members").upsert(row, { onConflict: "org_id,user_id" });
|
|
2161
|
+
if (error) throw mapError$7(error);
|
|
2162
|
+
await this.events.emit({
|
|
2163
|
+
type: "org_member.added",
|
|
2164
|
+
orgId: member.orgId,
|
|
2165
|
+
userId: member.userId,
|
|
2166
|
+
actorId: actorFrom(ctx)
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
async updateOrgMemberRole(ctx, orgId, userId, role) {
|
|
2170
|
+
const row = {
|
|
2171
|
+
role,
|
|
2172
|
+
permissions: [...DEFAULT_ORG_PERMISSIONS[role]]
|
|
2173
|
+
};
|
|
2174
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2175
|
+
const { data, error } = await this.clients.forRequest(ctx).from("org_members").update(row).eq("org_id", orgId).eq("user_id", userId).is("deleted_at", null).select("user_id");
|
|
2176
|
+
if (error) throw mapError$7(error);
|
|
2177
|
+
if (!data || data.length === 0)
|
|
2178
|
+
throw new ProviderError(`Org member '${userId}' not found`, 404);
|
|
2179
|
+
await this.events.emit({
|
|
2180
|
+
type: "org_member.role_changed",
|
|
2181
|
+
orgId,
|
|
2182
|
+
userId,
|
|
2183
|
+
role,
|
|
2184
|
+
actorId: actorFrom(ctx)
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
async updateOrgMemberPermissions(ctx, orgId, userId, permissions) {
|
|
2188
|
+
const row = { permissions: [...permissions] };
|
|
2189
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2190
|
+
const { data, error } = await this.clients.forRequest(ctx).from("org_members").update(row).eq("org_id", orgId).eq("user_id", userId).is("deleted_at", null).select("user_id");
|
|
2191
|
+
if (error) throw mapError$7(error);
|
|
2192
|
+
if (!data || data.length === 0)
|
|
2193
|
+
throw new ProviderError(`Org member '${userId}' not found`, 404);
|
|
2194
|
+
await this.events.emit({
|
|
2195
|
+
type: "org_member.permissions_changed",
|
|
2196
|
+
orgId,
|
|
2197
|
+
userId,
|
|
2198
|
+
permissions: [...permissions],
|
|
2199
|
+
actorId: actorFrom(ctx)
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
async removeOrgMember(ctx, orgId, userId) {
|
|
2203
|
+
const client = this.clients.forRequest(ctx);
|
|
2204
|
+
const stamp = auditSoftDelete(ctx, ctx.userId);
|
|
2205
|
+
const stampRow = stampToRow$1(stamp);
|
|
2206
|
+
const { data: orgProjects, error: projError } = await client.from("projects").select("id").eq("org_id", orgId).is("deleted_at", null);
|
|
2207
|
+
if (projError) throw mapError$7(projError);
|
|
2208
|
+
const projectIds = (orgProjects ?? []).map((p) => p.id);
|
|
2209
|
+
if (projectIds.length > 0) {
|
|
2210
|
+
const { error: pmError } = await client.from("project_members").update(stampRow).in("project_id", projectIds).eq("user_id", userId).is("deleted_at", null);
|
|
2211
|
+
if (pmError) throw mapError$7(pmError);
|
|
2212
|
+
}
|
|
2213
|
+
const { error } = await client.from("org_members").update(stampRow).eq("org_id", orgId).eq("user_id", userId).is("deleted_at", null);
|
|
2214
|
+
if (error) throw mapError$7(error);
|
|
2215
|
+
await this.events.emit({
|
|
2216
|
+
type: "org_member.removed",
|
|
2217
|
+
orgId,
|
|
2218
|
+
userId,
|
|
2219
|
+
actorId: actorFrom(ctx)
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
function stampToRow$1(stamp) {
|
|
2224
|
+
const row = {
|
|
2225
|
+
deleted_at: stamp.deletedAt,
|
|
2226
|
+
updated_at: stamp.updatedAt
|
|
2227
|
+
};
|
|
2228
|
+
if (stamp.updatedBy) row.updated_by = stamp.updatedBy;
|
|
2229
|
+
return row;
|
|
2230
|
+
}
|
|
2231
|
+
function rowToOrg(row) {
|
|
2232
|
+
return {
|
|
2233
|
+
id: row.id,
|
|
2234
|
+
name: row.name,
|
|
2235
|
+
slug: row.slug,
|
|
2236
|
+
ownerId: row.owner_id,
|
|
2237
|
+
createdBy: row.created_by ?? row.owner_id,
|
|
2238
|
+
updatedBy: row.updated_by ?? row.owner_id,
|
|
2239
|
+
createdAt: row.created_at,
|
|
2240
|
+
updatedAt: row.updated_at,
|
|
2241
|
+
deletedAt: row.deleted_at ?? null
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
function orgToRow(org) {
|
|
2245
|
+
return {
|
|
2246
|
+
id: org.id,
|
|
2247
|
+
name: org.name,
|
|
2248
|
+
slug: org.slug,
|
|
2249
|
+
owner_id: org.ownerId,
|
|
2250
|
+
created_by: org.createdBy,
|
|
2251
|
+
updated_by: org.updatedBy,
|
|
2252
|
+
created_at: org.createdAt,
|
|
2253
|
+
updated_at: org.updatedAt,
|
|
2254
|
+
deleted_at: org.deletedAt ?? null
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
function rowToOrgMember(row) {
|
|
2258
|
+
return {
|
|
2259
|
+
orgId: row.org_id,
|
|
2260
|
+
userId: row.user_id,
|
|
2261
|
+
role: row.role,
|
|
2262
|
+
permissions: row.permissions ?? [],
|
|
2263
|
+
joinedAt: row.joined_at,
|
|
2264
|
+
updatedAt: row.updated_at ?? row.joined_at,
|
|
2265
|
+
updatedBy: row.updated_by ?? row.user_id,
|
|
2266
|
+
deletedAt: row.deleted_at ?? null
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
function memberToRow(m) {
|
|
2270
|
+
return {
|
|
2271
|
+
org_id: m.orgId,
|
|
2272
|
+
user_id: m.userId,
|
|
2273
|
+
role: m.role,
|
|
2274
|
+
permissions: m.permissions ?? [],
|
|
2275
|
+
joined_at: m.joinedAt
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function mapError$7(e) {
|
|
2279
|
+
const pg = e;
|
|
2280
|
+
if (pg?.code === "23505") {
|
|
2281
|
+
return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
2282
|
+
}
|
|
2283
|
+
if (pg?.code === "23503") {
|
|
2284
|
+
return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
2285
|
+
}
|
|
2286
|
+
if (e instanceof Error) return e;
|
|
2287
|
+
if (e && typeof e === "object") {
|
|
2288
|
+
const obj = e;
|
|
2289
|
+
const msg = obj.message ?? obj.details ?? obj.hint ?? "Unknown Postgres error";
|
|
2290
|
+
const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
|
|
2291
|
+
Object.assign(err, obj);
|
|
2292
|
+
return err;
|
|
2293
|
+
}
|
|
2294
|
+
return new Error(String(e));
|
|
2295
|
+
}
|
|
2296
|
+
class SupabaseProjectStore {
|
|
2297
|
+
constructor(clients, events = new NoopEventSink()) {
|
|
2298
|
+
this.clients = clients;
|
|
2299
|
+
this.events = events;
|
|
2300
|
+
}
|
|
2301
|
+
clients;
|
|
2302
|
+
events;
|
|
2303
|
+
async listProjects(ctx, orgId, opts) {
|
|
2304
|
+
const range = toRange(opts);
|
|
2305
|
+
const direction = opts?.orderDir ?? "desc";
|
|
2306
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("projects").select("*", { count: "exact" }).eq("org_id", orgId).is("deleted_at", null).order(orderColumn(opts?.orderBy), { ascending: direction === "asc" }).range(range.from, range.to);
|
|
2307
|
+
if (error) throw mapError$6(error);
|
|
2308
|
+
const items = (data ?? []).map(rowToProject);
|
|
2309
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2310
|
+
}
|
|
2311
|
+
async getProject(ctx, id) {
|
|
2312
|
+
const { data, error } = await this.clients.forRequest(ctx).from("projects").select("*").eq("id", id).is("deleted_at", null).maybeSingle();
|
|
2313
|
+
if (error) throw mapError$6(error);
|
|
2314
|
+
return data ? rowToProject(data) : null;
|
|
2315
|
+
}
|
|
2316
|
+
async getProjectBySlug(ctx, orgId, slug) {
|
|
2317
|
+
const { data, error } = await this.clients.forRequest(ctx).from("projects").select("*").eq("org_id", orgId).eq("slug", slug).is("deleted_at", null).maybeSingle();
|
|
2318
|
+
if (error) throw mapError$6(error);
|
|
2319
|
+
return data ? rowToProject(data) : null;
|
|
2320
|
+
}
|
|
2321
|
+
async createProject(ctx, project) {
|
|
2322
|
+
const client = this.clients.forRequest(ctx);
|
|
2323
|
+
const { error } = await client.from("projects").insert(projectToRow(project));
|
|
2324
|
+
if (error) throw mapError$6(error);
|
|
2325
|
+
const { error: memberError } = await client.from("project_members").upsert(
|
|
2326
|
+
{
|
|
2327
|
+
project_id: project.id,
|
|
2328
|
+
user_id: project.ownerId,
|
|
2329
|
+
role: "owner",
|
|
2330
|
+
joined_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2331
|
+
deleted_at: null
|
|
2332
|
+
},
|
|
2333
|
+
{ onConflict: "project_id,user_id" }
|
|
2334
|
+
);
|
|
2335
|
+
if (memberError) throw mapError$6(memberError);
|
|
2336
|
+
await this.events.emit({
|
|
2337
|
+
type: "project.created",
|
|
2338
|
+
projectId: project.id,
|
|
2339
|
+
orgId: project.orgId,
|
|
2340
|
+
actorId: actorFrom(ctx)
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
async updateProject(ctx, id, patch) {
|
|
2344
|
+
const row = {};
|
|
2345
|
+
if (patch.name !== void 0) row.name = patch.name;
|
|
2346
|
+
if (patch.slug !== void 0) row.slug = patch.slug;
|
|
2347
|
+
if (patch.description !== void 0) row.description = patch.description;
|
|
2348
|
+
if (patch.visibility !== void 0) row.visibility = patch.visibility;
|
|
2349
|
+
if (patch.autoJoinOnUpload !== void 0) row.auto_join_on_upload = patch.autoJoinOnUpload;
|
|
2350
|
+
if (Object.keys(row).length === 0) return;
|
|
2351
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2352
|
+
const { data, error } = await this.clients.forRequest(ctx).from("projects").update(row).eq("id", id).is("deleted_at", null).select("id");
|
|
2353
|
+
if (error) throw mapError$6(error);
|
|
2354
|
+
if (!data || data.length === 0) throw new ProviderError(`Project '${id}' not found`, 404);
|
|
2355
|
+
}
|
|
2356
|
+
async deleteProject(ctx, id) {
|
|
2357
|
+
const client = this.clients.forRequest(ctx);
|
|
2358
|
+
const stamp = auditSoftDelete(ctx, ctx.userId);
|
|
2359
|
+
const stampRow = stampToRow(stamp);
|
|
2360
|
+
const { data, error } = await client.from("projects").update(stampRow).eq("id", id).is("deleted_at", null).select("id");
|
|
2361
|
+
if (error) throw mapError$6(error);
|
|
2362
|
+
if (!data || data.length === 0) throw new ProviderError(`Project '${id}' not found`, 404);
|
|
2363
|
+
const { error: pmErr } = await client.from("project_members").update(stampRow).eq("project_id", id).is("deleted_at", null);
|
|
2364
|
+
if (pmErr) throw mapError$6(pmErr);
|
|
2365
|
+
const { error: defErr } = await client.from("definitions").update(stampRow).eq("project_id", id).is("deleted_at", null);
|
|
2366
|
+
if (defErr) throw mapError$6(defErr);
|
|
2367
|
+
await this.events.emit({ type: "project.deleted", projectId: id, actorId: actorFrom(ctx) });
|
|
2368
|
+
}
|
|
2369
|
+
async reactivateProject(ctx, orgId, slug) {
|
|
2370
|
+
const client = this.clients.forRequest(ctx);
|
|
2371
|
+
const { data, error } = await client.from("projects").update({ deleted_at: null }).eq("org_id", orgId).eq("slug", slug).not("deleted_at", "is", null).select("*").maybeSingle();
|
|
2372
|
+
if (error) throw mapError$6(error);
|
|
2373
|
+
if (!data) return null;
|
|
2374
|
+
const { error: pmErr } = await client.from("project_members").update({ deleted_at: null }).eq("project_id", data.id).eq("user_id", data.owner_id).not("deleted_at", "is", null);
|
|
2375
|
+
if (pmErr) throw mapError$6(pmErr);
|
|
2376
|
+
const project = rowToProject(data);
|
|
2377
|
+
await this.events.emit({
|
|
2378
|
+
type: "project.created",
|
|
2379
|
+
projectId: project.id,
|
|
2380
|
+
orgId: project.orgId,
|
|
2381
|
+
actorId: actorFrom(ctx)
|
|
2382
|
+
});
|
|
2383
|
+
return project;
|
|
2384
|
+
}
|
|
2385
|
+
// ============================================================================
|
|
2386
|
+
// Project members
|
|
2387
|
+
// ============================================================================
|
|
2388
|
+
async listProjectMembers(ctx, projectId, opts) {
|
|
2389
|
+
const range = toRange(opts);
|
|
2390
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("project_members").select("*", { count: "exact" }).eq("project_id", projectId).is("deleted_at", null).order("joined_at", { ascending: (opts?.orderDir ?? "desc") === "asc" }).range(range.from, range.to);
|
|
2391
|
+
if (error) throw mapError$6(error);
|
|
2392
|
+
const items = (data ?? []).map(rowToProjectMember);
|
|
2393
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2394
|
+
}
|
|
2395
|
+
async getProjectMember(ctx, projectId, userId) {
|
|
2396
|
+
const { data, error } = await this.clients.forRequest(ctx).from("project_members").select("*").eq("project_id", projectId).eq("user_id", userId).is("deleted_at", null).maybeSingle();
|
|
2397
|
+
if (error) throw mapError$6(error);
|
|
2398
|
+
return data ? rowToProjectMember(data) : null;
|
|
2399
|
+
}
|
|
2400
|
+
async addProjectMember(ctx, member) {
|
|
2401
|
+
const row = {
|
|
2402
|
+
...projectMemberToRow(member),
|
|
2403
|
+
deleted_at: null
|
|
2404
|
+
};
|
|
2405
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2406
|
+
const { error } = await this.clients.forRequest(ctx).from("project_members").upsert(row, { onConflict: "project_id,user_id" });
|
|
2407
|
+
if (error) throw mapError$6(error);
|
|
2408
|
+
await this.events.emit({
|
|
2409
|
+
type: "project_member.added",
|
|
2410
|
+
projectId: member.projectId,
|
|
2411
|
+
userId: member.userId,
|
|
2412
|
+
actorId: actorFrom(ctx)
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
async updateProjectMemberRole(ctx, projectId, userId, role) {
|
|
2416
|
+
const row = { role };
|
|
2417
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2418
|
+
const { data, error } = await this.clients.forRequest(ctx).from("project_members").update(row).eq("project_id", projectId).eq("user_id", userId).is("deleted_at", null).select("user_id");
|
|
2419
|
+
if (error) throw mapError$6(error);
|
|
2420
|
+
if (!data || data.length === 0)
|
|
2421
|
+
throw new ProviderError(`Project member '${userId}' not found`, 404);
|
|
2422
|
+
await this.events.emit({
|
|
2423
|
+
type: "project_member.role_changed",
|
|
2424
|
+
projectId,
|
|
2425
|
+
userId,
|
|
2426
|
+
role,
|
|
2427
|
+
actorId: actorFrom(ctx)
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
async removeProjectMember(ctx, projectId, userId) {
|
|
2431
|
+
const stamp = auditSoftDelete(ctx, ctx.userId);
|
|
2432
|
+
const { error } = await this.clients.forRequest(ctx).from("project_members").update(stampToRow(stamp)).eq("project_id", projectId).eq("user_id", userId).is("deleted_at", null);
|
|
2433
|
+
if (error) throw mapError$6(error);
|
|
2434
|
+
await this.events.emit({
|
|
2435
|
+
type: "project_member.removed",
|
|
2436
|
+
projectId,
|
|
2437
|
+
userId,
|
|
2438
|
+
actorId: actorFrom(ctx)
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
function rowToProject(row) {
|
|
2443
|
+
return {
|
|
2444
|
+
id: row.id,
|
|
2445
|
+
orgId: row.org_id,
|
|
2446
|
+
name: row.name,
|
|
2447
|
+
slug: row.slug,
|
|
2448
|
+
description: row.description ?? void 0,
|
|
2449
|
+
visibility: row.visibility,
|
|
2450
|
+
ownerId: row.owner_id,
|
|
2451
|
+
createdBy: row.created_by ?? row.owner_id,
|
|
2452
|
+
updatedBy: row.updated_by ?? row.owner_id,
|
|
2453
|
+
autoJoinOnUpload: row.auto_join_on_upload ?? false,
|
|
2454
|
+
createdAt: row.created_at,
|
|
2455
|
+
updatedAt: row.updated_at,
|
|
2456
|
+
deletedAt: row.deleted_at ?? null
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
function projectToRow(p) {
|
|
2460
|
+
return {
|
|
2461
|
+
id: p.id,
|
|
2462
|
+
org_id: p.orgId,
|
|
2463
|
+
name: p.name,
|
|
2464
|
+
slug: p.slug,
|
|
2465
|
+
description: p.description ?? null,
|
|
2466
|
+
visibility: p.visibility,
|
|
2467
|
+
owner_id: p.ownerId,
|
|
2468
|
+
created_by: p.createdBy,
|
|
2469
|
+
updated_by: p.updatedBy,
|
|
2470
|
+
auto_join_on_upload: p.autoJoinOnUpload,
|
|
2471
|
+
created_at: p.createdAt,
|
|
2472
|
+
updated_at: p.updatedAt,
|
|
2473
|
+
deleted_at: p.deletedAt ?? null
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function rowToProjectMember(row) {
|
|
2477
|
+
return {
|
|
2478
|
+
projectId: row.project_id,
|
|
2479
|
+
userId: row.user_id,
|
|
2480
|
+
role: row.role,
|
|
2481
|
+
joinedAt: row.joined_at,
|
|
2482
|
+
updatedAt: row.updated_at ?? row.joined_at,
|
|
2483
|
+
updatedBy: row.updated_by ?? row.user_id,
|
|
2484
|
+
deletedAt: row.deleted_at ?? null
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function projectMemberToRow(m) {
|
|
2488
|
+
return {
|
|
2489
|
+
project_id: m.projectId,
|
|
2490
|
+
user_id: m.userId,
|
|
2491
|
+
role: m.role,
|
|
2492
|
+
joined_at: m.joinedAt
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
function stampToRow(stamp) {
|
|
2496
|
+
const row = {
|
|
2497
|
+
deleted_at: stamp.deletedAt,
|
|
2498
|
+
updated_at: stamp.updatedAt
|
|
2499
|
+
};
|
|
2500
|
+
if (stamp.updatedBy) row.updated_by = stamp.updatedBy;
|
|
2501
|
+
return row;
|
|
2502
|
+
}
|
|
2503
|
+
function mapError$6(e) {
|
|
2504
|
+
const pg = e;
|
|
2505
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
2506
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
2507
|
+
if (e instanceof Error) return e;
|
|
2508
|
+
if (e && typeof e === "object") {
|
|
2509
|
+
const obj = e;
|
|
2510
|
+
const msg = obj.message ?? obj.details ?? obj.hint ?? "Unknown Postgres error";
|
|
2511
|
+
const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
|
|
2512
|
+
Object.assign(err, obj);
|
|
2513
|
+
return err;
|
|
2514
|
+
}
|
|
2515
|
+
return new Error(String(e));
|
|
2516
|
+
}
|
|
2517
|
+
class SupabaseDefinitionStore {
|
|
2518
|
+
constructor(clients, events = new NoopEventSink()) {
|
|
2519
|
+
this.clients = clients;
|
|
2520
|
+
this.events = events;
|
|
2521
|
+
}
|
|
2522
|
+
clients;
|
|
2523
|
+
events;
|
|
2524
|
+
// ============================================================================
|
|
2525
|
+
// Definitions
|
|
2526
|
+
// ============================================================================
|
|
2527
|
+
async list(ctx, opts) {
|
|
2528
|
+
return this.runList(ctx, void 0, opts);
|
|
2529
|
+
}
|
|
2530
|
+
async listByProject(ctx, projectId, opts) {
|
|
2531
|
+
return this.runList(ctx, { projectId }, opts);
|
|
2532
|
+
}
|
|
2533
|
+
async listPublic(ctx, opts) {
|
|
2534
|
+
return this.runList(ctx, { publicOnly: true, orgId: opts?.orgId }, opts);
|
|
2535
|
+
}
|
|
2536
|
+
async runList(ctx, filter, opts) {
|
|
2537
|
+
const range = toRange(opts);
|
|
2538
|
+
const direction = opts?.orderDir ?? "desc";
|
|
2539
|
+
let query = this.clients.forRequest(ctx).from("definitions").select("*", { count: "exact" }).is("deleted_at", null);
|
|
2540
|
+
if (filter?.projectId) query = query.eq("project_id", filter.projectId);
|
|
2541
|
+
if (filter?.publicOnly) {
|
|
2542
|
+
query = this.clients.forRequest(ctx).from("definitions").select("*, project:projects!inner(visibility, org_id)", {
|
|
2543
|
+
count: "exact"
|
|
2544
|
+
}).is("deleted_at", null).eq("project.visibility", "public");
|
|
2545
|
+
if (filter.orgId) query = query.eq("project.org_id", filter.orgId);
|
|
2546
|
+
if (filter.projectId) query = query.eq("project_id", filter.projectId);
|
|
2547
|
+
}
|
|
2548
|
+
if (opts?.statuses?.length) {
|
|
2549
|
+
query = query.in("status", opts.statuses);
|
|
2550
|
+
} else {
|
|
2551
|
+
const excluded = [];
|
|
2552
|
+
if (!opts?.includePending) excluded.push("pending");
|
|
2553
|
+
if (!opts?.includeArchived) excluded.push("archived");
|
|
2554
|
+
if (excluded.length) {
|
|
2555
|
+
query = query.not("status", "in", `(${excluded.join(",")})`);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
query = query.order(definitionOrderColumn(opts?.orderBy), { ascending: direction === "asc" }).range(range.from, range.to);
|
|
2559
|
+
const { data, error, count } = await query;
|
|
2560
|
+
if (error) throw mapError$5(error);
|
|
2561
|
+
const items = (data ?? []).map(rowToRecord);
|
|
2562
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2563
|
+
}
|
|
2564
|
+
async get(ctx, guid) {
|
|
2565
|
+
const { data, error } = await this.clients.forRequest(ctx).from("definitions").select("*").eq("guid", guid).is("deleted_at", null).maybeSingle();
|
|
2566
|
+
if (error) throw mapError$5(error);
|
|
2567
|
+
return data ? rowToRecord(data) : null;
|
|
2568
|
+
}
|
|
2569
|
+
async create(ctx, record) {
|
|
2570
|
+
const { error } = await this.clients.forRequest(ctx).from("definitions").insert(recordToRow(record));
|
|
2571
|
+
if (error) throw mapError$5(error);
|
|
2572
|
+
await this.events.emit({
|
|
2573
|
+
type: "definition.created",
|
|
2574
|
+
definitionId: record.guid,
|
|
2575
|
+
projectId: record.projectId,
|
|
2576
|
+
actorId: actorFrom(ctx)
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
async update(ctx, guid, patch) {
|
|
2580
|
+
const row = patchToRow(patch);
|
|
2581
|
+
if (Object.keys(row).length === 0) return;
|
|
2582
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2583
|
+
const { data, error } = await this.clients.forRequest(ctx).from("definitions").update(row).eq("guid", guid).is("deleted_at", null).select("guid");
|
|
2584
|
+
if (error) throw mapError$5(error);
|
|
2585
|
+
if (!data || data.length === 0) throw new ProviderError(`Definition '${guid}' not found`, 404);
|
|
2586
|
+
}
|
|
2587
|
+
async delete(ctx, guid) {
|
|
2588
|
+
const stamp = auditSoftDelete(ctx, ctx.userId);
|
|
2589
|
+
const row = {
|
|
2590
|
+
deleted_at: stamp.deletedAt,
|
|
2591
|
+
updated_at: stamp.updatedAt
|
|
2592
|
+
};
|
|
2593
|
+
if (stamp.updatedBy) row.updated_by = stamp.updatedBy;
|
|
2594
|
+
const { error } = await this.clients.forRequest(ctx).from("definitions").update(row).eq("guid", guid).is("deleted_at", null);
|
|
2595
|
+
if (error) throw mapError$5(error);
|
|
2596
|
+
await this.events.emit({
|
|
2597
|
+
type: "definition.deleted",
|
|
2598
|
+
definitionId: guid,
|
|
2599
|
+
actorId: actorFrom(ctx)
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
async incrementSolveCount(ctx, guid) {
|
|
2603
|
+
const { error } = await this.clients.forRequest(ctx).rpc("increment_run_count", { g: guid });
|
|
2604
|
+
if (error) throw mapError$5(error);
|
|
2605
|
+
}
|
|
2606
|
+
// ============================================================================
|
|
2607
|
+
// Versions (spec §6)
|
|
2608
|
+
// ============================================================================
|
|
2609
|
+
async createVersion(ctx, version) {
|
|
2610
|
+
const { error } = await this.clients.forRequest(ctx).from("definition_versions").insert(versionToRow(version));
|
|
2611
|
+
if (error) throw mapError$5(error);
|
|
2612
|
+
await this.events.emit({
|
|
2613
|
+
type: "definition_version.created",
|
|
2614
|
+
versionId: version.id,
|
|
2615
|
+
definitionId: version.definitionId,
|
|
2616
|
+
actorId: actorFrom(ctx)
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
async listVersions(ctx, definitionId, opts) {
|
|
2620
|
+
const range = toRange(opts);
|
|
2621
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("definition_versions").select("*", { count: "exact" }).eq("definition_guid", definitionId).order("version_number", { ascending: false }).range(range.from, range.to);
|
|
2622
|
+
if (error) throw mapError$5(error);
|
|
2623
|
+
const items = (data ?? []).map(rowToVersion);
|
|
2624
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2625
|
+
}
|
|
2626
|
+
async getVersion(ctx, versionId) {
|
|
2627
|
+
const { data, error } = await this.clients.forRequest(ctx).from("definition_versions").select("*").eq("id", versionId).maybeSingle();
|
|
2628
|
+
if (error) throw mapError$5(error);
|
|
2629
|
+
return data ? rowToVersion(data) : null;
|
|
2630
|
+
}
|
|
2631
|
+
async setVersionSchema(ctx, versionId, schema) {
|
|
2632
|
+
const { error } = await this.clients.forRequest(ctx).from("definition_versions").update({ schema, schema_extracted_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", versionId);
|
|
2633
|
+
if (error) throw mapError$5(error);
|
|
2634
|
+
}
|
|
2635
|
+
async deleteVersion(ctx, versionId) {
|
|
2636
|
+
const { error } = await this.clients.forRequest(ctx).from("definition_versions").delete().eq("id", versionId);
|
|
2637
|
+
if (error) throw mapError$5(error);
|
|
2638
|
+
await this.events.emit({
|
|
2639
|
+
type: "definition_version.deleted",
|
|
2640
|
+
versionId,
|
|
2641
|
+
actorId: actorFrom(ctx)
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
async setLiveVersion(ctx, definitionId, versionId) {
|
|
2645
|
+
await this.repointChannel(ctx, definitionId, versionId, "live_version_id");
|
|
2646
|
+
await this.events.emit({
|
|
2647
|
+
type: "definition.published",
|
|
2648
|
+
definitionId,
|
|
2649
|
+
versionId,
|
|
2650
|
+
actorId: actorFrom(ctx)
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
async setDraftVersion(ctx, definitionId, versionId) {
|
|
2654
|
+
await this.repointChannel(ctx, definitionId, versionId, "draft_version_id");
|
|
2655
|
+
}
|
|
2656
|
+
async attachInitialVersion(ctx, definitionId, versionId) {
|
|
2657
|
+
const client = this.clients.forRequest(ctx);
|
|
2658
|
+
const { data: version, error: vError } = await client.from("definition_versions").select("id, definition_guid").eq("id", versionId).maybeSingle();
|
|
2659
|
+
if (vError) throw mapError$5(vError);
|
|
2660
|
+
if (!version || version.definition_guid !== definitionId) {
|
|
2661
|
+
throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
|
|
2662
|
+
}
|
|
2663
|
+
const row = {
|
|
2664
|
+
live_version_id: versionId,
|
|
2665
|
+
draft_version_id: versionId,
|
|
2666
|
+
status: "draft"
|
|
2667
|
+
};
|
|
2668
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2669
|
+
const { data, error } = await client.from("definitions").update(row).eq("guid", definitionId).is("deleted_at", null).select("guid");
|
|
2670
|
+
if (error) throw mapError$5(error);
|
|
2671
|
+
if (!data || data.length === 0) {
|
|
2672
|
+
throw new ProviderError(`Definition '${definitionId}' not found`, 404);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
async repointChannel(ctx, definitionId, versionId, column) {
|
|
2676
|
+
const client = this.clients.forRequest(ctx);
|
|
2677
|
+
const { data: version, error: vError } = await client.from("definition_versions").select("id, definition_guid").eq("id", versionId).maybeSingle();
|
|
2678
|
+
if (vError) throw mapError$5(vError);
|
|
2679
|
+
if (!version || version.definition_guid !== definitionId) {
|
|
2680
|
+
throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
|
|
2681
|
+
}
|
|
2682
|
+
const row = { [column]: versionId };
|
|
2683
|
+
if (ctx.userId) row.updated_by = ctx.userId;
|
|
2684
|
+
const { data, error } = await client.from("definitions").update(row).eq("guid", definitionId).is("deleted_at", null).select("guid");
|
|
2685
|
+
if (error) throw mapError$5(error);
|
|
2686
|
+
if (!data || data.length === 0) {
|
|
2687
|
+
throw new ProviderError(`Definition '${definitionId}' not found`, 404);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
function rowToRecord(row) {
|
|
2692
|
+
return {
|
|
2693
|
+
guid: row.guid,
|
|
2694
|
+
projectId: row.project_id,
|
|
2695
|
+
ownerId: row.owner_id,
|
|
2696
|
+
createdBy: row.created_by ?? row.owner_id,
|
|
2697
|
+
updatedBy: row.updated_by ?? row.owner_id,
|
|
2698
|
+
liveVersionId: row.live_version_id,
|
|
2699
|
+
draftVersionId: row.draft_version_id,
|
|
2700
|
+
computeServerId: row.compute_server_id ?? void 0,
|
|
2701
|
+
displayName: row.display_name,
|
|
2702
|
+
description: row.description ?? void 0,
|
|
2703
|
+
category: row.category ?? void 0,
|
|
2704
|
+
tags: row.tags ?? void 0,
|
|
2705
|
+
coverImage: row.cover_image ?? void 0,
|
|
2706
|
+
status: row.status,
|
|
2707
|
+
solveCount: typeof row.run_count === "string" ? Number(row.run_count) : row.run_count,
|
|
2708
|
+
createdAt: row.created_at,
|
|
2709
|
+
updatedAt: row.updated_at,
|
|
2710
|
+
deletedAt: row.deleted_at ?? null
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
function recordToRow(r) {
|
|
2714
|
+
const row = {
|
|
2715
|
+
guid: r.guid,
|
|
2716
|
+
project_id: r.projectId,
|
|
2717
|
+
owner_id: r.ownerId,
|
|
2718
|
+
created_by: r.createdBy,
|
|
2719
|
+
updated_by: r.updatedBy,
|
|
2720
|
+
live_version_id: r.liveVersionId,
|
|
2721
|
+
draft_version_id: r.draftVersionId,
|
|
2722
|
+
compute_server_id: r.computeServerId ?? null,
|
|
2723
|
+
display_name: r.displayName,
|
|
2724
|
+
description: r.description ?? null,
|
|
2725
|
+
category: r.category ?? null,
|
|
2726
|
+
cover_image: r.coverImage ?? null,
|
|
2727
|
+
status: r.status,
|
|
2728
|
+
run_count: r.solveCount,
|
|
2729
|
+
created_at: r.createdAt,
|
|
2730
|
+
updated_at: r.updatedAt,
|
|
2731
|
+
deleted_at: r.deletedAt ?? null
|
|
2732
|
+
};
|
|
2733
|
+
if (r.tags !== void 0) row.tags = r.tags;
|
|
2734
|
+
return row;
|
|
2735
|
+
}
|
|
2736
|
+
function patchToRow(patch) {
|
|
2737
|
+
const row = {};
|
|
2738
|
+
if (patch.displayName !== void 0) row.display_name = patch.displayName;
|
|
2739
|
+
if (patch.description !== void 0) row.description = patch.description;
|
|
2740
|
+
if (patch.category !== void 0) row.category = patch.category;
|
|
2741
|
+
if (patch.tags !== void 0) row.tags = patch.tags;
|
|
2742
|
+
if (patch.coverImage !== void 0) row.cover_image = patch.coverImage;
|
|
2743
|
+
if (patch.projectId !== void 0) row.project_id = patch.projectId;
|
|
2744
|
+
if (patch.computeServerId !== void 0) row.compute_server_id = patch.computeServerId;
|
|
2745
|
+
if (patch.status !== void 0) row.status = patch.status;
|
|
2746
|
+
if (patch.ownerId !== void 0) row.owner_id = patch.ownerId;
|
|
2747
|
+
return row;
|
|
2748
|
+
}
|
|
2749
|
+
function rowToVersion(row) {
|
|
2750
|
+
return {
|
|
2751
|
+
id: row.id,
|
|
2752
|
+
definitionId: row.definition_guid,
|
|
2753
|
+
versionNumber: row.version_number,
|
|
2754
|
+
fileExt: row.file_ext,
|
|
2755
|
+
fileKey: row.file_key,
|
|
2756
|
+
originalFilename: row.original_filename ?? void 0,
|
|
2757
|
+
uploadedBy: row.uploaded_by,
|
|
2758
|
+
uploadedAt: row.uploaded_at,
|
|
2759
|
+
changeNote: row.change_note ?? void 0,
|
|
2760
|
+
schema: row.schema ?? void 0,
|
|
2761
|
+
schemaExtractedAt: row.schema_extracted_at ?? void 0
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
function versionToRow(v) {
|
|
2765
|
+
const row = {
|
|
2766
|
+
id: v.id,
|
|
2767
|
+
definition_guid: v.definitionId,
|
|
2768
|
+
version_number: v.versionNumber,
|
|
2769
|
+
file_ext: v.fileExt,
|
|
2770
|
+
file_key: v.fileKey,
|
|
2771
|
+
original_filename: v.originalFilename ?? null,
|
|
2772
|
+
uploaded_by: v.uploadedBy,
|
|
2773
|
+
uploaded_at: v.uploadedAt
|
|
2774
|
+
};
|
|
2775
|
+
if (v.changeNote !== void 0) row.change_note = v.changeNote;
|
|
2776
|
+
if (v.schema !== void 0) row.schema = v.schema;
|
|
2777
|
+
if (v.schemaExtractedAt !== void 0) row.schema_extracted_at = v.schemaExtractedAt;
|
|
2778
|
+
return row;
|
|
2779
|
+
}
|
|
2780
|
+
function definitionOrderColumn(orderBy) {
|
|
2781
|
+
switch (orderBy) {
|
|
2782
|
+
case "name":
|
|
2783
|
+
return "display_name";
|
|
2784
|
+
case "solveCount":
|
|
2785
|
+
return "run_count";
|
|
2786
|
+
case "updatedAt":
|
|
2787
|
+
return "updated_at";
|
|
2788
|
+
case "createdAt":
|
|
2789
|
+
default:
|
|
2790
|
+
return "created_at";
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
function mapError$5(e) {
|
|
2794
|
+
const pg = e;
|
|
2795
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
2796
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
2797
|
+
if (e instanceof Error) return e;
|
|
2798
|
+
if (e && typeof e === "object") {
|
|
2799
|
+
const obj = e;
|
|
2800
|
+
const msg = obj.message ?? obj.details ?? obj.hint ?? "Unknown Postgres error";
|
|
2801
|
+
const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
|
|
2802
|
+
Object.assign(err, obj);
|
|
2803
|
+
return err;
|
|
2804
|
+
}
|
|
2805
|
+
return new Error(String(e));
|
|
2806
|
+
}
|
|
2807
|
+
class SupabaseInviteStore {
|
|
2808
|
+
constructor(clients, events = new NoopEventSink()) {
|
|
2809
|
+
this.clients = clients;
|
|
2810
|
+
this.events = events;
|
|
2811
|
+
}
|
|
2812
|
+
clients;
|
|
2813
|
+
events;
|
|
2814
|
+
async create(ctx, invite) {
|
|
2815
|
+
const { error } = await this.clients.forRequest(ctx).from("invites").insert(inviteToRow(invite));
|
|
2816
|
+
if (error) throw mapError$4(error);
|
|
2817
|
+
await this.events.emit({
|
|
2818
|
+
type: "invite.created",
|
|
2819
|
+
inviteId: invite.id,
|
|
2820
|
+
orgId: invite.orgId,
|
|
2821
|
+
email: invite.email,
|
|
2822
|
+
actorId: actorFrom(ctx)
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
async getByTokenHash(ctx, tokenHash) {
|
|
2826
|
+
const { data, error } = await this.clients.forRequest(ctx).rpc("get_invite_by_token_hash", { h: tokenHash });
|
|
2827
|
+
if (error) throw mapError$4(error);
|
|
2828
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
2829
|
+
return row ? rowToInvite(row) : null;
|
|
2830
|
+
}
|
|
2831
|
+
async listByOrg(ctx, orgId, opts) {
|
|
2832
|
+
const range = toRange(opts);
|
|
2833
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("invites").select("*", { count: "exact" }).eq("org_id", orgId).order("created_at", { ascending: (opts?.orderDir ?? "desc") === "asc" }).range(range.from, range.to);
|
|
2834
|
+
if (error) throw mapError$4(error);
|
|
2835
|
+
const items = (data ?? []).map(rowToInvite);
|
|
2836
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
2837
|
+
}
|
|
2838
|
+
async markAccepted(ctx, id, userId) {
|
|
2839
|
+
const { data, error } = await this.clients.forRequest(ctx).from("invites").update({ accepted_at: (/* @__PURE__ */ new Date()).toISOString(), accepted_by_user_id: userId }).eq("id", id).is("accepted_at", null).select("org_id");
|
|
2840
|
+
if (error) throw mapError$4(error);
|
|
2841
|
+
const row = data?.[0];
|
|
2842
|
+
if (!row) return;
|
|
2843
|
+
await this.events.emit({
|
|
2844
|
+
type: "invite.accepted",
|
|
2845
|
+
inviteId: id,
|
|
2846
|
+
orgId: row.org_id,
|
|
2847
|
+
userId,
|
|
2848
|
+
actorId: actorFrom(ctx)
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
async revoke(ctx, id) {
|
|
2852
|
+
const { data, error } = await this.clients.forRequest(ctx).from("invites").delete().eq("id", id).is("accepted_at", null).select("org_id");
|
|
2853
|
+
if (error) throw mapError$4(error);
|
|
2854
|
+
const row = data?.[0];
|
|
2855
|
+
if (!row) return;
|
|
2856
|
+
await this.events.emit({
|
|
2857
|
+
type: "invite.revoked",
|
|
2858
|
+
inviteId: id,
|
|
2859
|
+
orgId: row.org_id,
|
|
2860
|
+
actorId: actorFrom(ctx)
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
async deleteByOrg(ctx, orgId) {
|
|
2864
|
+
const { error } = await this.clients.forRequest(ctx).from("invites").delete().eq("org_id", orgId);
|
|
2865
|
+
if (error) throw mapError$4(error);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
function rowToInvite(row) {
|
|
2869
|
+
return {
|
|
2870
|
+
id: row.id,
|
|
2871
|
+
tokenHash: row.token_hash,
|
|
2872
|
+
email: row.email,
|
|
2873
|
+
orgId: row.org_id,
|
|
2874
|
+
orgRole: row.org_role,
|
|
2875
|
+
orgPermissions: row.org_permissions,
|
|
2876
|
+
invitedBy: row.invited_by,
|
|
2877
|
+
createdAt: row.created_at,
|
|
2878
|
+
expiresAt: row.expires_at,
|
|
2879
|
+
acceptedAt: row.accepted_at ?? void 0,
|
|
2880
|
+
acceptedByUserId: row.accepted_by_user_id ?? void 0
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function inviteToRow(i) {
|
|
2884
|
+
return {
|
|
2885
|
+
id: i.id,
|
|
2886
|
+
token_hash: i.tokenHash,
|
|
2887
|
+
email: i.email,
|
|
2888
|
+
org_id: i.orgId,
|
|
2889
|
+
org_role: i.orgRole,
|
|
2890
|
+
org_permissions: i.orgPermissions,
|
|
2891
|
+
invited_by: i.invitedBy,
|
|
2892
|
+
created_at: i.createdAt,
|
|
2893
|
+
expires_at: i.expiresAt,
|
|
2894
|
+
accepted_at: i.acceptedAt ?? null,
|
|
2895
|
+
accepted_by_user_id: i.acceptedByUserId ?? null
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
function mapError$4(e) {
|
|
2899
|
+
const pg = e;
|
|
2900
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
2901
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
2902
|
+
if (e instanceof Error) return e;
|
|
2903
|
+
if (e && typeof e === "object") {
|
|
2904
|
+
const obj = e;
|
|
2905
|
+
return new Error(obj.code ? `[${obj.code}] ${obj.message ?? ""}` : obj.message ?? String(e));
|
|
2906
|
+
}
|
|
2907
|
+
return new Error(String(e));
|
|
2908
|
+
}
|
|
2909
|
+
class SupabaseComputeServerStore {
|
|
2910
|
+
constructor(clients) {
|
|
2911
|
+
this.clients = clients;
|
|
2912
|
+
}
|
|
2913
|
+
clients;
|
|
2914
|
+
async getConfig(ctx) {
|
|
2915
|
+
const client = this.clients.forRequest(ctx);
|
|
2916
|
+
const [serversRes, sharesRes, orgDefRes, platDefRes] = await Promise.all([
|
|
2917
|
+
client.from("compute_servers").select("*"),
|
|
2918
|
+
client.from("compute_server_shares").select("*"),
|
|
2919
|
+
client.from("compute_server_org_defaults").select("*"),
|
|
2920
|
+
client.from("compute_server_platform_default").select("default_server_id").eq("singleton", true).maybeSingle()
|
|
2921
|
+
]);
|
|
2922
|
+
if (serversRes.error) throw mapError$3(serversRes.error);
|
|
2923
|
+
if (sharesRes.error) throw mapError$3(sharesRes.error);
|
|
2924
|
+
if (orgDefRes.error) throw mapError$3(orgDefRes.error);
|
|
2925
|
+
if (platDefRes.error) throw mapError$3(platDefRes.error);
|
|
2926
|
+
const sharedByServer = /* @__PURE__ */ new Map();
|
|
2927
|
+
for (const row of sharesRes.data ?? []) {
|
|
2928
|
+
const list = sharedByServer.get(row.server_id) ?? [];
|
|
2929
|
+
list.push(row.org_id);
|
|
2930
|
+
sharedByServer.set(row.server_id, list);
|
|
2931
|
+
}
|
|
2932
|
+
const servers = (serversRes.data ?? []).map((row) => rowToServer(row, sharedByServer));
|
|
2933
|
+
const orgDefaults = {};
|
|
2934
|
+
for (const row of orgDefRes.data ?? []) {
|
|
2935
|
+
if (row.default_server_id) orgDefaults[row.org_id] = row.default_server_id;
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
servers,
|
|
2939
|
+
defaultServerId: platDefRes.data?.default_server_id ?? void 0,
|
|
2940
|
+
orgDefaults
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
async savePlatformServers(ctx, servers, defaultServerId) {
|
|
2944
|
+
const client = this.clients.forRequest(ctx);
|
|
2945
|
+
const platformOnly = servers.filter(isPlatformServer);
|
|
2946
|
+
const { error: delErr } = await client.from("compute_servers").delete().eq("scope", "platform");
|
|
2947
|
+
if (delErr) throw mapError$3(delErr);
|
|
2948
|
+
if (platformOnly.length > 0) {
|
|
2949
|
+
const { error: insErr } = await client.from("compute_servers").insert(platformOnly.map(serverToRow));
|
|
2950
|
+
if (insErr) throw mapError$3(insErr);
|
|
2951
|
+
const shareRows = platformOnly.flatMap(
|
|
2952
|
+
(s) => s.sharedWith === "all" ? [] : s.sharedWith.map((orgId) => ({ server_id: s.id, org_id: orgId }))
|
|
2953
|
+
);
|
|
2954
|
+
if (shareRows.length > 0) {
|
|
2955
|
+
const { error: shErr } = await client.from("compute_server_shares").insert(shareRows);
|
|
2956
|
+
if (shErr) throw mapError$3(shErr);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
const { error: defErr } = await client.from("compute_server_platform_default").update({ default_server_id: defaultServerId ?? null }).eq("singleton", true);
|
|
2960
|
+
if (defErr) throw mapError$3(defErr);
|
|
2961
|
+
}
|
|
2962
|
+
async saveOrgServers(ctx, orgId, servers, defaultServerId) {
|
|
2963
|
+
const client = this.clients.forRequest(ctx);
|
|
2964
|
+
const orgOnly = servers.filter(isOrgServer).map((s) => ({ ...s, ownerOrgId: orgId }));
|
|
2965
|
+
const { error: delErr } = await client.from("compute_servers").delete().eq("scope", "org").eq("owner_org_id", orgId);
|
|
2966
|
+
if (delErr) throw mapError$3(delErr);
|
|
2967
|
+
if (orgOnly.length > 0) {
|
|
2968
|
+
const { error: insErr } = await client.from("compute_servers").insert(orgOnly.map(serverToRow));
|
|
2969
|
+
if (insErr) throw mapError$3(insErr);
|
|
2970
|
+
}
|
|
2971
|
+
if (defaultServerId === null) {
|
|
2972
|
+
const { error } = await client.from("compute_server_org_defaults").delete().eq("org_id", orgId);
|
|
2973
|
+
if (error) throw mapError$3(error);
|
|
2974
|
+
} else if (typeof defaultServerId === "string") {
|
|
2975
|
+
const { error } = await client.from("compute_server_org_defaults").upsert({ org_id: orgId, default_server_id: defaultServerId });
|
|
2976
|
+
if (error) throw mapError$3(error);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
async setOrgDefault(ctx, orgId, serverId) {
|
|
2980
|
+
const client = this.clients.forRequest(ctx);
|
|
2981
|
+
if (serverId === null) {
|
|
2982
|
+
const { error: error2 } = await client.from("compute_server_org_defaults").delete().eq("org_id", orgId);
|
|
2983
|
+
if (error2) throw mapError$3(error2);
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
const { error } = await client.from("compute_server_org_defaults").upsert({ org_id: orgId, default_server_id: serverId });
|
|
2987
|
+
if (error) throw mapError$3(error);
|
|
2988
|
+
}
|
|
2989
|
+
async deleteByOrg(ctx, orgId) {
|
|
2990
|
+
const client = this.clients.forRequest(ctx);
|
|
2991
|
+
const { error: defErr } = await client.from("compute_server_org_defaults").delete().eq("org_id", orgId);
|
|
2992
|
+
if (defErr) throw mapError$3(defErr);
|
|
2993
|
+
const { error: srvErr } = await client.from("compute_servers").delete().eq("scope", "org").eq("owner_org_id", orgId);
|
|
2994
|
+
if (srvErr) throw mapError$3(srvErr);
|
|
2995
|
+
const { error: shErr } = await client.from("compute_server_shares").delete().eq("org_id", orgId);
|
|
2996
|
+
if (shErr) throw mapError$3(shErr);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
function rowToServer(row, sharedByServer) {
|
|
3000
|
+
const common = {
|
|
3001
|
+
id: row.id,
|
|
3002
|
+
label: row.label,
|
|
3003
|
+
serverUrl: row.server_url,
|
|
3004
|
+
apiKey: row.api_key ?? void 0,
|
|
3005
|
+
timeoutMs: row.timeout_ms ?? void 0,
|
|
3006
|
+
retryCount: row.retry_count ?? void 0
|
|
3007
|
+
};
|
|
3008
|
+
if (row.scope === "platform") {
|
|
3009
|
+
return {
|
|
3010
|
+
...common,
|
|
3011
|
+
scope: "platform",
|
|
3012
|
+
sharedWith: row.shared_with_all ? "all" : sharedByServer.get(row.id) ?? []
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
if (!row.owner_org_id) {
|
|
3016
|
+
throw new Error(`compute_servers row ${row.id} has scope='org' but null owner_org_id`);
|
|
3017
|
+
}
|
|
3018
|
+
return { ...common, scope: "org", ownerOrgId: row.owner_org_id };
|
|
3019
|
+
}
|
|
3020
|
+
function serverToRow(s) {
|
|
3021
|
+
const base = {
|
|
3022
|
+
id: s.id,
|
|
3023
|
+
label: s.label,
|
|
3024
|
+
server_url: s.serverUrl,
|
|
3025
|
+
api_key: s.apiKey ?? null,
|
|
3026
|
+
timeout_ms: s.timeoutMs ?? null,
|
|
3027
|
+
retry_count: s.retryCount ?? null
|
|
3028
|
+
};
|
|
3029
|
+
if (isPlatformServer(s)) {
|
|
3030
|
+
return {
|
|
3031
|
+
...base,
|
|
3032
|
+
scope: "platform",
|
|
3033
|
+
owner_org_id: null,
|
|
3034
|
+
shared_with_all: s.sharedWith === "all"
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
return {
|
|
3038
|
+
...base,
|
|
3039
|
+
scope: "org",
|
|
3040
|
+
owner_org_id: s.ownerOrgId,
|
|
3041
|
+
shared_with_all: false
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
function mapError$3(e) {
|
|
3045
|
+
const pg = e;
|
|
3046
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
3047
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
3048
|
+
if (e instanceof Error) return e;
|
|
3049
|
+
if (e && typeof e === "object") {
|
|
3050
|
+
const obj = e;
|
|
3051
|
+
return new Error(obj.code ? `[${obj.code}] ${obj.message ?? ""}` : obj.message ?? String(e));
|
|
3052
|
+
}
|
|
3053
|
+
return new Error(String(e));
|
|
3054
|
+
}
|
|
3055
|
+
class SupabaseShareLinkStore {
|
|
3056
|
+
constructor(clients, events = new NoopEventSink()) {
|
|
3057
|
+
this.clients = clients;
|
|
3058
|
+
this.events = events;
|
|
3059
|
+
}
|
|
3060
|
+
clients;
|
|
3061
|
+
events;
|
|
3062
|
+
async create(ctx, link) {
|
|
3063
|
+
const { error } = await this.clients.forRequest(ctx).from("share_links").insert(linkToRow(link));
|
|
3064
|
+
if (error) throw mapError$2(error);
|
|
3065
|
+
await this.events.emit({
|
|
3066
|
+
type: "share_link.minted",
|
|
3067
|
+
linkId: link.id,
|
|
3068
|
+
definitionId: link.definitionId,
|
|
3069
|
+
actorId: actorFrom(ctx)
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
async listByDefinition(ctx, definitionId, opts) {
|
|
3073
|
+
const range = toRange(opts);
|
|
3074
|
+
const { data, error, count } = await this.clients.forRequest(ctx).from("share_links").select("*", { count: "exact" }).eq("definition_guid", definitionId).is("revoked_at", null).order("created_at", { ascending: false }).range(range.from, range.to);
|
|
3075
|
+
if (error) throw mapError$2(error);
|
|
3076
|
+
const items = (data ?? []).map(rowToLink);
|
|
3077
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
3078
|
+
}
|
|
3079
|
+
async getById(ctx, id) {
|
|
3080
|
+
const { data, error } = await this.clients.forRequest(ctx).from("share_links").select("*").eq("id", id).maybeSingle();
|
|
3081
|
+
if (error) throw mapError$2(error);
|
|
3082
|
+
return data ? rowToLink(data) : null;
|
|
3083
|
+
}
|
|
3084
|
+
async getByTokenHash(_ctx, tokenHash) {
|
|
3085
|
+
const { data, error } = await this.clients.serviceClient.from("share_links").select("*, definitions!inner(deleted_at)").eq("token_hash", tokenHash).is("revoked_at", null).is("definitions.deleted_at", null).maybeSingle();
|
|
3086
|
+
if (error) throw mapError$2(error);
|
|
3087
|
+
return data ? rowToLink(data) : null;
|
|
3088
|
+
}
|
|
3089
|
+
async revoke(ctx, id) {
|
|
3090
|
+
const { error } = await this.clients.forRequest(ctx).from("share_links").update({ revoked_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).is("revoked_at", null);
|
|
3091
|
+
if (error) throw mapError$2(error);
|
|
3092
|
+
await this.events.emit({ type: "share_link.revoked", linkId: id, actorId: actorFrom(ctx) });
|
|
3093
|
+
}
|
|
3094
|
+
async tryIncrementSolveCount(_ctx, id) {
|
|
3095
|
+
const { data, error } = await this.clients.serviceClient.rpc(
|
|
3096
|
+
"try_increment_share_link_solve_count",
|
|
3097
|
+
{ link_id: id }
|
|
3098
|
+
);
|
|
3099
|
+
if (error) throw mapError$2(error);
|
|
3100
|
+
return typeof data === "number" ? data : null;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
function rowToLink(row) {
|
|
3104
|
+
return {
|
|
3105
|
+
id: row.id,
|
|
3106
|
+
definitionId: row.definition_guid,
|
|
3107
|
+
channel: row.channel,
|
|
3108
|
+
tokenHash: row.token_hash,
|
|
3109
|
+
name: row.name ?? void 0,
|
|
3110
|
+
createdBy: row.created_by,
|
|
3111
|
+
createdAt: row.created_at,
|
|
3112
|
+
expiresAt: row.expires_at,
|
|
3113
|
+
revokedAt: row.revoked_at,
|
|
3114
|
+
allowSolve: row.allow_solve,
|
|
3115
|
+
maxSolves: row.max_solves,
|
|
3116
|
+
solveCount: row.solve_count
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
function linkToRow(l) {
|
|
3120
|
+
return {
|
|
3121
|
+
id: l.id,
|
|
3122
|
+
definition_guid: l.definitionId,
|
|
3123
|
+
channel: l.channel,
|
|
3124
|
+
token_hash: l.tokenHash,
|
|
3125
|
+
name: l.name ?? null,
|
|
3126
|
+
created_by: l.createdBy,
|
|
3127
|
+
created_at: l.createdAt,
|
|
3128
|
+
expires_at: l.expiresAt ?? null,
|
|
3129
|
+
revoked_at: l.revokedAt ?? null,
|
|
3130
|
+
allow_solve: l.allowSolve,
|
|
3131
|
+
max_solves: l.maxSolves ?? null,
|
|
3132
|
+
solve_count: l.solveCount
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
function mapError$2(e) {
|
|
3136
|
+
const pg = e;
|
|
3137
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
3138
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
3139
|
+
if (e instanceof Error) return e;
|
|
3140
|
+
if (e && typeof e === "object") {
|
|
3141
|
+
const obj = e;
|
|
3142
|
+
const msg = obj.message ?? obj.details ?? obj.hint ?? "Unknown Postgres error";
|
|
3143
|
+
const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
|
|
3144
|
+
Object.assign(err, obj);
|
|
3145
|
+
return err;
|
|
3146
|
+
}
|
|
3147
|
+
return new Error(String(e));
|
|
3148
|
+
}
|
|
3149
|
+
function buildClientBundle(opts) {
|
|
3150
|
+
const serviceClient = createClient(opts.supabaseUrl, opts.serviceRoleKey, {
|
|
3151
|
+
db: { schema: "selva" },
|
|
3152
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
3153
|
+
});
|
|
3154
|
+
const anonClient = createClient(opts.supabaseUrl, opts.anonKey, {
|
|
3155
|
+
db: { schema: "selva" },
|
|
3156
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
3157
|
+
});
|
|
3158
|
+
const perRequestCache = /* @__PURE__ */ new WeakMap();
|
|
3159
|
+
return {
|
|
3160
|
+
serviceClient,
|
|
3161
|
+
forRequest(ctx) {
|
|
3162
|
+
if (ctx.system) return serviceClient;
|
|
3163
|
+
const token = extractSessionToken(ctx.adapterContext);
|
|
3164
|
+
if (!token) return anonClient;
|
|
3165
|
+
const cached = perRequestCache.get(ctx);
|
|
3166
|
+
if (cached) return cached;
|
|
3167
|
+
const client = createClient(opts.supabaseUrl, opts.anonKey, {
|
|
3168
|
+
db: { schema: "selva" },
|
|
3169
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
3170
|
+
global: {
|
|
3171
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
3174
|
+
perRequestCache.set(ctx, client);
|
|
3175
|
+
return client;
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
function extractSessionToken(adapterContext) {
|
|
3180
|
+
if (!adapterContext || typeof adapterContext !== "object") return void 0;
|
|
3181
|
+
const maybe = adapterContext.sessionToken;
|
|
3182
|
+
return typeof maybe === "string" && maybe.length > 0 ? maybe : void 0;
|
|
3183
|
+
}
|
|
3184
|
+
class SupabaseEventSink {
|
|
3185
|
+
constructor(clients) {
|
|
3186
|
+
this.clients = clients;
|
|
3187
|
+
}
|
|
3188
|
+
clients;
|
|
3189
|
+
async emit(event) {
|
|
3190
|
+
try {
|
|
3191
|
+
const { error } = await this.clients.serviceClient.from("audit_events").insert({
|
|
3192
|
+
type: event.type,
|
|
3193
|
+
actor_id: event.actorId,
|
|
3194
|
+
data: event
|
|
3195
|
+
});
|
|
3196
|
+
if (error) {
|
|
3197
|
+
console.error("[SupabaseEventSink] insert failed:", error.message, { event });
|
|
3198
|
+
}
|
|
3199
|
+
} catch (err) {
|
|
3200
|
+
console.error("[SupabaseEventSink] unexpected error:", err, { event });
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
class SupabaseSolveMetricSink {
|
|
3205
|
+
constructor(clients) {
|
|
3206
|
+
this.clients = clients;
|
|
3207
|
+
}
|
|
3208
|
+
clients;
|
|
3209
|
+
async record(_ctx, metric) {
|
|
3210
|
+
try {
|
|
3211
|
+
const { error } = await this.clients.serviceClient.from("solve_metrics").insert({
|
|
3212
|
+
actor_id: _ctx.userId || "system",
|
|
3213
|
+
definition_url: metric.definitionUrl,
|
|
3214
|
+
definition_id: metric.definitionId,
|
|
3215
|
+
version_id: metric.versionId,
|
|
3216
|
+
channel: metric.channel,
|
|
3217
|
+
org_id: metric.orgId,
|
|
3218
|
+
duration_ms: metric.durationMs,
|
|
3219
|
+
ok: metric.ok,
|
|
3220
|
+
failure_kind: metric.failureKind,
|
|
3221
|
+
error_count: metric.errorCount,
|
|
3222
|
+
warning_count: metric.warningCount
|
|
3223
|
+
});
|
|
3224
|
+
if (error) {
|
|
3225
|
+
console.error("[SupabaseSolveMetricSink] insert failed:", error.message, { metric });
|
|
3226
|
+
}
|
|
3227
|
+
} catch (err) {
|
|
3228
|
+
console.error("[SupabaseSolveMetricSink] unexpected error:", err, { metric });
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
const MAX_LIMIT = 200;
|
|
3233
|
+
const DEFAULT_LIMIT = 100;
|
|
3234
|
+
const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
3235
|
+
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
3236
|
+
class SupabaseAuditQuery {
|
|
3237
|
+
constructor(clients) {
|
|
3238
|
+
this.clients = clients;
|
|
3239
|
+
}
|
|
3240
|
+
clients;
|
|
3241
|
+
async list(_ctx, filters) {
|
|
3242
|
+
const limit = clampLimit(filters.limit);
|
|
3243
|
+
let q = this.clients.serviceClient.from("audit_events").select("id, type, actor_id, occurred_at, data").order("occurred_at", { ascending: false }).order("id", { ascending: false }).limit(limit + 1);
|
|
3244
|
+
if (filters.types && filters.types.length > 0) {
|
|
3245
|
+
q = q.in("type", [...filters.types]);
|
|
3246
|
+
}
|
|
3247
|
+
if (filters.actorId) {
|
|
3248
|
+
q = q.eq("actor_id", filters.actorId);
|
|
3249
|
+
}
|
|
3250
|
+
if (filters.sinceIso) {
|
|
3251
|
+
q = q.gte("occurred_at", filters.sinceIso);
|
|
3252
|
+
}
|
|
3253
|
+
if (filters.untilIso) {
|
|
3254
|
+
q = q.lte("occurred_at", filters.untilIso);
|
|
3255
|
+
}
|
|
3256
|
+
if (filters.cursor) {
|
|
3257
|
+
const ts = filters.cursor.occurredAt;
|
|
3258
|
+
const id = filters.cursor.id;
|
|
3259
|
+
if (!ISO_RE.test(ts) || !UUID_RE.test(id)) {
|
|
3260
|
+
throw new Error("audit_events query: malformed cursor");
|
|
3261
|
+
}
|
|
3262
|
+
q = q.or(`occurred_at.lt.${ts},and(occurred_at.eq.${ts},id.lt.${id})`);
|
|
3263
|
+
}
|
|
3264
|
+
const { data, error } = await q;
|
|
3265
|
+
if (error) {
|
|
3266
|
+
throw new Error(`audit_events query failed: ${error.message}`);
|
|
3267
|
+
}
|
|
3268
|
+
const fetched = data ?? [];
|
|
3269
|
+
const hasMore = fetched.length > limit;
|
|
3270
|
+
const page = hasMore ? fetched.slice(0, limit) : fetched;
|
|
3271
|
+
const last = page[page.length - 1];
|
|
3272
|
+
return {
|
|
3273
|
+
rows: page.map((r) => ({
|
|
3274
|
+
id: r.id,
|
|
3275
|
+
type: r.type,
|
|
3276
|
+
actorId: r.actor_id,
|
|
3277
|
+
occurredAt: r.occurred_at,
|
|
3278
|
+
data: r.data
|
|
3279
|
+
})),
|
|
3280
|
+
nextCursor: hasMore && last ? { occurredAt: last.occurred_at, id: last.id } : null
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
function clampLimit(requested) {
|
|
3285
|
+
if (!requested || requested <= 0) return DEFAULT_LIMIT;
|
|
3286
|
+
if (requested > MAX_LIMIT) return MAX_LIMIT;
|
|
3287
|
+
return Math.floor(requested);
|
|
3288
|
+
}
|
|
3289
|
+
const MAX_RECENT_RUNS = 20;
|
|
3290
|
+
class SupabaseUserProfileProvider {
|
|
3291
|
+
constructor(clients) {
|
|
3292
|
+
this.clients = clients;
|
|
3293
|
+
}
|
|
3294
|
+
clients;
|
|
3295
|
+
client() {
|
|
3296
|
+
return this.clients.serviceClient;
|
|
3297
|
+
}
|
|
3298
|
+
async getProfile(ctx, userId) {
|
|
3299
|
+
assertCanAccess(ctx, userId);
|
|
3300
|
+
const { data, error } = await this.client().from("user_profiles").select("user_id, display_name, starred_definitions, recent_runs").eq("user_id", userId).maybeSingle();
|
|
3301
|
+
if (error) throw mapError$1(error);
|
|
3302
|
+
return data ? rowToProfile(data) : null;
|
|
3303
|
+
}
|
|
3304
|
+
async getProfiles(_ctx, userIds) {
|
|
3305
|
+
if (userIds.length === 0) return [];
|
|
3306
|
+
const { data, error } = await this.client().from("user_profiles").select("user_id, display_name, starred_definitions, recent_runs").in("user_id", [...userIds]);
|
|
3307
|
+
if (error) throw mapError$1(error);
|
|
3308
|
+
return (data ?? []).map(rowToProfile);
|
|
3309
|
+
}
|
|
3310
|
+
async updateProfile(ctx, userId, patch) {
|
|
3311
|
+
assertCanAccess(ctx, userId);
|
|
3312
|
+
const row = {};
|
|
3313
|
+
if (patch.displayName !== void 0) row.display_name = patch.displayName;
|
|
3314
|
+
if (Object.keys(row).length === 0) return "ok";
|
|
3315
|
+
const { data, error } = await this.client().from("user_profiles").update(row).eq("user_id", userId).select("user_id");
|
|
3316
|
+
if (error) throw mapError$1(error);
|
|
3317
|
+
return data && data.length > 0 ? "ok" : "not_found";
|
|
3318
|
+
}
|
|
3319
|
+
async starDefinition(ctx, userId, definitionId) {
|
|
3320
|
+
assertCanAccess(ctx, userId);
|
|
3321
|
+
const existing = await this.getProfile(ctx, userId);
|
|
3322
|
+
if (!existing) return "not_found";
|
|
3323
|
+
if (existing.starredDefinitions.includes(definitionId)) return "ok";
|
|
3324
|
+
const next = [...existing.starredDefinitions, definitionId];
|
|
3325
|
+
const { data, error } = await this.client().from("user_profiles").update({ starred_definitions: next }).eq("user_id", userId).select("user_id");
|
|
3326
|
+
if (error) throw mapError$1(error);
|
|
3327
|
+
return data && data.length > 0 ? "ok" : "not_found";
|
|
3328
|
+
}
|
|
3329
|
+
async unstarDefinition(ctx, userId, definitionId) {
|
|
3330
|
+
assertCanAccess(ctx, userId);
|
|
3331
|
+
const existing = await this.getProfile(ctx, userId);
|
|
3332
|
+
if (!existing) return "not_found";
|
|
3333
|
+
if (!existing.starredDefinitions.includes(definitionId)) return "ok";
|
|
3334
|
+
const next = existing.starredDefinitions.filter((id) => id !== definitionId);
|
|
3335
|
+
const { data, error } = await this.client().from("user_profiles").update({ starred_definitions: next }).eq("user_id", userId).select("user_id");
|
|
3336
|
+
if (error) throw mapError$1(error);
|
|
3337
|
+
return data && data.length > 0 ? "ok" : "not_found";
|
|
3338
|
+
}
|
|
3339
|
+
async recordRun(ctx, userId, run) {
|
|
3340
|
+
assertCanAccess(ctx, userId);
|
|
3341
|
+
const existing = await this.getProfile(ctx, userId);
|
|
3342
|
+
if (!existing) return "not_found";
|
|
3343
|
+
const filtered = existing.recentRuns.filter((r) => r.definitionId !== run.definitionId);
|
|
3344
|
+
const next = [run, ...filtered].slice(0, MAX_RECENT_RUNS);
|
|
3345
|
+
const { data, error } = await this.client().from("user_profiles").update({ recent_runs: next }).eq("user_id", userId).select("user_id");
|
|
3346
|
+
if (error) throw mapError$1(error);
|
|
3347
|
+
return data && data.length > 0 ? "ok" : "not_found";
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
function assertCanAccess(ctx, userId) {
|
|
3351
|
+
if (ctx.system) return;
|
|
3352
|
+
if (ctx.userId === userId) return;
|
|
3353
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
3354
|
+
throw new ProviderError("Forbidden: cannot access another user’s profile", 403);
|
|
3355
|
+
}
|
|
3356
|
+
function rowToProfile(row) {
|
|
3357
|
+
return {
|
|
3358
|
+
userId: row.user_id,
|
|
3359
|
+
displayName: row.display_name ?? void 0,
|
|
3360
|
+
starredDefinitions: row.starred_definitions ?? [],
|
|
3361
|
+
recentRuns: row.recent_runs ?? []
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
function mapError$1(e) {
|
|
3365
|
+
const pg = e;
|
|
3366
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
3367
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
3368
|
+
if (e instanceof Error) return e;
|
|
3369
|
+
if (e && typeof e === "object") {
|
|
3370
|
+
const obj = e;
|
|
3371
|
+
return new Error(obj.code ? `[${obj.code}] ${obj.message ?? ""}` : obj.message ?? String(e));
|
|
3372
|
+
}
|
|
3373
|
+
return new Error(String(e));
|
|
3374
|
+
}
|
|
3375
|
+
class SupabasePlatformPermissionStore {
|
|
3376
|
+
constructor(clients) {
|
|
3377
|
+
this.clients = clients;
|
|
3378
|
+
}
|
|
3379
|
+
clients;
|
|
3380
|
+
client() {
|
|
3381
|
+
return this.clients.serviceClient;
|
|
3382
|
+
}
|
|
3383
|
+
async getFor(ctx, userId) {
|
|
3384
|
+
assertCanRead(ctx, userId);
|
|
3385
|
+
const { data, error } = await this.client().from("user_profiles").select("platform_permissions").eq("user_id", userId).maybeSingle();
|
|
3386
|
+
if (error) throw mapError(error);
|
|
3387
|
+
return filterValid(data?.platform_permissions ?? []);
|
|
3388
|
+
}
|
|
3389
|
+
async getForBatch(ctx, userIds) {
|
|
3390
|
+
assertAdmin(ctx);
|
|
3391
|
+
const out = /* @__PURE__ */ new Map();
|
|
3392
|
+
if (userIds.length === 0) return out;
|
|
3393
|
+
const { data, error } = await this.client().from("user_profiles").select("user_id, platform_permissions").in("user_id", [...userIds]);
|
|
3394
|
+
if (error) throw mapError(error);
|
|
3395
|
+
for (const row of data ?? []) {
|
|
3396
|
+
out.set(row.user_id, filterValid(row.platform_permissions ?? []));
|
|
3397
|
+
}
|
|
3398
|
+
return out;
|
|
3399
|
+
}
|
|
3400
|
+
async set(ctx, userId, permissions) {
|
|
3401
|
+
assertAdmin(ctx);
|
|
3402
|
+
const { data: existing, error: fetchError } = await this.client().from("user_profiles").select("platform_permissions").eq("user_id", userId).maybeSingle();
|
|
3403
|
+
if (fetchError) throw mapError(fetchError);
|
|
3404
|
+
if (!existing) return "not_found";
|
|
3405
|
+
const wasAdmin = (existing.platform_permissions ?? []).includes("instance_admin");
|
|
3406
|
+
const willBeAdmin = permissions.includes("instance_admin");
|
|
3407
|
+
if (wasAdmin && !willBeAdmin) {
|
|
3408
|
+
const others = await this.countOtherEnabledAdmins(userId);
|
|
3409
|
+
if (others === 0) return "last_admin";
|
|
3410
|
+
}
|
|
3411
|
+
const { error } = await this.client().from("user_profiles").update({ platform_permissions: [...permissions] }).eq("user_id", userId);
|
|
3412
|
+
if (error) throw mapError(error);
|
|
3413
|
+
return "ok";
|
|
3414
|
+
}
|
|
3415
|
+
async hasInstanceAdmin(_ctx) {
|
|
3416
|
+
const { data, error } = await this.client().from("user_profiles").select("user_id").contains("platform_permissions", ["instance_admin"]);
|
|
3417
|
+
if (error) throw mapError(error);
|
|
3418
|
+
const candidates = (data ?? []).map((r) => r.user_id);
|
|
3419
|
+
if (candidates.length === 0) return false;
|
|
3420
|
+
for (const id of candidates) {
|
|
3421
|
+
const { data: authData } = await this.clients.serviceClient.auth.admin.getUserById(id);
|
|
3422
|
+
if (authData.user && authData.user.user_metadata?.disabled !== true) return true;
|
|
3423
|
+
}
|
|
3424
|
+
return false;
|
|
3425
|
+
}
|
|
3426
|
+
async countInstanceAdminsExcluding(_ctx, excludeUserId) {
|
|
3427
|
+
return this.countOtherEnabledAdmins(excludeUserId);
|
|
3428
|
+
}
|
|
3429
|
+
async countOtherEnabledAdmins(excludeUserId) {
|
|
3430
|
+
const { data, error } = await this.client().from("user_profiles").select("user_id").contains("platform_permissions", ["instance_admin"]).neq("user_id", excludeUserId);
|
|
3431
|
+
if (error) throw mapError(error);
|
|
3432
|
+
const candidates = (data ?? []).map((r) => r.user_id);
|
|
3433
|
+
if (candidates.length === 0) return 0;
|
|
3434
|
+
let count = 0;
|
|
3435
|
+
for (const id of candidates) {
|
|
3436
|
+
const { data: authData } = await this.clients.serviceClient.auth.admin.getUserById(id);
|
|
3437
|
+
if (authData.user && authData.user.user_metadata?.disabled !== true) count += 1;
|
|
3438
|
+
}
|
|
3439
|
+
return count;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function filterValid(raw) {
|
|
3443
|
+
return raw.filter((p) => PlatformPermissionSchema.safeParse(p).success);
|
|
3444
|
+
}
|
|
3445
|
+
function assertCanRead(ctx, userId) {
|
|
3446
|
+
if (ctx.system) return;
|
|
3447
|
+
if (ctx.userId === userId) return;
|
|
3448
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
3449
|
+
throw new ProviderError("Forbidden: cannot read another user’s permissions", 403);
|
|
3450
|
+
}
|
|
3451
|
+
function assertAdmin(ctx) {
|
|
3452
|
+
if (ctx.system) return;
|
|
3453
|
+
if (hasPermission(ctx, "instance_admin")) return;
|
|
3454
|
+
throw new ProviderError("Forbidden: instance admin required", 403);
|
|
3455
|
+
}
|
|
3456
|
+
function mapError(e) {
|
|
3457
|
+
const pg = e;
|
|
3458
|
+
if (pg?.code === "23505") return new ProviderError(pg.message ?? "Duplicate record", 409);
|
|
3459
|
+
if (pg?.code === "23503") return new ProviderError(pg.message ?? "Foreign key violation", 409);
|
|
3460
|
+
if (e instanceof Error) return e;
|
|
3461
|
+
if (e && typeof e === "object") {
|
|
3462
|
+
const obj = e;
|
|
3463
|
+
return new Error(obj.code ? `[${obj.code}] ${obj.message ?? ""}` : obj.message ?? String(e));
|
|
3464
|
+
}
|
|
3465
|
+
return new Error(String(e));
|
|
3466
|
+
}
|
|
3467
|
+
const NOT_IMPLEMENTED_MSG = "Platform projects are not supported on this deployment yet.";
|
|
3468
|
+
const notImplementedGrantStore = {
|
|
3469
|
+
listByProject: async () => {
|
|
3470
|
+
throw new ProviderError(NOT_IMPLEMENTED_MSG, 501);
|
|
3471
|
+
},
|
|
3472
|
+
create: async () => {
|
|
3473
|
+
throw new ProviderError(NOT_IMPLEMENTED_MSG, 501);
|
|
3474
|
+
},
|
|
3475
|
+
delete: async () => {
|
|
3476
|
+
throw new ProviderError(NOT_IMPLEMENTED_MSG, 501);
|
|
3477
|
+
},
|
|
3478
|
+
deleteByProject: async () => {
|
|
3479
|
+
},
|
|
3480
|
+
deleteByGranteeOrg: async () => {
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
class SupabaseDataProvider {
|
|
3484
|
+
constructor(clients, events) {
|
|
3485
|
+
this.clients = clients;
|
|
3486
|
+
this.solveMetrics = new SupabaseSolveMetricSink(clients);
|
|
3487
|
+
this.orgs = new SupabaseOrgStore(clients, events);
|
|
3488
|
+
this.projects = new SupabaseProjectStore(clients, events);
|
|
3489
|
+
this.definitions = new SupabaseDefinitionStore(clients, events);
|
|
3490
|
+
this.invites = new SupabaseInviteStore(clients, events);
|
|
3491
|
+
this.computeServer = new SupabaseComputeServerStore(clients);
|
|
3492
|
+
this.shareLinks = new SupabaseShareLinkStore(clients, events);
|
|
3493
|
+
this.userProfile = new SupabaseUserProfileProvider(clients);
|
|
3494
|
+
this.permissions = new SupabasePlatformPermissionStore(clients);
|
|
3495
|
+
this.platformProjectGrants = notImplementedGrantStore;
|
|
3496
|
+
this.auditQuery = new SupabaseAuditQuery(clients);
|
|
3497
|
+
}
|
|
3498
|
+
clients;
|
|
3499
|
+
orgs;
|
|
3500
|
+
projects;
|
|
3501
|
+
definitions;
|
|
3502
|
+
invites;
|
|
3503
|
+
computeServer;
|
|
3504
|
+
shareLinks;
|
|
3505
|
+
userProfile;
|
|
3506
|
+
permissions;
|
|
3507
|
+
platformProjectGrants;
|
|
3508
|
+
auditQuery;
|
|
3509
|
+
/**
|
|
3510
|
+
* Per-solve timing sink, built from the same client bundle. Exposed so the
|
|
3511
|
+
* app's provider wiring can hand it to `SelvaConfig.solveMetrics` without
|
|
3512
|
+
* constructing a second Supabase client.
|
|
3513
|
+
*/
|
|
3514
|
+
solveMetrics;
|
|
3515
|
+
static fromEnv(env, events) {
|
|
3516
|
+
const supabaseUrl = env.SUPABASE_URL;
|
|
3517
|
+
const anonKey = env.SUPABASE_ANON_KEY;
|
|
3518
|
+
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
3519
|
+
if (!supabaseUrl) throw new Error("Missing required env var: SUPABASE_URL");
|
|
3520
|
+
if (!anonKey) throw new Error("Missing required env var: SUPABASE_ANON_KEY");
|
|
3521
|
+
if (!serviceRoleKey) throw new Error("Missing required env var: SUPABASE_SERVICE_ROLE_KEY");
|
|
3522
|
+
const bundle = buildClientBundle({ supabaseUrl, anonKey, serviceRoleKey });
|
|
3523
|
+
return new SupabaseDataProvider(bundle, events ?? new SupabaseEventSink(bundle));
|
|
3524
|
+
}
|
|
3525
|
+
static create(opts, events) {
|
|
3526
|
+
const bundle = buildClientBundle(opts);
|
|
3527
|
+
return new SupabaseDataProvider(bundle, events ?? new SupabaseEventSink(bundle));
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Build from a pre-existing `ClientBundle`. Useful for tests or advanced
|
|
3531
|
+
* cases that need to inject a custom event sink or share a bundle externally.
|
|
3532
|
+
*/
|
|
3533
|
+
static fromBundle(bundle, events) {
|
|
3534
|
+
return new SupabaseDataProvider(bundle, events ?? new SupabaseEventSink(bundle));
|
|
3535
|
+
}
|
|
3536
|
+
/**
|
|
3537
|
+
* Expose the underlying bundle so other providers in the same package
|
|
3538
|
+
* (storage, user profile, auth) can share one set of clients per process.
|
|
3539
|
+
*/
|
|
3540
|
+
getClientBundle() {
|
|
3541
|
+
return this.clients;
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* No-op: `public.user_profiles` is auto-seeded by the `handle_new_auth_user`
|
|
3545
|
+
* trigger on every `auth.users` insert (see `0001_initial.sql`). The data
|
|
3546
|
+
* layer always has a row for any authenticated user without per-request work.
|
|
3547
|
+
*/
|
|
3548
|
+
async ensureUser() {
|
|
3549
|
+
}
|
|
3550
|
+
/**
|
|
3551
|
+
* No-op: `public.user_profiles.user_id` references `auth.users(id)` with
|
|
3552
|
+
* `on delete cascade`. Deleting the auth user removes the profile row
|
|
3553
|
+
* automatically.
|
|
3554
|
+
*/
|
|
3555
|
+
async onUserDeleted() {
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
class SupabaseAuthProvider {
|
|
3559
|
+
name = "Supabase Auth";
|
|
3560
|
+
passwordAuth;
|
|
3561
|
+
oauth;
|
|
3562
|
+
emailLink;
|
|
3563
|
+
admin;
|
|
3564
|
+
anon;
|
|
3565
|
+
anonKey;
|
|
3566
|
+
supabaseUrl;
|
|
3567
|
+
constructor(config) {
|
|
3568
|
+
this.supabaseUrl = config.supabaseUrl;
|
|
3569
|
+
this.anonKey = config.anonKey;
|
|
3570
|
+
this.admin = createClient(config.supabaseUrl, config.serviceRoleKey, {
|
|
3571
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
3572
|
+
});
|
|
3573
|
+
this.anon = createClient(config.supabaseUrl, config.anonKey, {
|
|
3574
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
3575
|
+
});
|
|
3576
|
+
this.passwordAuth = new SupabasePasswordAuth(
|
|
3577
|
+
this.admin,
|
|
3578
|
+
this.anon,
|
|
3579
|
+
(email, password) => this.signIn(email, password),
|
|
3580
|
+
config.enableSelfSignup ?? false,
|
|
3581
|
+
(user) => this.hydrate(user)
|
|
3582
|
+
);
|
|
3583
|
+
this.oauth = new SupabaseOAuthAuth(
|
|
3584
|
+
this.anon,
|
|
3585
|
+
(user) => this.hydrate(user),
|
|
3586
|
+
config.oauthProviders ?? []
|
|
3587
|
+
);
|
|
3588
|
+
this.emailLink = new SupabaseEmailLinkAuth(
|
|
3589
|
+
this.anon,
|
|
3590
|
+
(user) => this.hydrate(user),
|
|
3591
|
+
config.allowEmailLinkSignup ?? true
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
static fromEnv(env) {
|
|
3595
|
+
const supabaseUrl = env.SUPABASE_URL;
|
|
3596
|
+
const anonKey = env.SUPABASE_ANON_KEY;
|
|
3597
|
+
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
3598
|
+
if (!supabaseUrl) throw new Error("Missing required env var: SUPABASE_URL");
|
|
3599
|
+
if (!anonKey) throw new Error("Missing required env var: SUPABASE_ANON_KEY");
|
|
3600
|
+
if (!serviceRoleKey) throw new Error("Missing required env var: SUPABASE_SERVICE_ROLE_KEY");
|
|
3601
|
+
const oauthProviders = (env.SUPABASE_OAUTH_PROVIDERS ?? "").split(",").map((p) => p.trim().toLowerCase()).filter((p) => p.length > 0);
|
|
3602
|
+
return new SupabaseAuthProvider({
|
|
3603
|
+
supabaseUrl,
|
|
3604
|
+
anonKey,
|
|
3605
|
+
serviceRoleKey,
|
|
3606
|
+
enableSelfSignup: env.SUPABASE_ENABLE_SELF_SIGNUP === "true",
|
|
3607
|
+
oauthProviders,
|
|
3608
|
+
// Default true; set SUPABASE_ALLOW_EMAIL_LINK_SIGNUP=false to lock down
|
|
3609
|
+
// to invite-only and reject magic-link signups for new addresses.
|
|
3610
|
+
allowEmailLinkSignup: env.SUPABASE_ALLOW_EMAIL_LINK_SIGNUP !== "false"
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
async verifyToken(token) {
|
|
3614
|
+
if (!token) return null;
|
|
3615
|
+
const { data, error } = await this.anon.auth.getUser(token);
|
|
3616
|
+
if (error || !data.user) return null;
|
|
3617
|
+
if (data.user.user_metadata?.disabled === true) return null;
|
|
3618
|
+
return this.hydrate(data.user);
|
|
3619
|
+
}
|
|
3620
|
+
async getUser(id) {
|
|
3621
|
+
const { data, error } = await this.admin.auth.admin.getUserById(id);
|
|
3622
|
+
if (error || !data.user) return null;
|
|
3623
|
+
return this.hydrate(data.user);
|
|
3624
|
+
}
|
|
3625
|
+
async listUsers(opts) {
|
|
3626
|
+
const perPage = opts?.limit ?? 50;
|
|
3627
|
+
const page = opts?.cursor ? parseInt(opts.cursor, 10) : 1;
|
|
3628
|
+
const { data, error } = await this.admin.auth.admin.listUsers({
|
|
3629
|
+
page: Number.isFinite(page) && page >= 1 ? page : 1,
|
|
3630
|
+
perPage
|
|
3631
|
+
});
|
|
3632
|
+
if (error) throw error;
|
|
3633
|
+
const users = data.users;
|
|
3634
|
+
const hydrated = users.map((u) => this.hydrate(u));
|
|
3635
|
+
const nextCursor = users.length >= perPage ? String(page + 1) : void 0;
|
|
3636
|
+
return { items: hydrated, nextCursor };
|
|
3637
|
+
}
|
|
3638
|
+
async createUser(email) {
|
|
3639
|
+
const { data, error } = await this.admin.auth.admin.createUser({
|
|
3640
|
+
email,
|
|
3641
|
+
email_confirm: true
|
|
3642
|
+
});
|
|
3643
|
+
if (error) throw error;
|
|
3644
|
+
if (!data.user) throw new Error("createUser returned no user");
|
|
3645
|
+
return this.hydrate(data.user);
|
|
3646
|
+
}
|
|
3647
|
+
async deleteUser(id) {
|
|
3648
|
+
const { error } = await this.admin.auth.admin.deleteUser(id);
|
|
3649
|
+
if (error) {
|
|
3650
|
+
const e = error;
|
|
3651
|
+
if (e.status === 404 || /not.?found/i.test(e.message ?? "")) return "not_found";
|
|
3652
|
+
throw error;
|
|
3653
|
+
}
|
|
3654
|
+
return "ok";
|
|
3655
|
+
}
|
|
3656
|
+
async disableUser(id) {
|
|
3657
|
+
const { data: existing, error: fetchError } = await this.admin.auth.admin.getUserById(id);
|
|
3658
|
+
if (fetchError || !existing.user) return "not_found";
|
|
3659
|
+
const mergedMetadata = {
|
|
3660
|
+
...existing.user.user_metadata ?? {},
|
|
3661
|
+
disabled: true
|
|
3662
|
+
};
|
|
3663
|
+
const { error } = await this.admin.auth.admin.updateUserById(id, {
|
|
3664
|
+
user_metadata: mergedMetadata
|
|
3665
|
+
});
|
|
3666
|
+
if (error) {
|
|
3667
|
+
const e = error;
|
|
3668
|
+
if (e.status === 404 || /not.?found/i.test(e.message ?? "")) return "not_found";
|
|
3669
|
+
throw error;
|
|
3670
|
+
}
|
|
3671
|
+
return "ok";
|
|
3672
|
+
}
|
|
3673
|
+
async touchLastLogin(id) {
|
|
3674
|
+
const { data, error } = await this.admin.from("user_profiles").select("last_login_at").eq("user_id", id).maybeSingle();
|
|
3675
|
+
if (error) return;
|
|
3676
|
+
if (data?.last_login_at) {
|
|
3677
|
+
const prev = Date.parse(data.last_login_at);
|
|
3678
|
+
if (Number.isFinite(prev) && Date.now() - prev < 6e4) return;
|
|
3679
|
+
}
|
|
3680
|
+
await this.admin.from("user_profiles").update({ last_login_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("user_id", id);
|
|
3681
|
+
}
|
|
3682
|
+
// OAuth lives on `this.oauth` (typed `IOAuthAuth`); see `SupabaseOAuthAuth`
|
|
3683
|
+
// below. Mirrors the `passwordAuth` capability split.
|
|
3684
|
+
// ============================================================================
|
|
3685
|
+
// Internals
|
|
3686
|
+
// ============================================================================
|
|
3687
|
+
/**
|
|
3688
|
+
* Sign in via GoTrue. Returns the access token string and the hydrated
|
|
3689
|
+
* user. Called by `SupabasePasswordAuth.verifyLogin` — kept on the
|
|
3690
|
+
* provider class so it has access to the hydrate helper without plumbing.
|
|
3691
|
+
*/
|
|
3692
|
+
async signIn(email, password) {
|
|
3693
|
+
const { data, error } = await this.anon.auth.signInWithPassword({ email, password });
|
|
3694
|
+
if (error || !data.user || !data.session) return null;
|
|
3695
|
+
if (data.user.user_metadata?.disabled === true) return null;
|
|
3696
|
+
return { user: this.hydrate(data.user), sessionToken: data.session.access_token };
|
|
3697
|
+
}
|
|
3698
|
+
/**
|
|
3699
|
+
* Map a GoTrue `User` to our identity-only `AuthUser`. Platform permissions
|
|
3700
|
+
* live on `IPlatformPermissionStore` and profile fields (displayName,
|
|
3701
|
+
* starred, recentRuns) on `IUserProfileStore` — neither is in AuthUser.
|
|
3702
|
+
*/
|
|
3703
|
+
hydrate(user) {
|
|
3704
|
+
return {
|
|
3705
|
+
id: user.id,
|
|
3706
|
+
email: user.email ?? void 0,
|
|
3707
|
+
createdAt: user.created_at ?? void 0,
|
|
3708
|
+
lastLoginAt: user.last_sign_in_at ?? void 0,
|
|
3709
|
+
disabled: user.user_metadata?.disabled === true,
|
|
3710
|
+
metadata: user.user_metadata
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Expose the anon URL + key so other code (e.g. the same process's
|
|
3715
|
+
* storage provider or per-request client factories) can build user-scoped
|
|
3716
|
+
* clients without requiring a second config read.
|
|
3717
|
+
*/
|
|
3718
|
+
getAnonClientConfig() {
|
|
3719
|
+
return { supabaseUrl: this.supabaseUrl, anonKey: this.anonKey };
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
class SupabasePasswordAuth {
|
|
3723
|
+
constructor(admin, anon, signIn, enableSelfSignup, hydrate) {
|
|
3724
|
+
this.admin = admin;
|
|
3725
|
+
this.anon = anon;
|
|
3726
|
+
this.signIn = signIn;
|
|
3727
|
+
this.enableSelfSignup = enableSelfSignup;
|
|
3728
|
+
this.hydrate = hydrate;
|
|
3729
|
+
}
|
|
3730
|
+
admin;
|
|
3731
|
+
anon;
|
|
3732
|
+
signIn;
|
|
3733
|
+
enableSelfSignup;
|
|
3734
|
+
hydrate;
|
|
3735
|
+
async verifyLogin(email, password) {
|
|
3736
|
+
const result = await this.signIn(email, password);
|
|
3737
|
+
if (!result) return { kind: "failed", reason: "invalid_credentials" };
|
|
3738
|
+
return { kind: "success", user: result.user, sessionToken: result.sessionToken };
|
|
3739
|
+
}
|
|
3740
|
+
async createUserWithPassword(email, password) {
|
|
3741
|
+
const { data, error } = await this.admin.auth.admin.createUser({
|
|
3742
|
+
email,
|
|
3743
|
+
password,
|
|
3744
|
+
email_confirm: true
|
|
3745
|
+
});
|
|
3746
|
+
if (error) throw error;
|
|
3747
|
+
if (!data.user) throw new Error("createUserWithPassword returned no user");
|
|
3748
|
+
return this.hydrate(data.user);
|
|
3749
|
+
}
|
|
3750
|
+
async registerUser(email, password) {
|
|
3751
|
+
if (!this.enableSelfSignup) return null;
|
|
3752
|
+
const { data, error } = await this.anon.auth.signUp({ email, password });
|
|
3753
|
+
if (error || !data.user) return null;
|
|
3754
|
+
return this.hydrate(data.user);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
class SupabaseOAuthAuth {
|
|
3758
|
+
constructor(anon, hydrate, providers2) {
|
|
3759
|
+
this.anon = anon;
|
|
3760
|
+
this.hydrate = hydrate;
|
|
3761
|
+
this.providers = providers2;
|
|
3762
|
+
}
|
|
3763
|
+
anon;
|
|
3764
|
+
hydrate;
|
|
3765
|
+
providers;
|
|
3766
|
+
listProviders() {
|
|
3767
|
+
return this.providers;
|
|
3768
|
+
}
|
|
3769
|
+
async getOAuthAuthorizationUrl(provider, redirectTo) {
|
|
3770
|
+
const { data, error } = await this.anon.auth.signInWithOAuth({
|
|
3771
|
+
provider,
|
|
3772
|
+
options: { redirectTo, skipBrowserRedirect: true }
|
|
3773
|
+
});
|
|
3774
|
+
if (error || !data.url) throw error ?? new Error("signInWithOAuth returned no URL");
|
|
3775
|
+
return data.url;
|
|
3776
|
+
}
|
|
3777
|
+
async exchangeOAuthCode(code) {
|
|
3778
|
+
const { data, error } = await this.anon.auth.exchangeCodeForSession(code);
|
|
3779
|
+
if (error || !data.user || !data.session) return null;
|
|
3780
|
+
if (data.user.user_metadata?.disabled === true) return null;
|
|
3781
|
+
return {
|
|
3782
|
+
user: this.hydrate(data.user),
|
|
3783
|
+
sessionToken: data.session.access_token,
|
|
3784
|
+
refreshToken: data.session.refresh_token
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
async refreshSession(refreshToken) {
|
|
3788
|
+
const { data, error } = await this.anon.auth.refreshSession({ refresh_token: refreshToken });
|
|
3789
|
+
if (error || !data.session) return null;
|
|
3790
|
+
return {
|
|
3791
|
+
sessionToken: data.session.access_token,
|
|
3792
|
+
refreshToken: data.session.refresh_token
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
class SupabaseEmailLinkAuth {
|
|
3797
|
+
constructor(anon, hydrate, allowSignup) {
|
|
3798
|
+
this.anon = anon;
|
|
3799
|
+
this.hydrate = hydrate;
|
|
3800
|
+
this.allowSignup = allowSignup;
|
|
3801
|
+
}
|
|
3802
|
+
anon;
|
|
3803
|
+
hydrate;
|
|
3804
|
+
allowSignup;
|
|
3805
|
+
async sendMagicLink(email, callbackUrl) {
|
|
3806
|
+
const { error } = await this.anon.auth.signInWithOtp({
|
|
3807
|
+
email,
|
|
3808
|
+
options: {
|
|
3809
|
+
emailRedirectTo: callbackUrl,
|
|
3810
|
+
shouldCreateUser: this.allowSignup
|
|
3811
|
+
}
|
|
3812
|
+
});
|
|
3813
|
+
if (!error) return { ok: true };
|
|
3814
|
+
const status = error.status;
|
|
3815
|
+
const code = error.code;
|
|
3816
|
+
const message = error.message ?? "";
|
|
3817
|
+
if (status === 429 || /rate.?limit/i.test(message))
|
|
3818
|
+
return { ok: false, reason: "rate_limited" };
|
|
3819
|
+
if (code === "otp_disabled" || code === "signup_disabled" || /signup.*disabled/i.test(message)) {
|
|
3820
|
+
return { ok: false, reason: "signup_disabled" };
|
|
3821
|
+
}
|
|
3822
|
+
if (code === "validation_failed" || /invalid.*email|email.*invalid/i.test(message)) {
|
|
3823
|
+
return { ok: false, reason: "invalid_email" };
|
|
3824
|
+
}
|
|
3825
|
+
throw error;
|
|
3826
|
+
}
|
|
3827
|
+
async verifyMagicLink(rawCallbackUrl) {
|
|
3828
|
+
const params = parseCallbackParams(rawCallbackUrl);
|
|
3829
|
+
if (!params) return null;
|
|
3830
|
+
const { tokenHash, type } = params;
|
|
3831
|
+
const { data, error } = await this.anon.auth.verifyOtp({
|
|
3832
|
+
token_hash: tokenHash,
|
|
3833
|
+
type
|
|
3834
|
+
});
|
|
3835
|
+
if (error || !data.user || !data.session) return null;
|
|
3836
|
+
if (data.user.user_metadata?.disabled === true) return null;
|
|
3837
|
+
return {
|
|
3838
|
+
user: this.hydrate(data.user),
|
|
3839
|
+
sessionToken: data.session.access_token,
|
|
3840
|
+
refreshToken: data.session.refresh_token
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
function parseCallbackParams(raw) {
|
|
3845
|
+
let search;
|
|
3846
|
+
try {
|
|
3847
|
+
search = new URL(raw).searchParams;
|
|
3848
|
+
} catch {
|
|
3849
|
+
search = new URLSearchParams(raw.startsWith("?") ? raw.slice(1) : raw);
|
|
3850
|
+
}
|
|
3851
|
+
const tokenHash = search.get("token_hash");
|
|
3852
|
+
const type = search.get("type");
|
|
3853
|
+
if (!tokenHash) return null;
|
|
3854
|
+
const allowed = /* @__PURE__ */ new Set(["magiclink", "email", "signup", "invite", "recovery"]);
|
|
3855
|
+
if (!type || !allowed.has(type)) return null;
|
|
3856
|
+
return {
|
|
3857
|
+
tokenHash,
|
|
3858
|
+
type
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
const empty = () => ({ users: [] });
|
|
3862
|
+
const LAST_LOGIN_DEBOUNCE_MS = 6e4;
|
|
3863
|
+
async function readFile(filePath) {
|
|
3864
|
+
try {
|
|
3865
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
3866
|
+
return JSON.parse(raw);
|
|
3867
|
+
} catch (err) {
|
|
3868
|
+
if (err.code === "ENOENT") return empty();
|
|
3869
|
+
throw err;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
async function writeFile(filePath, data) {
|
|
3873
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
3874
|
+
const tmp = `${filePath}.tmp`;
|
|
3875
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, " "), "utf-8");
|
|
3876
|
+
await fs.rename(tmp, filePath);
|
|
3877
|
+
}
|
|
3878
|
+
function createAllowlistStore(filePath) {
|
|
3879
|
+
const norm = (upn) => upn.trim().toLowerCase();
|
|
3880
|
+
return {
|
|
3881
|
+
async findByUpn(upn) {
|
|
3882
|
+
const { users } = await readFile(filePath);
|
|
3883
|
+
const key = norm(upn);
|
|
3884
|
+
return users.find((u) => u.upn === key) ?? null;
|
|
3885
|
+
},
|
|
3886
|
+
async findByEmail(email) {
|
|
3887
|
+
const { users } = await readFile(filePath);
|
|
3888
|
+
const key = norm(email);
|
|
3889
|
+
return users.find((u) => norm(u.email ?? "") === key || u.upn === key) ?? null;
|
|
3890
|
+
},
|
|
3891
|
+
async findById(id) {
|
|
3892
|
+
const { users } = await readFile(filePath);
|
|
3893
|
+
return users.find((u) => u.id === id) ?? null;
|
|
3894
|
+
},
|
|
3895
|
+
async listUsers() {
|
|
3896
|
+
const { users } = await readFile(filePath);
|
|
3897
|
+
return users;
|
|
3898
|
+
},
|
|
3899
|
+
async createUser(upn) {
|
|
3900
|
+
const file = await readFile(filePath);
|
|
3901
|
+
const key = norm(upn);
|
|
3902
|
+
if (file.users.some((u) => u.upn === key)) {
|
|
3903
|
+
throw new ProviderError(`User with UPN "${upn}" already exists`, 409);
|
|
3904
|
+
}
|
|
3905
|
+
const entry = {
|
|
3906
|
+
id: randomUUID(),
|
|
3907
|
+
upn: key,
|
|
3908
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3909
|
+
};
|
|
3910
|
+
file.users.push(entry);
|
|
3911
|
+
await writeFile(filePath, file);
|
|
3912
|
+
return entry;
|
|
3913
|
+
},
|
|
3914
|
+
async materializeFromHeaders(id, fields) {
|
|
3915
|
+
const file = await readFile(filePath);
|
|
3916
|
+
const user = file.users.find((u) => u.id === id);
|
|
3917
|
+
if (!user) return;
|
|
3918
|
+
let dirty = false;
|
|
3919
|
+
if (!user.email && fields.email) {
|
|
3920
|
+
user.email = fields.email;
|
|
3921
|
+
dirty = true;
|
|
3922
|
+
}
|
|
3923
|
+
if (!user.displayName && fields.displayName) {
|
|
3924
|
+
user.displayName = fields.displayName;
|
|
3925
|
+
dirty = true;
|
|
3926
|
+
}
|
|
3927
|
+
if (dirty) await writeFile(filePath, file);
|
|
3928
|
+
},
|
|
3929
|
+
async rebindUpn(id, upn) {
|
|
3930
|
+
const file = await readFile(filePath);
|
|
3931
|
+
const key = norm(upn);
|
|
3932
|
+
const user = file.users.find((u) => u.id === id);
|
|
3933
|
+
if (!user || user.upn === key) return;
|
|
3934
|
+
if (file.users.some((u) => u.id !== id && u.upn === key)) return;
|
|
3935
|
+
user.upn = key;
|
|
3936
|
+
await writeFile(filePath, file);
|
|
3937
|
+
},
|
|
3938
|
+
async setDisabled(id, disabled) {
|
|
3939
|
+
const file = await readFile(filePath);
|
|
3940
|
+
const user = file.users.find((u) => u.id === id);
|
|
3941
|
+
if (!user) throw new ProviderError(`User "${id}" not found`, 404);
|
|
3942
|
+
user.disabled = disabled;
|
|
3943
|
+
await writeFile(filePath, file);
|
|
3944
|
+
},
|
|
3945
|
+
async touchLastLogin(id) {
|
|
3946
|
+
const file = await readFile(filePath);
|
|
3947
|
+
const user = file.users.find((u) => u.id === id);
|
|
3948
|
+
if (!user) return;
|
|
3949
|
+
const now = Date.now();
|
|
3950
|
+
if (user.lastLoginAt) {
|
|
3951
|
+
const prev = Date.parse(user.lastLoginAt);
|
|
3952
|
+
if (Number.isFinite(prev) && now - prev < LAST_LOGIN_DEBOUNCE_MS) return;
|
|
3953
|
+
}
|
|
3954
|
+
user.lastLoginAt = new Date(now).toISOString();
|
|
3955
|
+
await writeFile(filePath, file);
|
|
3956
|
+
},
|
|
3957
|
+
async deleteUser(id) {
|
|
3958
|
+
const file = await readFile(filePath);
|
|
3959
|
+
const before = file.users.length;
|
|
3960
|
+
file.users = file.users.filter((u) => u.id !== id);
|
|
3961
|
+
if (file.users.length === before) throw new ProviderError(`User "${id}" not found`, 404);
|
|
3962
|
+
await writeFile(filePath, file);
|
|
3963
|
+
}
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
const DEFAULT_HEADERS = {
|
|
3967
|
+
upn: "SELVA-UserPrincipalName",
|
|
3968
|
+
email: "SELVA-Email",
|
|
3969
|
+
displayName: "SELVA-DisplayName"
|
|
3970
|
+
};
|
|
3971
|
+
function toAuthUser(u) {
|
|
3972
|
+
return {
|
|
3973
|
+
id: u.id,
|
|
3974
|
+
email: u.email,
|
|
3975
|
+
metadata: { upn: u.upn, displayName: u.displayName },
|
|
3976
|
+
createdAt: u.createdAt,
|
|
3977
|
+
lastLoginAt: u.lastLoginAt,
|
|
3978
|
+
disabled: u.disabled
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
class HeaderProxyAuth {
|
|
3982
|
+
constructor(users, headers, bootstrapPolicy) {
|
|
3983
|
+
this.users = users;
|
|
3984
|
+
this.headers = headers;
|
|
3985
|
+
this.bootstrapPolicy = bootstrapPolicy;
|
|
3986
|
+
}
|
|
3987
|
+
users;
|
|
3988
|
+
headers;
|
|
3989
|
+
bootstrapPolicy;
|
|
3990
|
+
// One-shot diagnostic flag. Flipped the first time we see a request that
|
|
3991
|
+
// carries none of the configured SELVA-* headers, so deploys with a
|
|
3992
|
+
// misconfigured proxy get a single loud warning in the logs without
|
|
3993
|
+
// spamming on every anonymous request. Reset is intentional — the
|
|
3994
|
+
// process restarts and you see the warning again next deploy.
|
|
3995
|
+
missingHeadersWarned = false;
|
|
3996
|
+
setBootstrapPolicy(policy) {
|
|
3997
|
+
this.bootstrapPolicy = policy ?? void 0;
|
|
3998
|
+
}
|
|
3999
|
+
/**
|
|
4000
|
+
* Names of the configured identity headers, in priority order. Exposed so
|
|
4001
|
+
* the hook layer can emit per-page diagnostics ("you hit /login without
|
|
4002
|
+
* these headers") without having to know provider internals.
|
|
4003
|
+
*/
|
|
4004
|
+
get configuredHeaderNames() {
|
|
4005
|
+
return [this.headers.upn, this.headers.email, this.headers.displayName];
|
|
4006
|
+
}
|
|
4007
|
+
/** True iff none of the configured identity headers are present. */
|
|
4008
|
+
hasNoIdentityHeaders(headers) {
|
|
4009
|
+
return !headers.get(this.headers.upn) && !headers.get(this.headers.email) && !headers.get(this.headers.displayName);
|
|
4010
|
+
}
|
|
4011
|
+
async identifyFromHeaders(headers) {
|
|
4012
|
+
const upn = headers.get(this.headers.upn);
|
|
4013
|
+
if (!upn || !upn.trim()) {
|
|
4014
|
+
if (!this.missingHeadersWarned && this.hasNoIdentityHeaders(headers)) {
|
|
4015
|
+
this.missingHeadersWarned = true;
|
|
4016
|
+
console.warn(
|
|
4017
|
+
`[HeaderAuth] No identity headers received on the first non-authed request. Expected one of: ${this.headers.upn}, ${this.headers.email}, ${this.headers.displayName}. If you reach the app through your forward-auth proxy, this means the proxy is not forwarding the configured headers — check your forward_auth / copy_headers config. If you're hitting the app directly (bypassing the proxy), bind the process to 127.0.0.1 and only reach it through the proxy. See @selvajs/header-auth-provider README.`
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
return null;
|
|
4021
|
+
}
|
|
4022
|
+
const email = headers.get(this.headers.email)?.trim() || void 0;
|
|
4023
|
+
const displayName = headers.get(this.headers.displayName)?.trim() || void 0;
|
|
4024
|
+
let entry = await this.users.findByUpn(upn);
|
|
4025
|
+
if (!entry && email) {
|
|
4026
|
+
const byEmail = await this.users.findByEmail(email);
|
|
4027
|
+
if (byEmail) {
|
|
4028
|
+
entry = byEmail;
|
|
4029
|
+
if (byEmail.upn !== upn.trim().toLowerCase()) {
|
|
4030
|
+
await this.users.rebindUpn(byEmail.id, upn).catch(() => {
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
if (!entry && this.bootstrapPolicy) {
|
|
4036
|
+
const allowed = await this.bootstrapPolicy({ upn, email });
|
|
4037
|
+
if (allowed) {
|
|
4038
|
+
try {
|
|
4039
|
+
entry = await this.users.createUser(upn);
|
|
4040
|
+
} catch {
|
|
4041
|
+
entry = await this.users.findByUpn(upn);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
if (!entry || entry.disabled) {
|
|
4046
|
+
return null;
|
|
4047
|
+
}
|
|
4048
|
+
if (!entry.email && email || !entry.displayName && displayName) {
|
|
4049
|
+
await this.users.materializeFromHeaders(entry.id, { email, displayName }).catch(() => {
|
|
4050
|
+
});
|
|
4051
|
+
if (email && !entry.email) entry.email = email;
|
|
4052
|
+
if (displayName && !entry.displayName) entry.displayName = displayName;
|
|
4053
|
+
}
|
|
4054
|
+
await this.users.touchLastLogin(entry.id).catch(() => {
|
|
4055
|
+
});
|
|
4056
|
+
return toAuthUser(entry);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
class HeaderAuthProvider {
|
|
4060
|
+
users;
|
|
4061
|
+
headers;
|
|
4062
|
+
name = "Header (Forward Auth)";
|
|
4063
|
+
proxyAuth;
|
|
4064
|
+
constructor(config) {
|
|
4065
|
+
this.users = createAllowlistStore(config.allowlistFilePath);
|
|
4066
|
+
this.headers = { ...DEFAULT_HEADERS, ...config.headers };
|
|
4067
|
+
this.proxyAuth = new HeaderProxyAuth(this.users, this.headers, config.bootstrapAllowlistPolicy);
|
|
4068
|
+
}
|
|
4069
|
+
/**
|
|
4070
|
+
* Late-bind a bootstrap-allowlist policy. Useful when the policy needs
|
|
4071
|
+
* runtime state the platform layer owns (e.g. `hasInstanceAdmin`) and
|
|
4072
|
+
* therefore can't be wired up at provider construction time. Pass `null`
|
|
4073
|
+
* to clear.
|
|
4074
|
+
*/
|
|
4075
|
+
setBootstrapAllowlistPolicy(policy) {
|
|
4076
|
+
this.proxyAuth.setBootstrapPolicy(policy);
|
|
4077
|
+
}
|
|
4078
|
+
static fromEnv(env) {
|
|
4079
|
+
const dir = env.HEADER_AUTH_DATA_DIR ?? env.DATA_PATH;
|
|
4080
|
+
if (!dir) {
|
|
4081
|
+
throw new Error(
|
|
4082
|
+
"Missing required env var: HEADER_AUTH_DATA_DIR (or DATA_PATH as fallback). This directory holds header-allowlist.json — the pre-provisioned UPN list."
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
return new HeaderAuthProvider({
|
|
4086
|
+
allowlistFilePath: path.join(dir, "header-allowlist.json"),
|
|
4087
|
+
headers: {
|
|
4088
|
+
upn: env.HEADER_AUTH_UPN_HEADER ?? DEFAULT_HEADERS.upn,
|
|
4089
|
+
email: env.HEADER_AUTH_EMAIL_HEADER ?? DEFAULT_HEADERS.email,
|
|
4090
|
+
displayName: env.HEADER_AUTH_DISPLAY_NAME_HEADER ?? DEFAULT_HEADERS.displayName
|
|
4091
|
+
}
|
|
4092
|
+
});
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* No tokens are issued by this provider — identity rides on every request
|
|
4096
|
+
* via the trusted-proxy headers. Always returns null; the hook layer
|
|
4097
|
+
* falls through to `proxyAuth.identifyFromHeaders`.
|
|
4098
|
+
*/
|
|
4099
|
+
async verifyToken(_token) {
|
|
4100
|
+
return null;
|
|
4101
|
+
}
|
|
4102
|
+
async getUser(id) {
|
|
4103
|
+
const u = await this.users.findById(id);
|
|
4104
|
+
return u ? toAuthUser(u) : null;
|
|
4105
|
+
}
|
|
4106
|
+
async listUsers(opts) {
|
|
4107
|
+
const all = await this.users.listUsers();
|
|
4108
|
+
const limit = Math.min(Math.max(1, opts?.limit ?? 25), 200);
|
|
4109
|
+
const offset = opts?.cursor ? parseInt(opts.cursor, 10) || 0 : 0;
|
|
4110
|
+
const slice = all.slice(offset, offset + limit).map(toAuthUser);
|
|
4111
|
+
const nextOffset = offset + slice.length;
|
|
4112
|
+
return {
|
|
4113
|
+
items: slice,
|
|
4114
|
+
nextCursor: nextOffset < all.length ? String(nextOffset) : void 0
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
/**
|
|
4118
|
+
* Allowlist a UPN. Admin POST `/admin/api/users` with `{ email }`; the
|
|
4119
|
+
* email IS the UPN for M365 / Entra deployments where they match. For
|
|
4120
|
+
* other IdPs, document the UPN format in your README/onboarding.
|
|
4121
|
+
*/
|
|
4122
|
+
async createUser(upn) {
|
|
4123
|
+
return toAuthUser(await this.users.createUser(upn));
|
|
4124
|
+
}
|
|
4125
|
+
async deleteUser(id) {
|
|
4126
|
+
const target = await this.users.findById(id);
|
|
4127
|
+
if (!target) return "not_found";
|
|
4128
|
+
try {
|
|
4129
|
+
await this.users.deleteUser(id);
|
|
4130
|
+
return "ok";
|
|
4131
|
+
} catch (err) {
|
|
4132
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
4133
|
+
throw err;
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
async disableUser(id) {
|
|
4137
|
+
const target = await this.users.findById(id);
|
|
4138
|
+
if (!target) return "not_found";
|
|
4139
|
+
try {
|
|
4140
|
+
await this.users.setDisabled(id, true);
|
|
4141
|
+
return "ok";
|
|
4142
|
+
} catch (err) {
|
|
4143
|
+
if (err instanceof ProviderError && err.statusCode === 404) return "not_found";
|
|
4144
|
+
throw err;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
async touchLastLogin(id) {
|
|
4148
|
+
await this.users.touchLastLogin(id);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
class DefinitionService {
|
|
4152
|
+
constructor(data, storage) {
|
|
4153
|
+
this.data = data;
|
|
4154
|
+
this.storage = storage;
|
|
4155
|
+
}
|
|
4156
|
+
data;
|
|
4157
|
+
storage;
|
|
4158
|
+
async create(ctx, input, file, schema) {
|
|
4159
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4160
|
+
const actor = ctx.userId || input.ownerId;
|
|
4161
|
+
const record = {
|
|
4162
|
+
guid: input.guid,
|
|
4163
|
+
projectId: input.projectId,
|
|
4164
|
+
ownerId: input.ownerId,
|
|
4165
|
+
createdBy: actor,
|
|
4166
|
+
updatedBy: actor,
|
|
4167
|
+
displayName: input.displayName,
|
|
4168
|
+
description: input.description,
|
|
4169
|
+
category: input.category,
|
|
4170
|
+
tags: input.tags,
|
|
4171
|
+
coverImage: input.coverImage,
|
|
4172
|
+
computeServerId: input.computeServerId,
|
|
4173
|
+
status: "pending",
|
|
4174
|
+
solveCount: 0,
|
|
4175
|
+
liveVersionId: null,
|
|
4176
|
+
draftVersionId: null,
|
|
4177
|
+
createdAt: now,
|
|
4178
|
+
updatedAt: now,
|
|
4179
|
+
deletedAt: null
|
|
4180
|
+
};
|
|
4181
|
+
await this.data.definitions.create(ctx, record);
|
|
4182
|
+
const versionId = randomUUID();
|
|
4183
|
+
const fileKey = definitionPaths.version(input.guid, 1, input.fileExt);
|
|
4184
|
+
await this.storage.put(fileKey, file, "application/octet-stream");
|
|
4185
|
+
const version = {
|
|
4186
|
+
id: versionId,
|
|
4187
|
+
definitionId: input.guid,
|
|
4188
|
+
versionNumber: 1,
|
|
4189
|
+
fileExt: input.fileExt,
|
|
4190
|
+
fileKey,
|
|
4191
|
+
originalFilename: input.originalFilename,
|
|
4192
|
+
uploadedBy: actor,
|
|
4193
|
+
uploadedAt: now,
|
|
4194
|
+
schema,
|
|
4195
|
+
schemaExtractedAt: now
|
|
4196
|
+
};
|
|
4197
|
+
await this.data.definitions.createVersion(ctx, version);
|
|
4198
|
+
await this.data.definitions.attachInitialVersion(ctx, input.guid, versionId);
|
|
4199
|
+
return {
|
|
4200
|
+
record: { ...record, status: "draft", liveVersionId: versionId, draftVersionId: versionId },
|
|
4201
|
+
version
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* Upload a new version of an existing definition. Writes the blob, inserts
|
|
4206
|
+
* the version row, and advances the draft pointer. `live` is unchanged —
|
|
4207
|
+
* use `publish` to promote.
|
|
4208
|
+
*/
|
|
4209
|
+
async uploadVersion(ctx, guid, file, ext, originalName, schema, changeNote) {
|
|
4210
|
+
const existing = await this.data.definitions.get(ctx, guid);
|
|
4211
|
+
if (!existing) throw new ProviderError(`Definition not found: ${guid}`, 404);
|
|
4212
|
+
const versions = await this.data.definitions.listVersions(ctx, guid, { limit: 1 });
|
|
4213
|
+
const next = (versions.items[0]?.versionNumber ?? 0) + 1;
|
|
4214
|
+
const versionId = randomUUID();
|
|
4215
|
+
const fileKey = definitionPaths.version(guid, next, ext);
|
|
4216
|
+
await this.storage.put(fileKey, file, "application/octet-stream");
|
|
4217
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4218
|
+
const actor = ctx.userId || existing.ownerId;
|
|
4219
|
+
const version = {
|
|
4220
|
+
id: versionId,
|
|
4221
|
+
definitionId: guid,
|
|
4222
|
+
versionNumber: next,
|
|
4223
|
+
fileExt: ext,
|
|
4224
|
+
fileKey,
|
|
4225
|
+
originalFilename: originalName,
|
|
4226
|
+
uploadedBy: actor,
|
|
4227
|
+
uploadedAt: now,
|
|
4228
|
+
changeNote: changeNote?.trim() || void 0,
|
|
4229
|
+
schema,
|
|
4230
|
+
schemaExtractedAt: now
|
|
4231
|
+
};
|
|
4232
|
+
await this.data.definitions.createVersion(ctx, version);
|
|
4233
|
+
await this.data.definitions.setDraftVersion(ctx, guid, versionId);
|
|
4234
|
+
return version;
|
|
4235
|
+
}
|
|
4236
|
+
/**
|
|
4237
|
+
* Advance the live channel. If `versionId` is omitted, promotes the
|
|
4238
|
+
* current draft. Pass an arbitrary version id to roll forward/back —
|
|
4239
|
+
* spec §6 makes rollback a first-class operation.
|
|
4240
|
+
*/
|
|
4241
|
+
async publish(ctx, guid, versionId) {
|
|
4242
|
+
const existing = await this.data.definitions.get(ctx, guid);
|
|
4243
|
+
if (!existing) throw new ProviderError(`Definition not found: ${guid}`, 404);
|
|
4244
|
+
const target = versionId ?? existing.draftVersionId;
|
|
4245
|
+
if (!target) throw new ProviderError("No version to publish", 400);
|
|
4246
|
+
const version = await this.data.definitions.getVersion(ctx, target);
|
|
4247
|
+
if (!version || version.definitionId !== guid) {
|
|
4248
|
+
throw new ProviderError(`Version '${target}' not found for this definition`, 404);
|
|
4249
|
+
}
|
|
4250
|
+
await this.data.definitions.setLiveVersion(ctx, guid, target);
|
|
4251
|
+
if (existing.status === "draft" || existing.status === "pending") {
|
|
4252
|
+
await this.data.definitions.update(ctx, guid, { status: "published" });
|
|
4253
|
+
}
|
|
4254
|
+
return version;
|
|
4255
|
+
}
|
|
4256
|
+
async deleteVersion(ctx, guid, versionId) {
|
|
4257
|
+
const version = await this.data.definitions.getVersion(ctx, versionId);
|
|
4258
|
+
if (!version || version.definitionId !== guid) {
|
|
4259
|
+
throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
|
|
4260
|
+
}
|
|
4261
|
+
await this.data.definitions.deleteVersion(ctx, versionId);
|
|
4262
|
+
await this.storage.delete(version.fileKey);
|
|
4263
|
+
}
|
|
4264
|
+
async updateMeta(ctx, guid, patch) {
|
|
4265
|
+
await this.data.definitions.update(ctx, guid, patch);
|
|
4266
|
+
}
|
|
4267
|
+
async saveCoverImage(ctx, guid, imageData) {
|
|
4268
|
+
const path2 = definitionPaths.image(guid);
|
|
4269
|
+
await this.storage.put(path2, imageData, "image/webp");
|
|
4270
|
+
const url = this.storage.getPublicUrl(path2);
|
|
4271
|
+
await this.data.definitions.update(ctx, guid, { coverImage: url });
|
|
4272
|
+
return url;
|
|
4273
|
+
}
|
|
4274
|
+
async delete(ctx, guid) {
|
|
4275
|
+
await this.data.definitions.delete(ctx, guid);
|
|
4276
|
+
await this.storage.deletePrefix(definitionPaths.prefix(guid));
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
function envBool(e, key) {
|
|
4280
|
+
const v = e[key]?.toLowerCase();
|
|
4281
|
+
return v === "true" || v === "1" || v === "yes";
|
|
4282
|
+
}
|
|
4283
|
+
function pickAuth(e) {
|
|
4284
|
+
const choice = (e.SELVA_AUTH_PROVIDER ?? "local").toLowerCase();
|
|
4285
|
+
switch (choice) {
|
|
4286
|
+
case "local":
|
|
4287
|
+
return LocalAuthProvider.fromEnv(e);
|
|
4288
|
+
case "supabase":
|
|
4289
|
+
return SupabaseAuthProvider.fromEnv(e);
|
|
4290
|
+
case "header":
|
|
4291
|
+
return HeaderAuthProvider.fromEnv(e);
|
|
4292
|
+
default:
|
|
4293
|
+
throw new Error(
|
|
4294
|
+
`Unknown SELVA_AUTH_PROVIDER="${choice}". Expected: local | supabase | header.`
|
|
4295
|
+
);
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
function pickData(e) {
|
|
4299
|
+
const choice = (e.SELVA_DATA_PROVIDER ?? "local").toLowerCase();
|
|
4300
|
+
switch (choice) {
|
|
4301
|
+
case "local":
|
|
4302
|
+
return LocalDataProvider.fromEnv(e);
|
|
4303
|
+
case "supabase":
|
|
4304
|
+
return SupabaseDataProvider.fromEnv(e);
|
|
4305
|
+
default:
|
|
4306
|
+
throw new Error(`Unknown SELVA_DATA_PROVIDER="${choice}". Expected: local | supabase.`);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function pickStorage(e) {
|
|
4310
|
+
const choice = (e.SELVA_STORAGE_PROVIDER ?? "local").toLowerCase();
|
|
4311
|
+
switch (choice) {
|
|
4312
|
+
case "local":
|
|
4313
|
+
return LocalStorageProvider.fromEnv(e);
|
|
4314
|
+
case "supabase":
|
|
4315
|
+
return SupabaseStorageProvider.fromEnv(e);
|
|
4316
|
+
default:
|
|
4317
|
+
throw new Error(`Unknown SELVA_STORAGE_PROVIDER="${choice}". Expected: local | supabase.`);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
function pickSolveMetrics(data) {
|
|
4321
|
+
const candidate = data.solveMetrics;
|
|
4322
|
+
if (candidate && typeof candidate.record === "function") {
|
|
4323
|
+
return candidate;
|
|
4324
|
+
}
|
|
4325
|
+
return void 0;
|
|
4326
|
+
}
|
|
4327
|
+
function pickTenancy(e) {
|
|
4328
|
+
const choice = (e.SELVA_TENANCY ?? "single").toLowerCase();
|
|
4329
|
+
if (choice !== "single" && choice !== "multi") {
|
|
4330
|
+
throw new Error(`Unknown SELVA_TENANCY="${choice}". Expected: single | multi.`);
|
|
4331
|
+
}
|
|
4332
|
+
return choice;
|
|
4333
|
+
}
|
|
4334
|
+
const defaultConfig = defineConfig((e) => {
|
|
4335
|
+
const data = pickData(e);
|
|
4336
|
+
return {
|
|
4337
|
+
tenancy: pickTenancy(e),
|
|
4338
|
+
flags: {
|
|
4339
|
+
ALLOW_CROSS_ORG_PUBLIC: envBool(e, "SELVA_FLAG_ALLOW_CROSS_ORG_PUBLIC"),
|
|
4340
|
+
ALLOW_ORG_COMPUTE_OVERRIDE: envBool(e, "SELVA_FLAG_ALLOW_ORG_COMPUTE_OVERRIDE"),
|
|
4341
|
+
ALLOW_ORG_CREATION: envBool(e, "SELVA_FLAG_ALLOW_ORG_CREATION"),
|
|
4342
|
+
ENABLE_PLATFORM_PROJECTS: envBool(e, "SELVA_FLAG_ENABLE_PLATFORM_PROJECTS"),
|
|
4343
|
+
ENABLE_SHARING: envBool(e, "SELVA_FLAG_ENABLE_SHARING")
|
|
4344
|
+
},
|
|
4345
|
+
branding: {
|
|
4346
|
+
name: e.SELVA_BRAND_NAME,
|
|
4347
|
+
copyrightName: e.SELVA_BRAND_COPYRIGHT_NAME,
|
|
4348
|
+
tagline: e.SELVA_BRAND_TAGLINE,
|
|
4349
|
+
description: e.SELVA_BRAND_DESCRIPTION
|
|
4350
|
+
},
|
|
4351
|
+
auth: pickAuth(e),
|
|
4352
|
+
data,
|
|
4353
|
+
storage: pickStorage(e),
|
|
4354
|
+
solveMetrics: pickSolveMetrics(data)
|
|
4355
|
+
};
|
|
4356
|
+
});
|
|
4357
|
+
async function loadRawConfig() {
|
|
4358
|
+
const override = private_env.SELVA_CONFIG_PATH;
|
|
4359
|
+
if (!override) {
|
|
4360
|
+
return defaultConfig;
|
|
4361
|
+
}
|
|
4362
|
+
const abs = resolve(process.cwd(), override);
|
|
4363
|
+
if (!existsSync(abs)) {
|
|
4364
|
+
throw new Error(`SELVA_CONFIG_PATH=${override} resolved to ${abs} which does not exist.`);
|
|
4365
|
+
}
|
|
4366
|
+
const mod = await import(
|
|
4367
|
+
/* @vite-ignore */
|
|
4368
|
+
pathToFileURL(abs).href
|
|
4369
|
+
);
|
|
4370
|
+
return mod.default;
|
|
4371
|
+
}
|
|
4372
|
+
const _raw = await loadRawConfig();
|
|
4373
|
+
let _providers;
|
|
4374
|
+
function resolveProviders() {
|
|
4375
|
+
if (_providers) return _providers;
|
|
4376
|
+
_providers = typeof _raw === "function" ? _raw(private_env) : _raw;
|
|
4377
|
+
console.info(
|
|
4378
|
+
`[selva] providers wired: auth=${_providers.auth.name} data=${_providers.data.constructor.name} storage=${_providers.storage.constructor.name} tenancy=${_providers.tenancy ?? "single"}` + (private_env.SELVA_CONFIG_PATH ? ` config=${private_env.SELVA_CONFIG_PATH}` : "")
|
|
4379
|
+
);
|
|
4380
|
+
return _providers;
|
|
4381
|
+
}
|
|
4382
|
+
const providers = new Proxy({}, {
|
|
4383
|
+
get(_t, prop) {
|
|
4384
|
+
return Reflect.get(resolveProviders(), prop);
|
|
4385
|
+
}
|
|
4386
|
+
});
|
|
4387
|
+
function getTenancy() {
|
|
4388
|
+
return resolveProviders().tenancy ?? "single";
|
|
4389
|
+
}
|
|
4390
|
+
function getBranding() {
|
|
4391
|
+
const brand = resolveProviders().branding ?? {};
|
|
4392
|
+
const name = brand.name?.trim() || "Selva";
|
|
4393
|
+
return {
|
|
4394
|
+
name,
|
|
4395
|
+
copyrightName: brand.copyrightName?.trim() || name,
|
|
4396
|
+
tagline: brand.tagline?.trim() || "Turn Grasshopper definitions into tools anyone can use.",
|
|
4397
|
+
description: brand.description?.trim() || `Build and deploy interactive web applications powered by Grasshopper definitions with ${name}.`
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
function flag(name) {
|
|
4401
|
+
return isFlagEnabled(resolveProviders(), name);
|
|
4402
|
+
}
|
|
4403
|
+
let _definitionService;
|
|
4404
|
+
function getDefinitionService() {
|
|
4405
|
+
if (!_definitionService) {
|
|
4406
|
+
const p = resolveProviders();
|
|
4407
|
+
_definitionService = new DefinitionService(p.data, p.storage);
|
|
4408
|
+
}
|
|
4409
|
+
return _definitionService;
|
|
4410
|
+
}
|
|
4411
|
+
function getAuthProvider() {
|
|
4412
|
+
return resolveProviders().auth;
|
|
4413
|
+
}
|
|
4414
|
+
function getStorageProvider() {
|
|
4415
|
+
return resolveProviders().storage;
|
|
4416
|
+
}
|
|
4417
|
+
function getDataProvider() {
|
|
4418
|
+
return resolveProviders().data;
|
|
4419
|
+
}
|
|
4420
|
+
function getOrganizationProvider() {
|
|
4421
|
+
return resolveProviders().data.orgs;
|
|
4422
|
+
}
|
|
4423
|
+
function getProjectProvider() {
|
|
4424
|
+
return resolveProviders().data.projects;
|
|
4425
|
+
}
|
|
4426
|
+
function getDefinitionMeta() {
|
|
4427
|
+
return resolveProviders().data.definitions;
|
|
4428
|
+
}
|
|
4429
|
+
function getComputeServerConfigStore() {
|
|
4430
|
+
return resolveProviders().data.computeServer;
|
|
4431
|
+
}
|
|
4432
|
+
function getUserProfileStore() {
|
|
4433
|
+
return resolveProviders().data.userProfile;
|
|
4434
|
+
}
|
|
4435
|
+
function getInviteStore() {
|
|
4436
|
+
return resolveProviders().data.invites;
|
|
4437
|
+
}
|
|
4438
|
+
function getPermissionStore() {
|
|
4439
|
+
return resolveProviders().data.permissions;
|
|
4440
|
+
}
|
|
4441
|
+
function getPlatformProjectGrantStore() {
|
|
4442
|
+
return resolveProviders().data.platformProjectGrants;
|
|
4443
|
+
}
|
|
4444
|
+
let _solveMetricSink;
|
|
4445
|
+
function getSolveMetricSink() {
|
|
4446
|
+
if (!_solveMetricSink) {
|
|
4447
|
+
_solveMetricSink = resolveProviders().solveMetrics ?? new NoopSolveMetricSink();
|
|
4448
|
+
}
|
|
4449
|
+
return _solveMetricSink;
|
|
4450
|
+
}
|
|
4451
|
+
function getAuditQuery() {
|
|
4452
|
+
return providers.data.auditQuery ?? null;
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
export { LocalComputeServerStore as L, MAX_PAGE_LIMIT as M, ProviderError as P, getAuthProvider as a, getBranding as b, getComputeServerConfigStore as c, definitionPaths as d, getDataProvider as e, flag as f, getAuditQuery as g, getDefinitionMeta as h, getDefinitionService as i, getInviteStore as j, getOrganizationProvider as k, getPermissionStore as l, getPlatformProjectGrantStore as m, getProjectProvider as n, getSolveMetricSink as o, getStorageProvider as p, getTenancy as q, getUserProfileStore as r, isOrgServer as s, isPlatformServer as t, providers as u };
|
|
4456
|
+
//# sourceMappingURL=providers.server-C3fQ7ZdV.js.map
|