@shapio/cms 0.4.1 → 0.5.1
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/dist/admin/assets/{AssetTile-CoNClUYS.js → AssetTile-B8ftUG4s.js} +2 -2
- package/dist/admin/assets/{AssetTile-CoNClUYS.js.map → AssetTile-B8ftUG4s.js.map} +1 -1
- package/dist/admin/assets/{AssistError-BoejyShH.js → AssistError-BidlzaBY.js} +2 -2
- package/dist/admin/assets/{AssistError-BoejyShH.js.map → AssistError-BidlzaBY.js.map} +1 -1
- package/dist/admin/assets/{BlockMenuItems-B2Jugisa.js → BlockMenuItems-DfNBHqVM.js} +2 -2
- package/dist/admin/assets/{BlockMenuItems-B2Jugisa.js.map → BlockMenuItems-DfNBHqVM.js.map} +1 -1
- package/dist/admin/assets/{Body-D3GwLGaB.js → Body-C0oQSX6L.js} +2 -2
- package/dist/admin/assets/{Body-D3GwLGaB.js.map → Body-C0oQSX6L.js.map} +1 -1
- package/dist/admin/assets/{Builder-CmT5-8hI.js → Builder-Z_hcyzNW.js} +2 -2
- package/dist/admin/assets/{Builder-CmT5-8hI.js.map → Builder-Z_hcyzNW.js.map} +1 -1
- package/dist/admin/assets/{ConfirmDialog-HS8Gc2t3.js → ConfirmDialog-DVN-n-tE.js} +2 -2
- package/dist/admin/assets/{ConfirmDialog-HS8Gc2t3.js.map → ConfirmDialog-DVN-n-tE.js.map} +1 -1
- package/dist/admin/assets/{CopyButton-DS-bAA23.js → CopyButton--tHegXQr.js} +2 -2
- package/dist/admin/assets/{CopyButton-DS-bAA23.js.map → CopyButton--tHegXQr.js.map} +1 -1
- package/dist/admin/assets/{CursorPager-C6PyD3wE.js → CursorPager-Cb94mITn.js} +2 -2
- package/dist/admin/assets/{CursorPager-C6PyD3wE.js.map → CursorPager-Cb94mITn.js.map} +1 -1
- package/dist/admin/assets/{ExternalLink-KdO72pTj.js → ExternalLink-M1cb1n2n.js} +2 -2
- package/dist/admin/assets/{ExternalLink-KdO72pTj.js.map → ExternalLink-M1cb1n2n.js.map} +1 -1
- package/dist/admin/assets/{FocalPoint-C62fRhmM.js → FocalPoint-pTq17ngi.js} +2 -2
- package/dist/admin/assets/{FocalPoint-C62fRhmM.js.map → FocalPoint-pTq17ngi.js.map} +1 -1
- package/dist/admin/assets/{Form-CLEr5Csd.js → Form-4zah109J.js} +2 -2
- package/dist/admin/assets/{Form-CLEr5Csd.js.map → Form-4zah109J.js.map} +1 -1
- package/dist/admin/assets/FormCheckboxGroup-DAeuGKAU.js +2 -0
- package/dist/admin/assets/FormCheckboxGroup-DAeuGKAU.js.map +1 -0
- package/dist/admin/assets/{FormRadioGroup-BjE1Jhar.js → FormRadioGroup--xx08rw6.js} +2 -2
- package/dist/admin/assets/{FormRadioGroup-BjE1Jhar.js.map → FormRadioGroup--xx08rw6.js.map} +1 -1
- package/dist/admin/assets/{FormSelectField-g4zrUlhv.js → FormSelectField-DNOCQKqc.js} +2 -2
- package/dist/admin/assets/{FormSelectField-g4zrUlhv.js.map → FormSelectField-DNOCQKqc.js.map} +1 -1
- package/dist/admin/assets/{FormSwitchField-gciThNGV.js → FormSwitchField-BECl-pzH.js} +2 -2
- package/dist/admin/assets/{FormSwitchField-gciThNGV.js.map → FormSwitchField-BECl-pzH.js.map} +1 -1
- package/dist/admin/assets/{FormTextareaField-tsS-UXym.js → FormTextareaField-DmRuKDJ_.js} +2 -2
- package/dist/admin/assets/{FormTextareaField-tsS-UXym.js.map → FormTextareaField-DmRuKDJ_.js.map} +1 -1
- package/dist/admin/assets/{HintedLabel-CVe-PIyv.js → HintedLabel-DIEBvftx.js} +2 -2
- package/dist/admin/assets/{HintedLabel-CVe-PIyv.js.map → HintedLabel-DIEBvftx.js.map} +1 -1
- package/dist/admin/assets/{InfoHint-DtaHU1Cg.js → InfoHint-BdAadb9U.js} +2 -2
- package/dist/admin/assets/{InfoHint-DtaHU1Cg.js.map → InfoHint-BdAadb9U.js.map} +1 -1
- package/dist/admin/assets/{InsertMenuItem-DlzaD9zr.js → InsertMenuItem-oh0NJrhB.js} +2 -2
- package/dist/admin/assets/{InsertMenuItem-DlzaD9zr.js.map → InsertMenuItem-oh0NJrhB.js.map} +1 -1
- package/dist/admin/assets/{LinkTabs-stM3j4aH.js → LinkTabs-EBgHX4Uh.js} +2 -2
- package/dist/admin/assets/{LinkTabs-stM3j4aH.js.map → LinkTabs-EBgHX4Uh.js.map} +1 -1
- package/dist/admin/assets/{Match-ErAxnNAY.js → Match-CvpSNEfi.js} +2 -2
- package/dist/admin/assets/{Match-ErAxnNAY.js.map → Match-CvpSNEfi.js.map} +1 -1
- package/dist/admin/assets/{Panel-ClY9b33D.js → Panel-BV1BpNCn.js} +2 -2
- package/dist/admin/assets/{Panel-ClY9b33D.js.map → Panel-BV1BpNCn.js.map} +1 -1
- package/dist/admin/assets/{QueryView-C630REML.js → QueryView-Bbw1eiV2.js} +2 -2
- package/dist/admin/assets/{QueryView-C630REML.js.map → QueryView-Bbw1eiV2.js.map} +1 -1
- package/dist/admin/assets/{RadioTile-CD4mel7U.js → RadioTile-CSYW-8gL.js} +2 -2
- package/dist/admin/assets/{RadioTile-CD4mel7U.js.map → RadioTile-CSYW-8gL.js.map} +1 -1
- package/dist/admin/assets/{RichTextField-CxeVX6C-.js → RichTextField-BDb67rCi.js} +2 -2
- package/dist/admin/assets/{RichTextField-CxeVX6C-.js.map → RichTextField-BDb67rCi.js.map} +1 -1
- package/dist/admin/assets/{RowTitle-B-7RptyN.js → RowTitle-Cted2GP8.js} +2 -2
- package/dist/admin/assets/{RowTitle-B-7RptyN.js.map → RowTitle-Cted2GP8.js.map} +1 -1
- package/dist/admin/assets/SecretReveal-Ccaul96d.js +2 -0
- package/dist/admin/assets/SecretReveal-Ccaul96d.js.map +1 -0
- package/dist/admin/assets/{SharedGlyph-97RaO6Bh.js → SharedGlyph-BrLi3Hug.js} +2 -2
- package/dist/admin/assets/{SharedGlyph-97RaO6Bh.js.map → SharedGlyph-BrLi3Hug.js.map} +1 -1
- package/dist/admin/assets/{StepList--0Fiqsq-.js → StepList-BLvElvPV.js} +2 -2
- package/dist/admin/assets/{StepList--0Fiqsq-.js.map → StepList-BLvElvPV.js.map} +1 -1
- package/dist/admin/assets/Structure-CRNAuVY7.js +2 -0
- package/dist/admin/assets/{Structure-CoO3iah3.js.map → Structure-CRNAuVY7.js.map} +1 -1
- package/dist/admin/assets/{SubNavLink-YjjaDN40.js → SubNavLink-biGV5ozd.js} +2 -2
- package/dist/admin/assets/{SubNavLink-YjjaDN40.js.map → SubNavLink-biGV5ozd.js.map} +1 -1
- package/dist/admin/assets/{SuggestAltButton-Dlyxoypu.js → SuggestAltButton-CrFiRWGr.js} +2 -2
- package/dist/admin/assets/{SuggestAltButton-Dlyxoypu.js.map → SuggestAltButton-CrFiRWGr.js.map} +1 -1
- package/dist/admin/assets/{TextLink-DIqr5zkJ.js → TextLink-BPLbXw2n.js} +2 -2
- package/dist/admin/assets/{TextLink-DIqr5zkJ.js.map → TextLink-BPLbXw2n.js.map} +1 -1
- package/dist/admin/assets/{Thumbnail-BUYOr4XJ.js → Thumbnail-B_TWlgEz.js} +2 -2
- package/dist/admin/assets/{Thumbnail-BUYOr4XJ.js.map → Thumbnail-B_TWlgEz.js.map} +1 -1
- package/dist/admin/assets/{UnsavedChangesGuard-BfzHgUkF.js → UnsavedChangesGuard-CrzR2boF.js} +2 -2
- package/dist/admin/assets/{UnsavedChangesGuard-BfzHgUkF.js.map → UnsavedChangesGuard-CrzR2boF.js.map} +1 -1
- package/dist/admin/assets/{UserAvatar-DNOWhSWS.js → UserAvatar-W-S3st7E.js} +2 -2
- package/dist/admin/assets/{UserAvatar-DNOWhSWS.js.map → UserAvatar-W-S3st7E.js.map} +1 -1
- package/dist/admin/assets/{auth-YVfK3SxF.js → auth-cXFFyABE.js} +2 -2
- package/dist/admin/assets/{auth-YVfK3SxF.js.map → auth-cXFFyABE.js.map} +1 -1
- package/dist/admin/assets/{badge-lJ4Ih_Zn.js → badge-DrPwivGk.js} +2 -2
- package/dist/admin/assets/{badge-lJ4Ih_Zn.js.map → badge-DrPwivGk.js.map} +1 -1
- package/dist/admin/assets/{boxes-DRvd25_u.js → boxes-CKJ6P9VA.js} +2 -2
- package/dist/admin/assets/{boxes-DRvd25_u.js.map → boxes-CKJ6P9VA.js.map} +1 -1
- package/dist/admin/assets/{calendar-clock-VENf4s1R.js → calendar-clock-DPyjti63.js} +2 -2
- package/dist/admin/assets/{calendar-clock-VENf4s1R.js.map → calendar-clock-DPyjti63.js.map} +1 -1
- package/dist/admin/assets/{changeSets-JmSyu3tu.js → changeSets-DYGm5j1A.js} +2 -2
- package/dist/admin/assets/{changeSets-JmSyu3tu.js.map → changeSets-DYGm5j1A.js.map} +1 -1
- package/dist/admin/assets/chevron-left-DIp5sJuv.js +2 -0
- package/dist/admin/assets/{chevron-left-D2DnQyeV.js.map → chevron-left-DIp5sJuv.js.map} +1 -1
- package/dist/admin/assets/{circle-check-jmRQz1Jq.js → circle-check-VXPKg4cE.js} +2 -2
- package/dist/admin/assets/{circle-check-jmRQz1Jq.js.map → circle-check-VXPKg4cE.js.map} +1 -1
- package/dist/admin/assets/{clock-DrSTMGjp.js → clock-DNhupIko.js} +2 -2
- package/dist/admin/assets/{clock-DrSTMGjp.js.map → clock-DNhupIko.js.map} +1 -1
- package/dist/admin/assets/cn-CEPUgFbZ.js +2 -0
- package/dist/admin/assets/cn-CEPUgFbZ.js.map +1 -0
- package/dist/admin/assets/coerce-BxuBjZdf.js +2 -0
- package/dist/admin/assets/{coerce-B4MJTGg1.js.map → coerce-BxuBjZdf.js.map} +1 -1
- package/dist/admin/assets/{constants-ahk0YSYl.js → constants-B9cNrCXV.js} +2 -2
- package/dist/admin/assets/{constants-ahk0YSYl.js.map → constants-B9cNrCXV.js.map} +1 -1
- package/dist/admin/assets/{content-DcvNzKGe.js → content-DjyCdi4c.js} +3 -3
- package/dist/admin/assets/{content-DcvNzKGe.js.map → content-DjyCdi4c.js.map} +1 -1
- package/dist/admin/assets/{contentCounts-DzIvdeXR.js → contentCounts-CX15iYcR.js} +2 -2
- package/dist/admin/assets/{contentCounts-DzIvdeXR.js.map → contentCounts-CX15iYcR.js.map} +1 -1
- package/dist/admin/assets/{contentTypes-YAd-AzOF.js → contentTypes-FgyxLhlk.js} +2 -2
- package/dist/admin/assets/{contentTypes-YAd-AzOF.js.map → contentTypes-FgyxLhlk.js.map} +1 -1
- package/dist/admin/assets/{context-4Jl20mYc.js → context-FqJXjS5k.js} +2 -2
- package/dist/admin/assets/{context-4Jl20mYc.js.map → context-FqJXjS5k.js.map} +1 -1
- package/dist/admin/assets/{copy-t6kAFw-O.js → copy-CoHbtS-Y.js} +2 -2
- package/dist/admin/assets/{copy-t6kAFw-O.js.map → copy-CoHbtS-Y.js.map} +1 -1
- package/dist/admin/assets/{dateTimeLocal-IDUOKulz.js → dateTimeLocal-BxmDLO_H.js} +2 -2
- package/dist/admin/assets/{dateTimeLocal-IDUOKulz.js.map → dateTimeLocal-BxmDLO_H.js.map} +1 -1
- package/dist/admin/assets/{develop-DYwiTd6R.js → develop-gx8WeWv4.js} +2 -2
- package/dist/admin/assets/{develop-DYwiTd6R.js.map → develop-gx8WeWv4.js.map} +1 -1
- package/dist/admin/assets/{dropdown-menu-DiYSm-g3.js → dropdown-menu-BG1rctRQ.js} +2 -2
- package/dist/admin/assets/{dropdown-menu-DiYSm-g3.js.map → dropdown-menu-BG1rctRQ.js.map} +1 -1
- package/dist/admin/assets/{ellipsis-DEHUaCkn.js → ellipsis-DHfP1Ah2.js} +2 -2
- package/dist/admin/assets/{ellipsis-DEHUaCkn.js.map → ellipsis-DHfP1Ah2.js.map} +1 -1
- package/dist/admin/assets/{external-link-B8UFREQ_.js → external-link-itxcNtaX.js} +2 -2
- package/dist/admin/assets/{external-link-B8UFREQ_.js.map → external-link-itxcNtaX.js.map} +1 -1
- package/dist/admin/assets/{fileRoute-CVQpW8cj.js → fileRoute-BZ0pGGsh.js} +2 -2
- package/dist/admin/assets/{fileRoute-CVQpW8cj.js.map → fileRoute-BZ0pGGsh.js.map} +1 -1
- package/dist/admin/assets/{folder-plus-iDg1hhew.js → folder-plus-BgsfY_6i.js} +2 -2
- package/dist/admin/assets/{folder-plus-iDg1hhew.js.map → folder-plus-BgsfY_6i.js.map} +1 -1
- package/dist/admin/assets/{followPlural-BdqfEe48.js → followPlural-BqjqhHmz.js} +2 -2
- package/dist/admin/assets/{followPlural-BdqfEe48.js.map → followPlural-BqjqhHmz.js.map} +1 -1
- package/dist/admin/assets/{globe-TooNSp9V.js → globe-RjXj4P-A.js} +2 -2
- package/dist/admin/assets/{globe-TooNSp9V.js.map → globe-RjXj4P-A.js.map} +1 -1
- package/dist/admin/assets/{image-CXZXXH58.js → image-4itB4TLl.js} +2 -2
- package/dist/admin/assets/{image-CXZXXH58.js.map → image-4itB4TLl.js.map} +1 -1
- package/dist/admin/assets/{image-off-cxva5G-Y.js → image-off-CokIymyg.js} +2 -2
- package/dist/admin/assets/{image-off-cxva5G-Y.js.map → image-off-CokIymyg.js.map} +1 -1
- package/dist/admin/assets/{inbox-CnzNfqt9.js → inbox-8AAyyM0T.js} +2 -2
- package/dist/admin/assets/{inbox-CnzNfqt9.js.map → inbox-8AAyyM0T.js.map} +1 -1
- package/dist/admin/assets/{inbox-B8c9Kj_z.js → inbox-97pzmuus.js} +2 -2
- package/dist/admin/assets/{inbox-B8c9Kj_z.js.map → inbox-97pzmuus.js.map} +1 -1
- package/dist/admin/assets/{index-D9KL7arJ.js → index-cpiZvLck.js} +4 -4
- package/dist/admin/assets/{index-D9KL7arJ.js.map → index-cpiZvLck.js.map} +1 -1
- package/dist/admin/assets/{key-round-CFJ61iCd.js → key-round-Dc4_SOi-.js} +2 -2
- package/dist/admin/assets/{key-round-CFJ61iCd.js.map → key-round-Dc4_SOi-.js.map} +1 -1
- package/dist/admin/assets/{languages-eh7U4anI.js → languages-DR2K97A5.js} +2 -2
- package/dist/admin/assets/{languages-eh7U4anI.js.map → languages-DR2K97A5.js.map} +1 -1
- package/dist/admin/assets/{layers-mGfmMsKE.js → layers-ZPmcbieN.js} +2 -2
- package/dist/admin/assets/{layers-mGfmMsKE.js.map → layers-ZPmcbieN.js.map} +1 -1
- package/dist/admin/assets/{link-2-off-DbZzRN_w.js → link-2-off-D_5_hkT7.js} +2 -2
- package/dist/admin/assets/{link-2-off-DbZzRN_w.js.map → link-2-off-D_5_hkT7.js.map} +1 -1
- package/dist/admin/assets/{link-b2pFaN-A.js → link-BnDFFyvi.js} +2 -2
- package/dist/admin/assets/{link-b2pFaN-A.js.map → link-BnDFFyvi.js.map} +1 -1
- package/dist/admin/assets/{locales-oBD0q1pN.js → locales-D-21t-l-.js} +2 -2
- package/dist/admin/assets/{locales-oBD0q1pN.js.map → locales-D-21t-l-.js.map} +1 -1
- package/dist/admin/assets/{media-5ks_0Rre.js → media-D7aimjyy.js} +2 -2
- package/dist/admin/assets/{media-5ks_0Rre.js.map → media-D7aimjyy.js.map} +1 -1
- package/dist/admin/assets/{media-Cs9LCQcz.js → media-Db0cHYNY.js} +2 -2
- package/dist/admin/assets/{media-Cs9LCQcz.js.map → media-Db0cHYNY.js.map} +1 -1
- package/dist/admin/assets/network-l_oLT5fa.js +2 -0
- package/dist/admin/assets/network-l_oLT5fa.js.map +1 -0
- package/dist/admin/assets/{pencil-C7eD5mlZ.js → pencil-Cn8c_gtv.js} +2 -2
- package/dist/admin/assets/{pencil-C7eD5mlZ.js.map → pencil-Cn8c_gtv.js.map} +1 -1
- package/dist/admin/assets/{plus-BDZHQBlU.js → plus-yxvf4aAT.js} +2 -2
- package/dist/admin/assets/{plus-BDZHQBlU.js.map → plus-yxvf4aAT.js.map} +1 -1
- package/dist/admin/assets/{preload-helper-CJNJ4S3k.js → preload-helper-pwSesV5P.js} +2 -2
- package/dist/admin/assets/{preload-helper-CJNJ4S3k.js.map → preload-helper-pwSesV5P.js.map} +1 -1
- package/dist/admin/assets/{publishing-C4YQ4XKt.js → publishing-PQLCOnvu.js} +2 -2
- package/dist/admin/assets/{publishing-C4YQ4XKt.js.map → publishing-PQLCOnvu.js.map} +1 -1
- package/dist/admin/assets/{radio-group-zOsGAYRP.js → radio-group-Fc2ENIMO.js} +2 -2
- package/dist/admin/assets/{radio-group-zOsGAYRP.js.map → radio-group-Fc2ENIMO.js.map} +1 -1
- package/dist/admin/assets/{rotate-ccw-DCWwb4Ga.js → rotate-ccw-BHa0BHug.js} +2 -2
- package/dist/admin/assets/{rotate-ccw-DCWwb4Ga.js.map → rotate-ccw-BHa0BHug.js.map} +1 -1
- package/dist/admin/assets/{schedules-DXrBSypK.js → schedules-B8y_GnqX.js} +2 -2
- package/dist/admin/assets/{schedules-DXrBSypK.js.map → schedules-B8y_GnqX.js.map} +1 -1
- package/dist/admin/assets/{schema-B7L9OJrX.js → schema-BJHcYshf.js} +2 -2
- package/dist/admin/assets/{schema-B7L9OJrX.js.map → schema-BJHcYshf.js.map} +1 -1
- package/dist/admin/assets/{scroll-text-BLThe4Ki.js → scroll-text-BHLmZpBB.js} +2 -2
- package/dist/admin/assets/{scroll-text-BLThe4Ki.js.map → scroll-text-BHLmZpBB.js.map} +1 -1
- package/dist/admin/assets/{search-CcJQqtRw.js → search-D3GGSaSL.js} +2 -2
- package/dist/admin/assets/{search-CcJQqtRw.js.map → search-D3GGSaSL.js.map} +1 -1
- package/dist/admin/assets/{searchSchemas-BCRZ1jyL.js → searchSchemas-C4dnYI9a.js} +2 -2
- package/dist/admin/assets/{searchSchemas-BCRZ1jyL.js.map → searchSchemas-C4dnYI9a.js.map} +1 -1
- package/dist/admin/assets/{sections-v5Flaor9.js → sections-BWoTaw2B.js} +2 -2
- package/dist/admin/assets/{sections-v5Flaor9.js.map → sections-BWoTaw2B.js.map} +1 -1
- package/dist/admin/assets/{sections-TIN_Nl1b.js → sections-eeTU636D.js} +2 -2
- package/dist/admin/assets/{sections-TIN_Nl1b.js.map → sections-eeTU636D.js.map} +1 -1
- package/dist/admin/assets/{select-CpHp3YOH.js → select-BMi4G1Ib.js} +2 -2
- package/dist/admin/assets/{select-CpHp3YOH.js.map → select-BMi4G1Ib.js.map} +1 -1
- package/dist/admin/assets/{send-CbP0Rc9_.js → send-Bqhmg9HP.js} +2 -2
- package/dist/admin/assets/{send-CbP0Rc9_.js.map → send-Bqhmg9HP.js.map} +1 -1
- package/dist/admin/assets/{seo-1n9s7YuX.js → seo-BL9C7Yt3.js} +2 -2
- package/dist/admin/assets/{seo-1n9s7YuX.js.map → seo-BL9C7Yt3.js.map} +1 -1
- package/dist/admin/assets/settings-D-nW3wQb.js +2 -0
- package/dist/admin/assets/settings-D-nW3wQb.js.map +1 -0
- package/dist/admin/assets/skeleton-BPLAzyDo.js +42 -0
- package/dist/admin/assets/skeleton-BPLAzyDo.js.map +1 -0
- package/dist/admin/assets/{snapshots-DkyyrWX3.js → snapshots-COSUeZBP.js} +2 -2
- package/dist/admin/assets/{snapshots-DkyyrWX3.js.map → snapshots-COSUeZBP.js.map} +1 -1
- package/dist/admin/assets/{sparkles-D88gf3vX.js → sparkles-CykwtKTd.js} +2 -2
- package/dist/admin/assets/{sparkles-D88gf3vX.js.map → sparkles-CykwtKTd.js.map} +1 -1
- package/dist/admin/assets/{stableId-DlNKh3HM.js → stableId-DfJkQUV3.js} +2 -2
- package/dist/admin/assets/{stableId-DlNKh3HM.js.map → stableId-DfJkQUV3.js.map} +1 -1
- package/dist/admin/assets/{store-jUWyOXDG.js → store-GOGeP63I.js} +2 -2
- package/dist/admin/assets/{store-jUWyOXDG.js.map → store-GOGeP63I.js.map} +1 -1
- package/dist/admin/assets/{switch-CijNGYkG.js → switch-XRU_EOoM.js} +2 -2
- package/dist/admin/assets/{switch-CijNGYkG.js.map → switch-XRU_EOoM.js.map} +1 -1
- package/dist/admin/assets/{table-DQ5ILlSh.js → table-CLVW6uJB.js} +2 -2
- package/dist/admin/assets/{table-DQ5ILlSh.js.map → table-CLVW6uJB.js.map} +1 -1
- package/dist/admin/assets/{tabs-CSqGW9Du.js → tabs-DB42Xljn.js} +3 -3
- package/dist/admin/assets/{tabs-CSqGW9Du.js.map → tabs-DB42Xljn.js.map} +1 -1
- package/dist/admin/assets/{textarea-FwSvwmyT.js → textarea-DcsLGCkq.js} +2 -2
- package/dist/admin/assets/{textarea-FwSvwmyT.js.map → textarea-DcsLGCkq.js.map} +1 -1
- package/dist/admin/assets/{theme-CHHVK8Ps.js → theme-CnSar6tt.js} +2 -2
- package/dist/admin/assets/{theme-CHHVK8Ps.js.map → theme-CnSar6tt.js.map} +1 -1
- package/dist/admin/assets/{titles-CQYc9nDQ.js → titles-DluirfBO.js} +2 -2
- package/dist/admin/assets/{titles-CQYc9nDQ.js.map → titles-DluirfBO.js.map} +1 -1
- package/dist/admin/assets/{tokens-C-20gLAT.js → tokens-CQooB8Sf.js} +2 -2
- package/dist/admin/assets/{tokens-C-20gLAT.js.map → tokens-CQooB8Sf.js.map} +1 -1
- package/dist/admin/assets/{tooltip-BXqFQv9N.js → tooltip-xfZn0Fla.js} +2 -2
- package/dist/admin/assets/{tooltip-BXqFQv9N.js.map → tooltip-xfZn0Fla.js.map} +1 -1
- package/dist/admin/assets/{undo-2-CLy2zklL.js → undo-2-B_QjuGmR.js} +2 -2
- package/dist/admin/assets/{undo-2-CLy2zklL.js.map → undo-2-B_QjuGmR.js.map} +1 -1
- package/dist/admin/assets/{useAuditLabels-BJF7M5RY.js → useAuditLabels-BZPAj3wG.js} +2 -2
- package/dist/admin/assets/{useAuditLabels-BJF7M5RY.js.map → useAuditLabels-BZPAj3wG.js.map} +1 -1
- package/dist/admin/assets/{useConfirmTarget-BUvlrGSW.js → useConfirmTarget-B7iLg66j.js} +2 -2
- package/dist/admin/assets/{useConfirmTarget-BUvlrGSW.js.map → useConfirmTarget-B7iLg66j.js.map} +1 -1
- package/dist/admin/assets/{useContentSchema-Cvtes34f.js → useContentSchema-BTyBYURX.js} +2 -2
- package/dist/admin/assets/{useContentSchema-Cvtes34f.js.map → useContentSchema-BTyBYURX.js.map} +1 -1
- package/dist/admin/assets/{useCursorPager-CzB6NDOB.js → useCursorPager-CTGOzl69.js} +2 -2
- package/dist/admin/assets/{useCursorPager-CzB6NDOB.js.map → useCursorPager-CTGOzl69.js.map} +1 -1
- package/dist/admin/assets/{useDiscardGuard-BAEGkGK9.js → useDiscardGuard-CSkPw8iq.js} +2 -2
- package/dist/admin/assets/{useDiscardGuard-BAEGkGK9.js.map → useDiscardGuard-CSkPw8iq.js.map} +1 -1
- package/dist/admin/assets/{useMediaPickerDialog-CiN_pias.js → useMediaPickerDialog-W0jhN-hi.js} +2 -2
- package/dist/admin/assets/{useMediaPickerDialog-CiN_pias.js.map → useMediaPickerDialog-W0jhN-hi.js.map} +1 -1
- package/dist/admin/assets/{useMediaUploader-CapEHBIt.js → useMediaUploader-BC5zCkQt.js} +2 -2
- package/dist/admin/assets/{useMediaUploader-CapEHBIt.js.map → useMediaUploader-BC5zCkQt.js.map} +1 -1
- package/dist/admin/assets/{usePaletteItems-wpn3ms6o.js → usePaletteItems-CqcKndUT.js} +2 -2
- package/dist/admin/assets/{usePaletteItems-wpn3ms6o.js.map → usePaletteItems-CqcKndUT.js.map} +1 -1
- package/dist/admin/assets/{usePlaces-6iTN15Xy.js → usePlaces-o4Os5Nbv.js} +2 -2
- package/dist/admin/assets/{usePlaces-6iTN15Xy.js.map → usePlaces-o4Os5Nbv.js.map} +1 -1
- package/dist/admin/assets/{useQueries-CYfcxI0Q.js → useQueries-BTnSD0k5.js} +2 -2
- package/dist/admin/assets/{useQueries-CYfcxI0Q.js.map → useQueries-BTnSD0k5.js.map} +1 -1
- package/dist/admin/assets/{useResolvedScheme-i1EwGHuF.js → useResolvedScheme-SoI-2Faf.js} +2 -2
- package/dist/admin/assets/{useResolvedScheme-i1EwGHuF.js.map → useResolvedScheme-SoI-2Faf.js.map} +1 -1
- package/dist/admin/assets/{useSchemaScopeAccess-WQKmj8_3.js → useSchemaScopeAccess-B4-31q7m.js} +2 -2
- package/dist/admin/assets/{useSchemaScopeAccess-WQKmj8_3.js.map → useSchemaScopeAccess-B4-31q7m.js.map} +1 -1
- package/dist/admin/assets/{users-BgTNDPXx.js → users-BVuapb3u.js} +2 -2
- package/dist/admin/assets/{users-BgTNDPXx.js.map → users-BVuapb3u.js.map} +1 -1
- package/dist/admin/assets/{users-B01EH47o.js → users-Oj5V5za_.js} +2 -2
- package/dist/admin/assets/{users-B01EH47o.js.map → users-Oj5V5za_.js.map} +1 -1
- package/dist/admin/assets/{zod-79xzBIrW.js → zod-cTJwCa4E.js} +2 -2
- package/dist/admin/assets/{zod-79xzBIrW.js.map → zod-cTJwCa4E.js.map} +1 -1
- package/dist/admin/index.html +55 -55
- package/dist/cli.js +128 -21
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{public-CTK82y3t.d.ts → public-C027OJX6.d.ts} +13 -2
- package/dist/s3Adapter-BipoQiwQ.js +125 -0
- package/dist/s3Adapter-BipoQiwQ.js.map +1 -0
- package/dist/{worker-BDYDbaea.js → worker-BU-V4qbH.js} +555 -411
- package/dist/worker-BU-V4qbH.js.map +1 -0
- package/package.json +8 -5
- package/dist/admin/assets/FormCheckboxGroup-BrXY6qBg.js +0 -2
- package/dist/admin/assets/FormCheckboxGroup-BrXY6qBg.js.map +0 -1
- package/dist/admin/assets/SecretReveal-Dhw_dYxe.js +0 -2
- package/dist/admin/assets/SecretReveal-Dhw_dYxe.js.map +0 -1
- package/dist/admin/assets/Structure-CoO3iah3.js +0 -2
- package/dist/admin/assets/chevron-left-D2DnQyeV.js +0 -2
- package/dist/admin/assets/cn-CL1YHRrS.js +0 -2
- package/dist/admin/assets/cn-CL1YHRrS.js.map +0 -1
- package/dist/admin/assets/coerce-B4MJTGg1.js +0 -2
- package/dist/admin/assets/network-DS8MW3AZ.js +0 -2
- package/dist/admin/assets/network-DS8MW3AZ.js.map +0 -1
- package/dist/admin/assets/settings-CuEO5l07.js +0 -2
- package/dist/admin/assets/settings-CuEO5l07.js.map +0 -1
- package/dist/admin/assets/skeleton-DElTumgk.js +0 -42
- package/dist/admin/assets/skeleton-DElTumgk.js.map +0 -1
- package/dist/worker-BDYDbaea.js.map +0 -1
|
@@ -7,7 +7,6 @@ import Fastify, { LogController } from "fastify";
|
|
|
7
7
|
import { AliasNode, AndNode, BinaryOperationNode, ColumnNode, ColumnUpdateNode, IdentifierNode, InsertQueryNode, Kysely, ListNode, MysqlAdapter, MysqlIntrospector, MysqlQueryCompiler, OnDuplicateKeyNode, OperationNodeTransformer, OperatorNode, OrNode, OrderByItemNode, ParensNode, PostgresDialect, PrimitiveValueListNode, RawNode, ReferenceNode, SelectAllNode, SelectModifierNode, SelectQueryNode, SelectionNode, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, TableNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode, WhereNode, sql } from "kysely";
|
|
8
8
|
import pg from "pg";
|
|
9
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
-
import mysql from "mysql2";
|
|
11
10
|
import { EventEmitter } from "node:events";
|
|
12
11
|
import { DatabaseSync } from "node:sqlite";
|
|
13
12
|
import { Type } from "typebox";
|
|
@@ -15,9 +14,6 @@ import { Value } from "typebox/value";
|
|
|
15
14
|
import { isDeepStrictEqual } from "node:util";
|
|
16
15
|
import { createJiti } from "jiti";
|
|
17
16
|
import { pino } from "pino";
|
|
18
|
-
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
19
|
-
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
|
|
20
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
21
17
|
import fp from "fastify-plugin";
|
|
22
18
|
import { hash, verify } from "@node-rs/argon2";
|
|
23
19
|
import cookie from "@fastify/cookie";
|
|
@@ -177,6 +173,20 @@ const sqliteLocationOfUrl = (url) => {
|
|
|
177
173
|
path
|
|
178
174
|
};
|
|
179
175
|
};
|
|
176
|
+
/** `host:port/database` (PostgreSQL, MySQL) or the file path (SQLite) from DATABASE_URL, never the credentials. */
|
|
177
|
+
const describeDatabaseTarget = (connectionString) => {
|
|
178
|
+
if (dialectOfUrl(connectionString) === "sqlite") {
|
|
179
|
+
const location = sqliteLocationOfUrl(connectionString);
|
|
180
|
+
return location.kind === "memory" ? "an in-memory database" : location.path;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const url = new URL(connectionString);
|
|
184
|
+
const defaultPort = dialectOfUrl(connectionString) === "mysql" ? "3306" : "5432";
|
|
185
|
+
return `${url.hostname}:${url.port || defaultPort}${url.pathname}`;
|
|
186
|
+
} catch {
|
|
187
|
+
return "the configured database";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
180
190
|
let current;
|
|
181
191
|
/** The dialect queries are built for. PostgreSQL until a database handle says otherwise. */
|
|
182
192
|
const currentDialect = () => current ?? "postgres";
|
|
@@ -1899,9 +1909,10 @@ const MYSQL_TABLES = {
|
|
|
1899
1909
|
"id",
|
|
1900
1910
|
"schema_version",
|
|
1901
1911
|
"permissions_version",
|
|
1902
|
-
"updated_at"
|
|
1912
|
+
"updated_at",
|
|
1913
|
+
"release"
|
|
1903
1914
|
],
|
|
1904
|
-
nullable: [],
|
|
1915
|
+
nullable: ["release"],
|
|
1905
1916
|
timestamps: ["updated_at"],
|
|
1906
1917
|
uniqueKeys: [{
|
|
1907
1918
|
name: "system_versions_pkey",
|
|
@@ -2684,6 +2695,13 @@ const typeCast = (field, next) => {
|
|
|
2684
2695
|
return next();
|
|
2685
2696
|
};
|
|
2686
2697
|
//#endregion
|
|
2698
|
+
//#region src/db/mysql/module.ts
|
|
2699
|
+
/**
|
|
2700
|
+
* mysql2, loaded on first use. A process on PostgreSQL never loads it, and a bundle of the read path (the
|
|
2701
|
+
* in-process delivery runtime) can leave it out as an optional dependency.
|
|
2702
|
+
*/
|
|
2703
|
+
const loadMysql = async () => (await import("mysql2")).default;
|
|
2704
|
+
//#endregion
|
|
2687
2705
|
//#region src/db/mysql/parameters.ts
|
|
2688
2706
|
/**
|
|
2689
2707
|
* Parameters the MySQL connection resolves itself just before a statement runs (ADR 0001, "MySQL").
|
|
@@ -2885,7 +2903,7 @@ var MysqlDriver = class {
|
|
|
2885
2903
|
this.databaseKey = mysqlDatabaseKey(options.url);
|
|
2886
2904
|
}
|
|
2887
2905
|
async init() {
|
|
2888
|
-
const pool =
|
|
2906
|
+
const pool = (await loadMysql()).createPool(mysqlPoolOptions(this.#options.url, this.#options.poolMax, this.#options.applicationName));
|
|
2889
2907
|
pool.on("connection", (connection) => {
|
|
2890
2908
|
connection.query(SESSION_SETUP, (error) => {
|
|
2891
2909
|
if (error) {
|
|
@@ -9435,6 +9453,10 @@ const bumpSchemaVersion = async (trx, now) => {
|
|
|
9435
9453
|
updated_at: now
|
|
9436
9454
|
})).returning("schema_version").executeTakeFirstOrThrow()).schema_version;
|
|
9437
9455
|
};
|
|
9456
|
+
/** Records the Shapio release this database is served by (in-process readers compare their own with it). */
|
|
9457
|
+
const setRelease = async (release, executor = db) => {
|
|
9458
|
+
await executor.updateTable("system_versions").set({ release }).execute();
|
|
9459
|
+
};
|
|
9438
9460
|
//#endregion
|
|
9439
9461
|
//#region src/schema/snapshot.ts
|
|
9440
9462
|
/**
|
|
@@ -10412,6 +10434,14 @@ const CONTENT_ACTIONS = [
|
|
|
10412
10434
|
"schemaManage"
|
|
10413
10435
|
];
|
|
10414
10436
|
/**
|
|
10437
|
+
* What a delivery role may grant (plan drafts-mode §1): `read`, and `readDrafts`, which lets a delivery token
|
|
10438
|
+
* read draft heads (`?publicationState=draft`, GraphQL `publicationState: DRAFT`) on top of `read`. A
|
|
10439
|
+
* `readDrafts` grant always covers every model (no model, condition or field list), so one check per request
|
|
10440
|
+
* covers relation targets too. Admin and app roles never hold it; admin principals read drafts without it.
|
|
10441
|
+
*/
|
|
10442
|
+
const DELIVERY_ACTIONS = ["read", "readDrafts"];
|
|
10443
|
+
const READ_DRAFTS_ACTION = "readDrafts";
|
|
10444
|
+
/**
|
|
10415
10445
|
* Content actions about the schema rather than one site's entries: network roles only for a shared
|
|
10416
10446
|
* definition; for a site's own definition, the roles that apply on that site (when the principal acts there).
|
|
10417
10447
|
*/
|
|
@@ -12131,14 +12161,25 @@ const groupBindings = (rows) => {
|
|
|
12131
12161
|
}
|
|
12132
12162
|
return bindings;
|
|
12133
12163
|
};
|
|
12134
|
-
const KNOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
12164
|
+
const KNOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
12165
|
+
...CONTENT_ACTIONS,
|
|
12166
|
+
...DELIVERY_ACTIONS,
|
|
12167
|
+
...GLOBAL_ACTIONS
|
|
12168
|
+
]);
|
|
12169
|
+
const KNOWN_CONDITIONS = /* @__PURE__ */ new Set(["ownedByPrincipal"]);
|
|
12170
|
+
/**
|
|
12171
|
+
* A stored grant as this build understands it. Rows written by a newer Shapio may name an action or a row
|
|
12172
|
+
* condition this build does not know; they grant nothing here (fail closed). An unknown condition must never
|
|
12173
|
+
* read as "no condition", which would widen the grant to every row.
|
|
12174
|
+
*/
|
|
12135
12175
|
const toGrant$1 = (row) => {
|
|
12136
12176
|
if (!KNOWN_ACTIONS.has(row.action)) return;
|
|
12177
|
+
if (row.condition !== null && !KNOWN_CONDITIONS.has(row.condition)) return;
|
|
12137
12178
|
return {
|
|
12138
12179
|
roleId: row.role_id,
|
|
12139
12180
|
action: row.action,
|
|
12140
12181
|
modelId: row.model_id,
|
|
12141
|
-
condition: row.condition
|
|
12182
|
+
condition: row.condition,
|
|
12142
12183
|
fieldIds: row.field_ids
|
|
12143
12184
|
};
|
|
12144
12185
|
};
|
|
@@ -12270,7 +12311,9 @@ const allowsGlobalAction = (grants, action) => grants.some((grant) => grant.acti
|
|
|
12270
12311
|
//#endregion
|
|
12271
12312
|
//#region src/permissions/evaluator.ts
|
|
12272
12313
|
const NETWORK_ACTION_SET = new Set(NETWORK_ACTIONS);
|
|
12273
|
-
|
|
12314
|
+
/** Delivery tokens read, and read drafts where their role grants `readDrafts`. */
|
|
12315
|
+
const DELIVERY_TOKEN_ACTIONS = new Set(DELIVERY_ACTIONS);
|
|
12316
|
+
/** App users and anonymous callers: never `readDrafts`, whatever a role says. */
|
|
12274
12317
|
const APP_ACTIONS = new Set(APP_CONTENT_ACTIONS);
|
|
12275
12318
|
/**
|
|
12276
12319
|
* Which roles a principal holds and how its masks are computed. App users hold the custom app roles assigned
|
|
@@ -12297,7 +12340,7 @@ const rolesOf = async (principal, grants, executor, versions) => {
|
|
|
12297
12340
|
roleIds: [principal.roleId],
|
|
12298
12341
|
networkRoleIds: [],
|
|
12299
12342
|
audience: "delivery",
|
|
12300
|
-
actions:
|
|
12343
|
+
actions: DELIVERY_TOKEN_ACTIONS
|
|
12301
12344
|
};
|
|
12302
12345
|
case "appUser": return {
|
|
12303
12346
|
roleIds: [...await grants.getSiteAppRoleIds(principal.siteId, "authenticated", executor, versions), ...principal.roleIds],
|
|
@@ -13400,6 +13443,22 @@ const SITE_QUERY_PARAMETER = "site";
|
|
|
13400
13443
|
* and the header that selects the site. CDNs that key on the URL only need `?site=` instead of the header.
|
|
13401
13444
|
*/
|
|
13402
13445
|
const DELIVERY_VARY = "Authorization, Cookie, Shapio-Site";
|
|
13446
|
+
/** `Cache-Control` of delivery responses carrying drafts (REST and GraphQL; plan drafts-mode): never stored. */
|
|
13447
|
+
const DRAFTS_CACHE_CONTROL = "private, no-store";
|
|
13448
|
+
//#endregion
|
|
13449
|
+
//#region src/services/contentContext.ts
|
|
13450
|
+
/**
|
|
13451
|
+
* The content service context for one call, the same however the call arrived: the site's view of the pinned
|
|
13452
|
+
* schema (plan site-schema: shared definitions and the site's own), scoped to the site.
|
|
13453
|
+
*/
|
|
13454
|
+
const buildContentContext = ({ network, hooks, media, requestId, ip, ...rest }) => ({
|
|
13455
|
+
...rest,
|
|
13456
|
+
snapshot: network.forSite(rest.site.id),
|
|
13457
|
+
hooks: hooks ?? createContentHooks(),
|
|
13458
|
+
...media ? { media } : {},
|
|
13459
|
+
...requestId !== void 0 ? { requestId } : {},
|
|
13460
|
+
...ip !== void 0 ? { ip } : {}
|
|
13461
|
+
});
|
|
13403
13462
|
const NUMERIC_TYPES = /* @__PURE__ */ new Set([
|
|
13404
13463
|
"number",
|
|
13405
13464
|
"integer",
|
|
@@ -13580,6 +13639,8 @@ const RICH_TEXT_MODES = [
|
|
|
13580
13639
|
/** What a delivery read returns for rich text when the request does not say (`?richText=`). */
|
|
13581
13640
|
const DEFAULT_RICH_TEXT_MODE = "json";
|
|
13582
13641
|
/** `?seo=`: SEO fields as stored, or merged with the site's SEO defaults (plan seo-fields). */
|
|
13642
|
+
/** Which heads a delivery read serves (`?publicationState=`, GraphQL's `publicationState`). */
|
|
13643
|
+
const PUBLICATION_STATES = ["published", "draft"];
|
|
13583
13644
|
const SEO_MODES = ["raw", "resolved"];
|
|
13584
13645
|
const ENTRY_LIST_STATUSES = [
|
|
13585
13646
|
"draft",
|
|
@@ -14047,6 +14108,7 @@ const TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
14047
14108
|
"snapshot",
|
|
14048
14109
|
"richText",
|
|
14049
14110
|
"seo",
|
|
14111
|
+
"publicationState",
|
|
14050
14112
|
"q",
|
|
14051
14113
|
"status",
|
|
14052
14114
|
"author"
|
|
@@ -14226,6 +14288,18 @@ const parseSeo = (tree, context) => {
|
|
|
14226
14288
|
if (value === "resolved" && !context.model.fields.some((field) => !field.deprecated && isSeoField(field) && context.isReadable(field))) throw queryInvalid(`"${context.model.apiKey}" has no SEO field to resolve`);
|
|
14227
14289
|
return { seo: value };
|
|
14228
14290
|
};
|
|
14291
|
+
const PUBLICATION_STATE_SET = new Set(PUBLICATION_STATES);
|
|
14292
|
+
/**
|
|
14293
|
+
* `?publicationState=published|draft` on delivery reads; whether the caller may read drafts is the service's
|
|
14294
|
+
* check.
|
|
14295
|
+
*/
|
|
14296
|
+
const parsePublicationState = (tree, context) => {
|
|
14297
|
+
const value = single(tree.publicationState, "publicationState");
|
|
14298
|
+
if (value === void 0) return {};
|
|
14299
|
+
if (!context.allowPublicationState) throw queryInvalid("publicationState is only available on the delivery API");
|
|
14300
|
+
if (!PUBLICATION_STATE_SET.has(value)) throw queryInvalid(`"publicationState" must be one of ${PUBLICATION_STATES.join(", ")}`);
|
|
14301
|
+
return { publicationState: value };
|
|
14302
|
+
};
|
|
14229
14303
|
const parseContentQuery = (tree, context) => {
|
|
14230
14304
|
for (const key of Object.keys(tree)) if (!TOP_LEVEL_KEYS.has(key)) throw queryInvalid(`Unknown query parameter "${key}"`);
|
|
14231
14305
|
const filters = tree.filters;
|
|
@@ -14249,6 +14323,7 @@ const parseContentQuery = (tree, context) => {
|
|
|
14249
14323
|
snapshot,
|
|
14250
14324
|
...parseRichText(tree, context),
|
|
14251
14325
|
...parseSeo(tree, context),
|
|
14326
|
+
...parsePublicationState(tree, context),
|
|
14252
14327
|
...parseAdminFilters(tree, context)
|
|
14253
14328
|
};
|
|
14254
14329
|
};
|
|
@@ -14868,6 +14943,7 @@ const parseQueryFor = (context, model, policy, rawQuery, options) => parseConten
|
|
|
14868
14943
|
allowAdminFilters: options.allowAdminFilters ?? false,
|
|
14869
14944
|
allowRichText: options.allowRichText ?? false,
|
|
14870
14945
|
allowSeo: options.allowSeo ?? false,
|
|
14946
|
+
allowPublicationState: options.allowPublicationState ?? false,
|
|
14871
14947
|
resolveModel: (modelId) => resolveModelById(context.snapshot, modelId)?.definition
|
|
14872
14948
|
});
|
|
14873
14949
|
/** The admin content list: drafts, filtered/sorted/paginated by the same compiler as delivery. */
|
|
@@ -14967,13 +15043,12 @@ const SYSTEM_PRINCIPAL = {
|
|
|
14967
15043
|
kind: "system",
|
|
14968
15044
|
component: EXTENSION_SYSTEM_COMPONENT
|
|
14969
15045
|
};
|
|
14970
|
-
const contentContext = async (environment, site, principal) => ({
|
|
15046
|
+
const contentContext = async (environment, site, principal) => buildContentContext({
|
|
14971
15047
|
db: environment.db,
|
|
14972
|
-
|
|
15048
|
+
network: await environment.registry.getSnapshot(),
|
|
14973
15049
|
permissions: environment.permissions,
|
|
14974
15050
|
actor: principal ?? SYSTEM_PRINCIPAL,
|
|
14975
|
-
site
|
|
14976
|
-
hooks: createContentHooks()
|
|
15051
|
+
site
|
|
14977
15052
|
});
|
|
14978
15053
|
const toReadEntry = (view) => ({
|
|
14979
15054
|
id: view.id,
|
|
@@ -15258,157 +15333,6 @@ const createLoggerOptions = (config) => ({
|
|
|
15258
15333
|
});
|
|
15259
15334
|
const createLogger = (config) => pino(createLoggerOptions(config));
|
|
15260
15335
|
//#endregion
|
|
15261
|
-
//#region src/media/contentDisposition.ts
|
|
15262
|
-
/**
|
|
15263
|
-
* A Content-Disposition value that names the file safely: an ASCII fallback plus the RFC 5987 UTF-8 form,
|
|
15264
|
-
* so quotes, semicolons or line breaks in a filename can never inject header parameters.
|
|
15265
|
-
*/
|
|
15266
|
-
const contentDisposition = (filename, type = "inline") => {
|
|
15267
|
-
return `${type}; filename="${filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
15268
|
-
};
|
|
15269
|
-
/** Types a browser only ever displays passively: opening one directly cannot run script. */
|
|
15270
|
-
const PASSIVE_INLINE_TYPE = /^(image\/(png|jpeg|gif|webp|avif|bmp|tiff|x-icon|vnd\.microsoft\.icon)|video\/.+|audio\/.+)$/;
|
|
15271
|
-
/**
|
|
15272
|
-
* Content-Disposition to store on an object in a bucket. Public objects can be served straight from the
|
|
15273
|
-
* bucket or a CDN, where Shapio's sandboxing headers are absent; anything that could run as a page there
|
|
15274
|
-
* (SVG, HTML, XML, PDF...) is stored as an attachment, so opening it downloads it. `<img>` tags still show
|
|
15275
|
-
* an SVG, since embedding ignores Content-Disposition.
|
|
15276
|
-
*/
|
|
15277
|
-
const bucketContentDisposition = (contentType) => PASSIVE_INLINE_TYPE.test(contentType.toLowerCase()) ? void 0 : "attachment";
|
|
15278
|
-
//#endregion
|
|
15279
|
-
//#region src/media/errors.ts
|
|
15280
|
-
/** Thrown by adapters when a key has no object, so callers can tell "missing" from "storage is down". */
|
|
15281
|
-
var ObjectNotFoundError = class extends Error {
|
|
15282
|
-
constructor(key, options) {
|
|
15283
|
-
super(`No stored object at ${key}`, options);
|
|
15284
|
-
this.name = "ObjectNotFoundError";
|
|
15285
|
-
}
|
|
15286
|
-
};
|
|
15287
|
-
var InvalidStorageKeyError = class extends Error {
|
|
15288
|
-
constructor(key) {
|
|
15289
|
-
super(`Not a valid media storage key: ${JSON.stringify(key)}`);
|
|
15290
|
-
this.name = "InvalidStorageKeyError";
|
|
15291
|
-
}
|
|
15292
|
-
};
|
|
15293
|
-
//#endregion
|
|
15294
|
-
//#region src/media/s3Adapter.ts
|
|
15295
|
-
/** R2 accepts any region name ("auto" by convention); AWS needs the bucket's real region. */
|
|
15296
|
-
const DEFAULT_REGION = "us-east-1";
|
|
15297
|
-
const isNotFound$2 = (error) => error instanceof S3ServiceException && (error.$metadata.httpStatusCode === 404 || error.name === "NotFound" || error.name === "NoSuchKey");
|
|
15298
|
-
const createS3Client = (config) => new S3Client({
|
|
15299
|
-
region: config.region ?? DEFAULT_REGION,
|
|
15300
|
-
...config.endpoint ? { endpoint: config.endpoint } : {},
|
|
15301
|
-
forcePathStyle: config.forcePathStyle,
|
|
15302
|
-
...config.accessKeyId && config.secretAccessKey ? { credentials: {
|
|
15303
|
-
accessKeyId: config.accessKeyId,
|
|
15304
|
-
secretAccessKey: config.secretAccessKey
|
|
15305
|
-
} } : {},
|
|
15306
|
-
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
15307
|
-
responseChecksumValidation: "WHEN_REQUIRED"
|
|
15308
|
-
});
|
|
15309
|
-
/**
|
|
15310
|
-
* Any S3-compatible service (AWS S3, Cloudflare R2, B2, Spaces, Wasabi, versitygw). Private objects are read
|
|
15311
|
-
* through presigned GET URLs; browsers upload straight to the bucket with a presigned POST whose policy
|
|
15312
|
-
* limits size and content type, so Shapio never streams upload bytes.
|
|
15313
|
-
*/
|
|
15314
|
-
const createS3Adapter = ({ config, publicBaseUrl, client }) => {
|
|
15315
|
-
const s3 = client ?? createS3Client(config);
|
|
15316
|
-
const Bucket = config.bucket;
|
|
15317
|
-
const checkKey = (key) => {
|
|
15318
|
-
if (!isValidStorageKey(key)) throw new InvalidStorageKeyError(key);
|
|
15319
|
-
return key;
|
|
15320
|
-
};
|
|
15321
|
-
const head = async (key) => {
|
|
15322
|
-
try {
|
|
15323
|
-
const result = await s3.send(new HeadObjectCommand({
|
|
15324
|
-
Bucket,
|
|
15325
|
-
Key: checkKey(key)
|
|
15326
|
-
}));
|
|
15327
|
-
return {
|
|
15328
|
-
size: result.ContentLength ?? 0,
|
|
15329
|
-
contentType: result.ContentType
|
|
15330
|
-
};
|
|
15331
|
-
} catch (error) {
|
|
15332
|
-
if (isNotFound$2(error)) return;
|
|
15333
|
-
throw error;
|
|
15334
|
-
}
|
|
15335
|
-
};
|
|
15336
|
-
return {
|
|
15337
|
-
driver: "s3",
|
|
15338
|
-
put: async (key, body, { contentType, contentLength }) => {
|
|
15339
|
-
await s3.send(new PutObjectCommand({
|
|
15340
|
-
Bucket,
|
|
15341
|
-
Key: checkKey(key),
|
|
15342
|
-
Body: body,
|
|
15343
|
-
ContentType: contentType,
|
|
15344
|
-
ContentLength: Buffer.isBuffer(body) ? body.length : contentLength,
|
|
15345
|
-
ContentDisposition: bucketContentDisposition(contentType)
|
|
15346
|
-
}));
|
|
15347
|
-
},
|
|
15348
|
-
getStream: async (key, range) => {
|
|
15349
|
-
try {
|
|
15350
|
-
const result = await s3.send(new GetObjectCommand({
|
|
15351
|
-
Bucket,
|
|
15352
|
-
Key: checkKey(key),
|
|
15353
|
-
...range ? { Range: `bytes=${range.start}-${range.end}` } : {}
|
|
15354
|
-
}));
|
|
15355
|
-
if (!result.Body) throw new ObjectNotFoundError(key);
|
|
15356
|
-
return result.Body;
|
|
15357
|
-
} catch (error) {
|
|
15358
|
-
throw isNotFound$2(error) ? new ObjectNotFoundError(key, { cause: error }) : error;
|
|
15359
|
-
}
|
|
15360
|
-
},
|
|
15361
|
-
head,
|
|
15362
|
-
exists: async (key) => await head(key) !== void 0,
|
|
15363
|
-
delete: async (key) => {
|
|
15364
|
-
await s3.send(new DeleteObjectCommand({
|
|
15365
|
-
Bucket,
|
|
15366
|
-
Key: checkKey(key)
|
|
15367
|
-
}));
|
|
15368
|
-
},
|
|
15369
|
-
signedGetUrl: async (key, { expiresInSeconds, filename, contentType }) => getSignedUrl(s3, new GetObjectCommand({
|
|
15370
|
-
Bucket,
|
|
15371
|
-
Key: checkKey(key),
|
|
15372
|
-
ResponseContentType: contentType,
|
|
15373
|
-
ResponseContentDisposition: contentDisposition(filename)
|
|
15374
|
-
}), { expiresIn: expiresInSeconds }),
|
|
15375
|
-
publicUrl: (key) => publicBaseUrl ? `${publicBaseUrl}/${encodeKeyPath(checkKey(key))}` : void 0,
|
|
15376
|
-
presignedUpload: async ({ key, contentType, maxSizeBytes, expiresInSeconds }) => {
|
|
15377
|
-
const disposition = bucketContentDisposition(contentType);
|
|
15378
|
-
const { url, fields } = await createPresignedPost(s3, {
|
|
15379
|
-
Bucket,
|
|
15380
|
-
Key: checkKey(key),
|
|
15381
|
-
Conditions: [
|
|
15382
|
-
[
|
|
15383
|
-
"content-length-range",
|
|
15384
|
-
1,
|
|
15385
|
-
maxSizeBytes
|
|
15386
|
-
],
|
|
15387
|
-
[
|
|
15388
|
-
"eq",
|
|
15389
|
-
"$Content-Type",
|
|
15390
|
-
contentType
|
|
15391
|
-
],
|
|
15392
|
-
...disposition ? [[
|
|
15393
|
-
"eq",
|
|
15394
|
-
"$Content-Disposition",
|
|
15395
|
-
disposition
|
|
15396
|
-
]] : []
|
|
15397
|
-
],
|
|
15398
|
-
Fields: {
|
|
15399
|
-
"Content-Type": contentType,
|
|
15400
|
-
...disposition ? { "Content-Disposition": disposition } : {}
|
|
15401
|
-
},
|
|
15402
|
-
Expires: expiresInSeconds
|
|
15403
|
-
});
|
|
15404
|
-
return {
|
|
15405
|
-
url,
|
|
15406
|
-
fields
|
|
15407
|
-
};
|
|
15408
|
-
}
|
|
15409
|
-
};
|
|
15410
|
-
};
|
|
15411
|
-
//#endregion
|
|
15412
15336
|
//#region src/media/origins.ts
|
|
15413
15337
|
const SAMPLE_KEY = "public/00000000-0000-0000-0000-000000000000/sample/origin-probe";
|
|
15414
15338
|
const originOf = (url) => {
|
|
@@ -15429,6 +15353,7 @@ const resolveMediaOrigins = async (storage) => {
|
|
|
15429
15353
|
const base = originOf(storage.publicBaseUrl);
|
|
15430
15354
|
if (base) origins.add(base);
|
|
15431
15355
|
if (storage.s3) {
|
|
15356
|
+
const { createS3Adapter } = await import("./s3Adapter-BipoQiwQ.js");
|
|
15432
15357
|
const adapter = createS3Adapter({
|
|
15433
15358
|
config: storage.s3,
|
|
15434
15359
|
publicBaseUrl: void 0
|
|
@@ -16086,7 +16011,42 @@ const memoizePermissions = (base, principal) => {
|
|
|
16086
16011
|
};
|
|
16087
16012
|
};
|
|
16088
16013
|
//#endregion
|
|
16014
|
+
//#region src/permissions/scoped.ts
|
|
16015
|
+
const isLazy = (versions) => "read" in versions;
|
|
16016
|
+
/**
|
|
16017
|
+
* The evaluator at the scope's versions. Inside a transaction before a lazy scope read its versions, the
|
|
16018
|
+
* unbound evaluator checks the versions itself through the transaction, so no pooled connection is asked
|
|
16019
|
+
* for while the transaction holds one.
|
|
16020
|
+
*/
|
|
16021
|
+
const boundEvaluator = async (base, versions, executor) => {
|
|
16022
|
+
if (!isLazy(versions)) return base.atVersions?.(versions) ?? base;
|
|
16023
|
+
if (executor && !versions.isRead()) return base;
|
|
16024
|
+
return base.atVersions?.(await versions.read()) ?? base;
|
|
16025
|
+
};
|
|
16026
|
+
/**
|
|
16027
|
+
* One scope's evaluator (an HTTP request, an in-process delivery call): checked against the scope's durable
|
|
16028
|
+
* versions (ADR 0005: a revoke applies from the next scope, not mid-scope) and memoized per (principal, action,
|
|
16029
|
+
* model) for the scope (`memo.ts`).
|
|
16030
|
+
*/
|
|
16031
|
+
const scopePermissions = (base, principal, versions) => memoizePermissions({
|
|
16032
|
+
evaluate: async (caller, policyRequest, executor) => (await boundEvaluator(base, versions, executor)).evaluate(caller, policyRequest, executor),
|
|
16033
|
+
canPerform: async (caller, action, executor) => (await boundEvaluator(base, versions, executor)).canPerform(caller, action, executor),
|
|
16034
|
+
canPerformOnSite: async (caller, action, siteId, executor) => (await boundEvaluator(base, versions, executor)).canPerformOnSite(caller, action, siteId, executor)
|
|
16035
|
+
}, principal);
|
|
16036
|
+
//#endregion
|
|
16037
|
+
//#region src/constants/delivery.ts
|
|
16038
|
+
/** `system_settings` key of the delivery descriptor the server writes at start (services/deliveryDescriptor.ts). */
|
|
16039
|
+
const DELIVERY_DESCRIPTOR_SETTING = "delivery_descriptor";
|
|
16040
|
+
//#endregion
|
|
16089
16041
|
//#region src/repositories/requestState.ts
|
|
16042
|
+
/** The site a lookup names, joined to the one `system_versions` row (at most one row matches). */
|
|
16043
|
+
const siteJoin = (join, lookup) => {
|
|
16044
|
+
switch (lookup.by) {
|
|
16045
|
+
case "id": return join.on("sites.id", "=", lookup.id);
|
|
16046
|
+
case "key": return join.on("sites.key", "=", lookup.key);
|
|
16047
|
+
case "primary": return join.on("sites.is_primary", "=", true);
|
|
16048
|
+
}
|
|
16049
|
+
};
|
|
16090
16050
|
/**
|
|
16091
16051
|
* Everything a request checks before its work, in one statement: the durable schema and permissions versions
|
|
16092
16052
|
* (`system_versions`, one row) and, when asked, the request's site. Read once per request on the pool, before
|
|
@@ -16101,13 +16061,7 @@ const read = async (lookup, trx = db) => {
|
|
|
16101
16061
|
site: void 0
|
|
16102
16062
|
};
|
|
16103
16063
|
}
|
|
16104
|
-
const row = await trx.selectFrom("system_versions").leftJoin("sites", (join) =>
|
|
16105
|
-
switch (lookup.by) {
|
|
16106
|
-
case "id": return join.on("sites.id", "=", lookup.id);
|
|
16107
|
-
case "key": return join.on("sites.key", "=", lookup.key);
|
|
16108
|
-
case "primary": return join.on("sites.is_primary", "=", true);
|
|
16109
|
-
}
|
|
16110
|
-
}).select([
|
|
16064
|
+
const row = await trx.selectFrom("system_versions").leftJoin("sites", (join) => siteJoin(join, lookup)).select([
|
|
16111
16065
|
"system_versions.schema_version",
|
|
16112
16066
|
"system_versions.permissions_version",
|
|
16113
16067
|
"sites.id as site_id",
|
|
@@ -16150,27 +16104,15 @@ const getRequestState = (request, lookup) => {
|
|
|
16150
16104
|
/** Reads the request's site with its state, or on its own when the state was already read without it. */
|
|
16151
16105
|
const readRequestSite = async (request, lookup) => request.requestState ? findSite(lookup) : (await getRequestState(request, lookup)).site;
|
|
16152
16106
|
/**
|
|
16153
|
-
* The server's evaluator at the request's versions. Inside a transaction before the state was read (no route
|
|
16154
|
-
* does that today), the unbound evaluator checks the versions itself through the transaction, so no pooled
|
|
16155
|
-
* connection is asked for while the transaction holds one.
|
|
16156
|
-
*/
|
|
16157
|
-
const boundPermissions = async (request, executor) => {
|
|
16158
|
-
const base = request.server.permissions;
|
|
16159
|
-
if (executor && !request.requestState) return base;
|
|
16160
|
-
const { versions } = await getRequestState(request);
|
|
16161
|
-
return base.atVersions?.(versions) ?? base;
|
|
16162
|
-
};
|
|
16163
|
-
/**
|
|
16164
16107
|
* The request's evaluator: checked against the request's versions, and memoized per (principal, action,
|
|
16165
16108
|
* model) for the request (`permissions/memo.ts`), for REST as for GraphQL. Create it after site resolution,
|
|
16166
16109
|
* which settles the principal.
|
|
16167
16110
|
*/
|
|
16168
16111
|
const getRequestPermissions = (request) => {
|
|
16169
|
-
request.requestPermissions ??=
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
}, request.principal);
|
|
16112
|
+
request.requestPermissions ??= scopePermissions(request.server.permissions, request.principal, {
|
|
16113
|
+
read: async () => (await getRequestState(request)).versions,
|
|
16114
|
+
isRead: () => request.requestState !== null
|
|
16115
|
+
});
|
|
16174
16116
|
return request.requestPermissions;
|
|
16175
16117
|
};
|
|
16176
16118
|
//#endregion
|
|
@@ -16413,12 +16355,14 @@ const revokeApiToken = async (context, permissions, id) => {
|
|
|
16413
16355
|
});
|
|
16414
16356
|
});
|
|
16415
16357
|
};
|
|
16358
|
+
/** Whether a token with this expiry has expired at `now`. */
|
|
16359
|
+
const isTokenExpired = (expiresAt, now) => expiresAt !== null && expiresAt.getTime() <= now.getTime();
|
|
16416
16360
|
/** The principal for a bearer API token, or undefined when unknown, revoked or expired. */
|
|
16417
|
-
const resolveApiToken = async (value, now = /* @__PURE__ */ new Date()) => {
|
|
16418
|
-
const row = await findLiveByHash$1(hashToken(value));
|
|
16419
|
-
if (!row || row.expires_at
|
|
16361
|
+
const resolveApiToken = async (value, now = /* @__PURE__ */ new Date(), { recordUse = true, executor = db } = {}) => {
|
|
16362
|
+
const row = await findLiveByHash$1(hashToken(value), executor);
|
|
16363
|
+
if (!row || isTokenExpired(row.expires_at, now)) return;
|
|
16420
16364
|
const staleBefore = /* @__PURE__ */ new Date(now.getTime() - LAST_SEEN_WRITE_INTERVAL_MS);
|
|
16421
|
-
if (row.last_used_at === null || row.last_used_at.getTime() < staleBefore.getTime()) await touchLastUsed$1(row.id, now, staleBefore);
|
|
16365
|
+
if (recordUse && (row.last_used_at === null || row.last_used_at.getTime() < staleBefore.getTime())) await touchLastUsed$1(row.id, now, staleBefore, executor);
|
|
16422
16366
|
return {
|
|
16423
16367
|
kind: "token",
|
|
16424
16368
|
tokenId: row.id,
|
|
@@ -17590,7 +17534,7 @@ const editorManifestPlugin = fp(async (app, { manifest }) => {
|
|
|
17590
17534
|
app.decorate("editorManifest", manifest);
|
|
17591
17535
|
}, { name: "shapio-editor-manifest" });
|
|
17592
17536
|
//#endregion
|
|
17593
|
-
//#region src/
|
|
17537
|
+
//#region src/helpers/errorResponse.ts
|
|
17594
17538
|
const STATUS_CODES = {
|
|
17595
17539
|
400: "BAD_REQUEST",
|
|
17596
17540
|
401: "UNAUTHORIZED",
|
|
@@ -17607,6 +17551,10 @@ const STATUS_CODES = {
|
|
|
17607
17551
|
};
|
|
17608
17552
|
/** Errors raised by @fastify/csrf-protection (plugins/csrf.ts). */
|
|
17609
17553
|
const CSRF_ERROR_CODES$1 = /* @__PURE__ */ new Set(["FST_CSRF_INVALID_TOKEN", "FST_CSRF_MISSING_SECRET"]);
|
|
17554
|
+
/**
|
|
17555
|
+
* The one error shape every endpoint returns, `{ error: { code, message, details? } }`, and its status.
|
|
17556
|
+
* Server errors (5xx) never carry their internal text; the caller logs the original error.
|
|
17557
|
+
*/
|
|
17610
17558
|
const toErrorResponse = (error) => {
|
|
17611
17559
|
if (error instanceof AppError) {
|
|
17612
17560
|
const body = { error: {
|
|
@@ -17619,7 +17567,7 @@ const toErrorResponse = (error) => {
|
|
|
17619
17567
|
body
|
|
17620
17568
|
};
|
|
17621
17569
|
}
|
|
17622
|
-
if (CSRF_ERROR_CODES$1.has(error.code)) return {
|
|
17570
|
+
if (error.code !== void 0 && CSRF_ERROR_CODES$1.has(error.code)) return {
|
|
17623
17571
|
statusCode: 403,
|
|
17624
17572
|
body: { error: {
|
|
17625
17573
|
code: "CSRF_INVALID",
|
|
@@ -17654,6 +17602,8 @@ const toErrorResponse = (error) => {
|
|
|
17654
17602
|
} }
|
|
17655
17603
|
};
|
|
17656
17604
|
};
|
|
17605
|
+
//#endregion
|
|
17606
|
+
//#region src/plugins/errorHandler.ts
|
|
17657
17607
|
/** The single error handler: consistent `{ error: { code, message, details? } }` bodies and logging. */
|
|
17658
17608
|
const errorHandlerPlugin = fp(async (app) => {
|
|
17659
17609
|
app.decorateRequest("errorCode", void 0);
|
|
@@ -18097,10 +18047,11 @@ const csrfForGet = (app) => (request, reply, done) => {
|
|
|
18097
18047
|
/**
|
|
18098
18048
|
* Responses vary by credentials. GET queries get REST's validators (a strong ETag over the body, 304 on a
|
|
18099
18049
|
* match) and its caching policy (public for anonymous callers, private otherwise); everything else is
|
|
18100
|
-
* never stored.
|
|
18050
|
+
* never stored. An operation that read drafts (the controller set `DRAFTS_CACHE_CONTROL`) keeps it.
|
|
18101
18051
|
*/
|
|
18102
18052
|
const cacheHeaders = async (request, reply, payload) => {
|
|
18103
18053
|
appendVary(reply, DELIVERY_VARY);
|
|
18054
|
+
if (reply.getHeader("cache-control") === "private, no-store") return payload;
|
|
18104
18055
|
if (request.method !== "GET" || reply.statusCode !== 200 || typeof payload !== "string") {
|
|
18105
18056
|
reply.header("cache-control", "no-store");
|
|
18106
18057
|
return payload;
|
|
@@ -18223,6 +18174,14 @@ const createMysqlNotificationListener = (options) => {
|
|
|
18223
18174
|
});
|
|
18224
18175
|
};
|
|
18225
18176
|
const connect = async () => {
|
|
18177
|
+
if (closed) return;
|
|
18178
|
+
let mysql;
|
|
18179
|
+
try {
|
|
18180
|
+
mysql = await loadMysql();
|
|
18181
|
+
} catch (error) {
|
|
18182
|
+
log.error({ err: error }, "mysql2 could not be loaded; notifications are off");
|
|
18183
|
+
return;
|
|
18184
|
+
}
|
|
18226
18185
|
if (closed) return;
|
|
18227
18186
|
const current = mysql.createConnection(mysqlPoolOptions(connectionString, 1, options.applicationName ?? "shapio-listen"));
|
|
18228
18187
|
connection = current;
|
|
@@ -18980,6 +18939,49 @@ const createLoaders = (siteId, content) => {
|
|
|
18980
18939
|
};
|
|
18981
18940
|
};
|
|
18982
18941
|
//#endregion
|
|
18942
|
+
//#region src/schema/codegen/graphql/requestContext.ts
|
|
18943
|
+
/**
|
|
18944
|
+
* The context every resolver of one operation receives, with its own DataLoaders. Carries no HTTP objects, so
|
|
18945
|
+
* an operation can run outside a request.
|
|
18946
|
+
*/
|
|
18947
|
+
const createGraphqlContext = ({ site, permissions, content }) => {
|
|
18948
|
+
let base;
|
|
18949
|
+
let drafts = false;
|
|
18950
|
+
const contentAt = async (snapshot) => {
|
|
18951
|
+
base ??= content(snapshot);
|
|
18952
|
+
return {
|
|
18953
|
+
...await base,
|
|
18954
|
+
snapshot,
|
|
18955
|
+
permissions
|
|
18956
|
+
};
|
|
18957
|
+
};
|
|
18958
|
+
return {
|
|
18959
|
+
permissions,
|
|
18960
|
+
markDrafts: () => {
|
|
18961
|
+
drafts = true;
|
|
18962
|
+
},
|
|
18963
|
+
readDrafts: () => drafts,
|
|
18964
|
+
site,
|
|
18965
|
+
loaders: createLoaders(() => site.id, contentAt),
|
|
18966
|
+
content: contentAt
|
|
18967
|
+
};
|
|
18968
|
+
};
|
|
18969
|
+
//#endregion
|
|
18970
|
+
//#region src/helpers/siteQuery.ts
|
|
18971
|
+
const isSiteSegment = (segment) => {
|
|
18972
|
+
const key = segment.split("=", 1)[0] ?? "";
|
|
18973
|
+
try {
|
|
18974
|
+
return decodeURIComponent(key.replace(/\+/g, " ")) === SITE_QUERY_PARAMETER;
|
|
18975
|
+
} catch {
|
|
18976
|
+
return false;
|
|
18977
|
+
}
|
|
18978
|
+
};
|
|
18979
|
+
/**
|
|
18980
|
+
* A raw querystring (without `?`) without its `site` parameter: site resolution consumes it, and the content
|
|
18981
|
+
* compiler refuses unknown keys. The rest is left exactly as sent (bracket syntax is the compiler's to parse).
|
|
18982
|
+
*/
|
|
18983
|
+
const withoutSiteParameter = (rawQuery) => rawQuery.split("&").filter((segment) => !isSiteSegment(segment)).join("&");
|
|
18984
|
+
//#endregion
|
|
18983
18985
|
//#region src/controllers/contentContext.ts
|
|
18984
18986
|
/**
|
|
18985
18987
|
* The service context for a content request: the pinned snapshot, the principal and dependencies.
|
|
@@ -18987,9 +18989,9 @@ const createLoaders = (siteId, content) => {
|
|
|
18987
18989
|
* memoized per (principal, action, model), so the read, relation visibility, populate and usage recording
|
|
18988
18990
|
* evaluate each model's policy once.
|
|
18989
18991
|
*/
|
|
18990
|
-
const contentContextFor = async (request) => ({
|
|
18992
|
+
const contentContextFor = async (request) => buildContentContext({
|
|
18991
18993
|
db: request.server.db,
|
|
18992
|
-
|
|
18994
|
+
network: await getRequestNetworkSchema(request),
|
|
18993
18995
|
permissions: getRequestPermissions(request),
|
|
18994
18996
|
actor: request.principal,
|
|
18995
18997
|
site: getRequestSite(request),
|
|
@@ -19001,14 +19003,6 @@ const contentContextFor = async (request) => ({
|
|
|
19001
19003
|
requestId: request.id,
|
|
19002
19004
|
ip: request.ip
|
|
19003
19005
|
});
|
|
19004
|
-
const isSiteSegment = (segment) => {
|
|
19005
|
-
const key = segment.split("=", 1)[0] ?? "";
|
|
19006
|
-
try {
|
|
19007
|
-
return decodeURIComponent(key.replace(/\+/g, " ")) === SITE_QUERY_PARAMETER;
|
|
19008
|
-
} catch {
|
|
19009
|
-
return false;
|
|
19010
|
-
}
|
|
19011
|
-
};
|
|
19012
19006
|
/**
|
|
19013
19007
|
* The raw querystring (bracket syntax is parsed by the content compiler, not Fastify's flat parser), without
|
|
19014
19008
|
* `?site=`: site resolution (plugins/siteResolution.ts) consumed it, and the compiler refuses unknown keys.
|
|
@@ -19016,34 +19010,16 @@ const isSiteSegment = (segment) => {
|
|
|
19016
19010
|
const rawQueryOf = (request) => {
|
|
19017
19011
|
const index = request.url.indexOf("?");
|
|
19018
19012
|
if (index === -1) return "";
|
|
19019
|
-
return request.url.slice(index + 1)
|
|
19013
|
+
return withoutSiteParameter(request.url.slice(index + 1));
|
|
19020
19014
|
};
|
|
19021
19015
|
//#endregion
|
|
19022
19016
|
//#region src/controllers/graphqlContext.ts
|
|
19023
|
-
/** Admin users and admin-scope API tokens: may read drafts. */
|
|
19024
|
-
const isAdminPrincipal = (principal) => principal.kind === "admin" || principal.kind === "token" && principal.scope === "admin";
|
|
19025
19017
|
/** The context every resolver of one `/api/graphql` request receives (built after site resolution). */
|
|
19026
|
-
const createGraphqlRequestContext = (request) => {
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
base ??= contentContextFor(request);
|
|
19032
|
-
return {
|
|
19033
|
-
...await base,
|
|
19034
|
-
snapshot,
|
|
19035
|
-
permissions
|
|
19036
|
-
};
|
|
19037
|
-
};
|
|
19038
|
-
return {
|
|
19039
|
-
request,
|
|
19040
|
-
permissions,
|
|
19041
|
-
isAdmin: isAdminPrincipal(request.principal),
|
|
19042
|
-
site,
|
|
19043
|
-
loaders: createLoaders(() => site.id, content),
|
|
19044
|
-
content
|
|
19045
|
-
};
|
|
19046
|
-
};
|
|
19018
|
+
const createGraphqlRequestContext = (request) => createGraphqlContext({
|
|
19019
|
+
site: getRequestSite(request),
|
|
19020
|
+
permissions: getRequestPermissions(request),
|
|
19021
|
+
content: () => contentContextFor(request)
|
|
19022
|
+
});
|
|
19047
19023
|
//#endregion
|
|
19048
19024
|
//#region src/controllers/graphql.ts
|
|
19049
19025
|
const introspectionDisabled = () => new AppError(403, "INTROSPECTION_DISABLED", "Schema introspection needs an admin session or an API token (or GRAPHQL_PUBLIC_INTROSPECTION=true)");
|
|
@@ -19077,8 +19053,10 @@ const runGraphql = async (request, reply, input) => {
|
|
|
19077
19053
|
}
|
|
19078
19054
|
if (prepared.selectsIntrospection && !mayIntrospect) throw introspectionDisabled();
|
|
19079
19055
|
const usage = collectUsage(request, schema, prepared.document, prepared.operation, input.variables ?? void 0);
|
|
19080
|
-
const
|
|
19056
|
+
const context = createGraphqlRequestContext(request);
|
|
19057
|
+
const result = await executeOperation(schema, prepared, input, context);
|
|
19081
19058
|
recordUsage$1(request, usage, result.data !== null && result.data !== void 0);
|
|
19059
|
+
if (context.readDrafts()) reply.header("cache-control", DRAFTS_CACHE_CONTROL);
|
|
19082
19060
|
return send$1(reply, formatGraphqlResult(result, { log: request.log }));
|
|
19083
19061
|
};
|
|
19084
19062
|
const postGraphql = async (request, reply) => runGraphql(request, reply, request.body);
|
|
@@ -19492,7 +19470,7 @@ const createFixedTypes = () => {
|
|
|
19492
19470
|
entry: createEntry$2(),
|
|
19493
19471
|
publicationState: new GraphQLEnumType({
|
|
19494
19472
|
name: "PublicationState",
|
|
19495
|
-
description: "DRAFT is for admin principals
|
|
19473
|
+
description: "DRAFT is for admin principals and delivery tokens granted Read drafts (development servers).",
|
|
19496
19474
|
values: {
|
|
19497
19475
|
PUBLISHED: { value: "published" },
|
|
19498
19476
|
DRAFT: { value: "draft" }
|
|
@@ -20473,6 +20451,26 @@ const restFieldReads = async ({ snapshot, model, policy, query, policyFor }) =>
|
|
|
20473
20451
|
return reads;
|
|
20474
20452
|
};
|
|
20475
20453
|
//#endregion
|
|
20454
|
+
//#region src/services/draftAccess.ts
|
|
20455
|
+
/**
|
|
20456
|
+
* Who may read draft heads through the delivery API (plan drafts-mode §1). Admin users, admin-scope tokens
|
|
20457
|
+
* and Shapio itself always could (GraphQL previews). A delivery token may when its role grants `readDrafts`,
|
|
20458
|
+
* which always covers every model, so this one check per request also covers relation targets. App users and
|
|
20459
|
+
* anonymous callers never may, whatever a role says (the evaluator denies them too). The caller has already
|
|
20460
|
+
* checked `read` on the model.
|
|
20461
|
+
*/
|
|
20462
|
+
const isAdminPrincipal = (principal) => principal.kind === "system" || principal.kind === "admin" || principal.kind === "token" && principal.scope === "admin";
|
|
20463
|
+
const draftsForbidden = (message) => new AppError(403, "DRAFTS_FORBIDDEN", message);
|
|
20464
|
+
const assertMayReadDrafts = async (context, modelId, modelKey) => {
|
|
20465
|
+
const { actor } = context;
|
|
20466
|
+
if (isAdminPrincipal(actor)) return;
|
|
20467
|
+
if (actor.kind !== "token") throw draftsForbidden("Drafts need an admin user or a delivery token granted Read drafts");
|
|
20468
|
+
if (!(await context.permissions.evaluate(actor, {
|
|
20469
|
+
action: "readDrafts",
|
|
20470
|
+
modelId
|
|
20471
|
+
})).allowed) throw draftsForbidden(`This token may not read drafts of "${modelKey}"; grant Read drafts on its delivery role`);
|
|
20472
|
+
};
|
|
20473
|
+
//#endregion
|
|
20476
20474
|
//#region src/services/contentDelivery.ts
|
|
20477
20475
|
const readableModel = async (context, modelKey) => {
|
|
20478
20476
|
const model = resolveModel(context.snapshot, modelKey);
|
|
@@ -20486,10 +20484,19 @@ const readableModel = async (context, modelKey) => {
|
|
|
20486
20484
|
policy
|
|
20487
20485
|
};
|
|
20488
20486
|
};
|
|
20489
|
-
/**
|
|
20490
|
-
const
|
|
20491
|
-
|
|
20492
|
-
|
|
20487
|
+
/** Whether a read asks for drafts: GraphQL's option, or `?publicationState=draft`. */
|
|
20488
|
+
const readsDrafts = (query, options) => options.drafts === true || query.publicationState === "draft";
|
|
20489
|
+
/**
|
|
20490
|
+
* A draft read must be allowed: `read` was checked already; `readDrafts` (or an admin principal) is checked
|
|
20491
|
+
* here.
|
|
20492
|
+
*/
|
|
20493
|
+
const authorizeDrafts$1 = async (context, model, modelKey, drafts) => {
|
|
20494
|
+
if (drafts) await assertMayReadDrafts(context, model.definition.id, modelKey);
|
|
20495
|
+
};
|
|
20496
|
+
/** The heads a read serves: drafts, a pinned snapshot, or the published heads. */
|
|
20497
|
+
const sourceOf = (query, drafts) => {
|
|
20498
|
+
if (query.snapshot !== void 0 && drafts) throw queryInvalid("snapshot reads published content; it cannot be combined with drafts");
|
|
20499
|
+
return drafts ? {
|
|
20493
20500
|
kind: "heads",
|
|
20494
20501
|
state: "draft"
|
|
20495
20502
|
} : query.snapshot !== void 0 ? {
|
|
@@ -20510,7 +20517,7 @@ const snapshotCheck = (query) => (current) => {
|
|
|
20510
20517
|
*/
|
|
20511
20518
|
const recordUsage = async (context, options, model, policy, query) => {
|
|
20512
20519
|
const principalKey = usagePrincipalKey(context.actor);
|
|
20513
|
-
if (!options.usage || options
|
|
20520
|
+
if (!options.usage || readsDrafts(query, options) || principalKey === null) return;
|
|
20514
20521
|
const reads = await restFieldReads({
|
|
20515
20522
|
snapshot: context.snapshot,
|
|
20516
20523
|
model,
|
|
@@ -20538,8 +20545,24 @@ const ITEM_PARAMETERS$1 = /* @__PURE__ */ new Set([
|
|
|
20538
20545
|
"locale",
|
|
20539
20546
|
"snapshot",
|
|
20540
20547
|
"richText",
|
|
20541
|
-
"seo"
|
|
20548
|
+
"seo",
|
|
20549
|
+
"publicationState"
|
|
20542
20550
|
]);
|
|
20551
|
+
const DELIVERY_PARSE = {
|
|
20552
|
+
allowSnapshot: true,
|
|
20553
|
+
allowRichText: true,
|
|
20554
|
+
allowSeo: true,
|
|
20555
|
+
allowPublicationState: true
|
|
20556
|
+
};
|
|
20557
|
+
/** Meta every delivery response carries; draft reads add `publicationState` (published ones are unchanged). */
|
|
20558
|
+
const metaOf$1 = (locale, snapshot, drafts) => drafts ? {
|
|
20559
|
+
locale,
|
|
20560
|
+
snapshot,
|
|
20561
|
+
publicationState: "draft"
|
|
20562
|
+
} : {
|
|
20563
|
+
locale,
|
|
20564
|
+
snapshot
|
|
20565
|
+
};
|
|
20543
20566
|
/** Reading one entry takes only these query parameters; anything else is refused, not ignored. */
|
|
20544
20567
|
const assertItemParameters = (rawQuery, allowed) => {
|
|
20545
20568
|
const unsupported = [...new URLSearchParams(rawQuery).keys()].find((key) => !allowed.has(key.split("[")[0] ?? key));
|
|
@@ -20548,16 +20571,15 @@ const assertItemParameters = (rawQuery, allowed) => {
|
|
|
20548
20571
|
/** `GET /api/content/:modelKey`: a page of a collection, or a singleton's one entry. */
|
|
20549
20572
|
const listDelivery$1 = async (context, modelKey, rawQuery, options = {}) => {
|
|
20550
20573
|
const { model, policy } = await readableModel(context, modelKey);
|
|
20551
|
-
const query = parseQueryFor(context, model, policy, rawQuery,
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
});
|
|
20574
|
+
const query = parseQueryFor(context, model, policy, rawQuery, DELIVERY_PARSE);
|
|
20575
|
+
const drafts = readsDrafts(query, options);
|
|
20576
|
+
const source = sourceOf(query, drafts);
|
|
20577
|
+
await authorizeDrafts$1(context, model, modelKey, drafts);
|
|
20556
20578
|
const locale = query.locale ?? context.snapshot.defaultLocale;
|
|
20557
20579
|
const singleton = model.definition.kind === "singleton";
|
|
20558
20580
|
const { entries, limit, total, seq } = await readEntryPage({
|
|
20559
20581
|
context,
|
|
20560
|
-
source
|
|
20582
|
+
source,
|
|
20561
20583
|
model,
|
|
20562
20584
|
policy,
|
|
20563
20585
|
query,
|
|
@@ -20573,34 +20595,32 @@ const listDelivery$1 = async (context, modelKey, rawQuery, options = {}) => {
|
|
|
20573
20595
|
if (!entry) throw entryNotFound(modelKey);
|
|
20574
20596
|
result = {
|
|
20575
20597
|
data: entry,
|
|
20576
|
-
meta:
|
|
20577
|
-
locale,
|
|
20578
|
-
snapshot
|
|
20579
|
-
}
|
|
20598
|
+
meta: metaOf$1(locale, snapshot, drafts)
|
|
20580
20599
|
};
|
|
20581
20600
|
} else result = {
|
|
20582
20601
|
data: entries,
|
|
20583
20602
|
meta: {
|
|
20584
|
-
locale,
|
|
20585
|
-
snapshot,
|
|
20603
|
+
...metaOf$1(locale, snapshot, drafts),
|
|
20586
20604
|
pagination: paginationMeta(query.page, limit, total)
|
|
20587
20605
|
}
|
|
20588
20606
|
};
|
|
20589
20607
|
await afterRead(options, () => recordUsage(context, options, model, policy, query));
|
|
20590
20608
|
return result;
|
|
20591
20609
|
};
|
|
20592
|
-
/**
|
|
20610
|
+
/**
|
|
20611
|
+
* `GET /api/content/:modelKey/:id`: one published entry (404 for unpublished, unknown or unreadable entries);
|
|
20612
|
+
* with drafts, the entry's draft.
|
|
20613
|
+
*/
|
|
20593
20614
|
const getDelivery$1 = async (context, modelKey, id, rawQuery, options = {}) => {
|
|
20594
20615
|
assertItemParameters(rawQuery, ITEM_PARAMETERS$1);
|
|
20595
20616
|
const { model, policy } = await readableModel(context, modelKey);
|
|
20596
|
-
const query = parseQueryFor(context, model, policy, rawQuery,
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
|
|
20600
|
-
});
|
|
20617
|
+
const query = parseQueryFor(context, model, policy, rawQuery, DELIVERY_PARSE);
|
|
20618
|
+
const drafts = readsDrafts(query, options);
|
|
20619
|
+
const source = sourceOf(query, drafts);
|
|
20620
|
+
await authorizeDrafts$1(context, model, modelKey, drafts);
|
|
20601
20621
|
const { entry, seq } = await readOneEntry({
|
|
20602
20622
|
context,
|
|
20603
|
-
source
|
|
20623
|
+
source,
|
|
20604
20624
|
model,
|
|
20605
20625
|
policy,
|
|
20606
20626
|
query,
|
|
@@ -20610,10 +20630,7 @@ const getDelivery$1 = async (context, modelKey, id, rawQuery, options = {}) => {
|
|
|
20610
20630
|
await afterRead(options, () => recordUsage(context, options, model, policy, query));
|
|
20611
20631
|
return {
|
|
20612
20632
|
data: entry,
|
|
20613
|
-
meta:
|
|
20614
|
-
locale: query.locale ?? context.snapshot.defaultLocale,
|
|
20615
|
-
snapshot: query.snapshot ?? seq
|
|
20616
|
-
}
|
|
20633
|
+
meta: metaOf$1(query.locale ?? context.snapshot.defaultLocale, query.snapshot ?? seq, drafts)
|
|
20617
20634
|
};
|
|
20618
20635
|
};
|
|
20619
20636
|
//#endregion
|
|
@@ -20758,18 +20775,21 @@ const readArgs = (build) => ({
|
|
|
20758
20775
|
publicationState: {
|
|
20759
20776
|
type: build.fixed.publicationState,
|
|
20760
20777
|
defaultValue: "published",
|
|
20761
|
-
description: "DRAFT needs an admin user
|
|
20778
|
+
description: "DRAFT needs an admin user, an admin API token or a delivery token granted Read drafts"
|
|
20762
20779
|
},
|
|
20763
20780
|
snapshot: {
|
|
20764
20781
|
type: GraphQLInt,
|
|
20765
20782
|
description: "Read published content as of this publication sequence"
|
|
20766
20783
|
}
|
|
20767
20784
|
});
|
|
20768
|
-
|
|
20769
|
-
|
|
20785
|
+
/**
|
|
20786
|
+
* The read scope of a root read. Drafts: the delivery service checks the caller may read them (admin
|
|
20787
|
+
* principals, delivery tokens granted Read drafts; plan drafts-mode) before reading any head, and the
|
|
20788
|
+
* response is marked never to be stored.
|
|
20789
|
+
*/
|
|
20770
20790
|
const scopeOf = (args, context) => {
|
|
20771
20791
|
const drafts = args.publicationState === "draft";
|
|
20772
|
-
if (drafts
|
|
20792
|
+
if (drafts) context.markDrafts();
|
|
20773
20793
|
return {
|
|
20774
20794
|
locale: args.locale ?? void 0,
|
|
20775
20795
|
fallback: args.fallback ?? true,
|
|
@@ -21548,6 +21568,21 @@ const graphqlPlugin = fp(async (app, { config, urls }) => {
|
|
|
21548
21568
|
]
|
|
21549
21569
|
});
|
|
21550
21570
|
//#endregion
|
|
21571
|
+
//#region src/media/errors.ts
|
|
21572
|
+
/** Thrown by adapters when a key has no object, so callers can tell "missing" from "storage is down". */
|
|
21573
|
+
var ObjectNotFoundError = class extends Error {
|
|
21574
|
+
constructor(key, options) {
|
|
21575
|
+
super(`No stored object at ${key}`, options);
|
|
21576
|
+
this.name = "ObjectNotFoundError";
|
|
21577
|
+
}
|
|
21578
|
+
};
|
|
21579
|
+
var InvalidStorageKeyError = class extends Error {
|
|
21580
|
+
constructor(key) {
|
|
21581
|
+
super(`Not a valid media storage key: ${JSON.stringify(key)}`);
|
|
21582
|
+
this.name = "InvalidStorageKeyError";
|
|
21583
|
+
}
|
|
21584
|
+
};
|
|
21585
|
+
//#endregion
|
|
21551
21586
|
//#region src/media/signing.ts
|
|
21552
21587
|
const sign = (secret, purpose, subject, expiresAt) => createHmac("sha256", secret).update(`${purpose}\n${subject}\n${expiresAt}`).digest("base64url");
|
|
21553
21588
|
const safeEqual = (a, b) => {
|
|
@@ -21665,9 +21700,10 @@ var StorageNotConfiguredError = class extends Error {
|
|
|
21665
21700
|
};
|
|
21666
21701
|
/**
|
|
21667
21702
|
* Every adapter this instance can reach. Local disk is always available; S3 whenever its bucket is set,
|
|
21668
|
-
* whatever STORAGE_DRIVER says, so assets keep being served while `shapio media migrate` moves them.
|
|
21703
|
+
* whatever STORAGE_DRIVER says, so assets keep being served while `shapio media migrate` moves them. The S3
|
|
21704
|
+
* adapter (and the AWS SDK) is loaded only then.
|
|
21669
21705
|
*/
|
|
21670
|
-
const createMediaStorage = (config, deps) => {
|
|
21706
|
+
const createMediaStorage = async (config, deps) => {
|
|
21671
21707
|
const adapters = /* @__PURE__ */ new Map();
|
|
21672
21708
|
adapters.set("local", createLocalAdapter({
|
|
21673
21709
|
root: config.mediaPath,
|
|
@@ -21675,10 +21711,13 @@ const createMediaStorage = (config, deps) => {
|
|
|
21675
21711
|
signingSecret: deps.signingSecret,
|
|
21676
21712
|
publicBaseUrl: config.publicBaseUrl
|
|
21677
21713
|
}));
|
|
21678
|
-
if (config.s3)
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21714
|
+
if (config.s3) {
|
|
21715
|
+
const { createS3Adapter } = await import("./s3Adapter-BipoQiwQ.js");
|
|
21716
|
+
adapters.set("s3", createS3Adapter({
|
|
21717
|
+
config: config.s3,
|
|
21718
|
+
publicBaseUrl: config.publicBaseUrl
|
|
21719
|
+
}));
|
|
21720
|
+
}
|
|
21682
21721
|
const get = (driver) => {
|
|
21683
21722
|
const adapter = adapters.get(driver);
|
|
21684
21723
|
if (!adapter) throw new StorageNotConfiguredError(driver);
|
|
@@ -21694,7 +21733,7 @@ const createMediaStorage = (config, deps) => {
|
|
|
21694
21733
|
//#region src/plugins/mediaStorage.ts
|
|
21695
21734
|
/** Exposes the media storage adapters to routes. Registered after the signing secret is resolved. */
|
|
21696
21735
|
const mediaStoragePlugin = fp(async (app, { storage, urls, signingSecret }) => {
|
|
21697
|
-
app.decorate("mediaStorage", createMediaStorage(storage, {
|
|
21736
|
+
app.decorate("mediaStorage", await createMediaStorage(storage, {
|
|
21698
21737
|
urls,
|
|
21699
21738
|
signingSecret
|
|
21700
21739
|
}));
|
|
@@ -22492,7 +22531,8 @@ const outboundPolicy = (runtime, allowPrivateNetwork) => ({
|
|
|
22492
22531
|
});
|
|
22493
22532
|
//#endregion
|
|
22494
22533
|
//#region src/services/roles.ts
|
|
22495
|
-
const
|
|
22534
|
+
const MODEL_ACTION_SET = /* @__PURE__ */ new Set([...CONTENT_ACTIONS, ...DELIVERY_ACTIONS]);
|
|
22535
|
+
const DELIVERY_ACTION_SET = new Set(DELIVERY_ACTIONS);
|
|
22496
22536
|
const GLOBAL_ACTION_SET = new Set(GLOBAL_ACTIONS);
|
|
22497
22537
|
const FIELD_SCOPED_ACTIONS = /* @__PURE__ */ new Set([
|
|
22498
22538
|
"read",
|
|
@@ -22519,9 +22559,17 @@ const toRoleView = (role, grants) => ({
|
|
|
22519
22559
|
});
|
|
22520
22560
|
const invalidPermissions = (message, details) => new AppError(400, "INVALID_PERMISSIONS", message, details);
|
|
22521
22561
|
/**
|
|
22562
|
+
* `readDrafts` (plan drafts-mode §1): delivery roles only, and always on every model with no condition or
|
|
22563
|
+
* field list, so one check per request covers every model a draft read reaches (relation targets too).
|
|
22564
|
+
*/
|
|
22565
|
+
const validateReadDrafts = (kind, permission, at) => {
|
|
22566
|
+
if (kind !== "delivery") throw invalidPermissions("Read drafts is for delivery roles only", at);
|
|
22567
|
+
if (permission.modelId !== null || permission.condition !== null || permission.fieldIds !== null) throw invalidPermissions("Read drafts applies to every model, with no condition or field list", at);
|
|
22568
|
+
};
|
|
22569
|
+
/**
|
|
22522
22570
|
* The server-side rules for grants (never only the UI): known actions, global actions only without a
|
|
22523
|
-
* model, conditions and field lists only where they mean something, delivery roles read-only
|
|
22524
|
-
* duplicate (action, model) pairs.
|
|
22571
|
+
* model, conditions and field lists only where they mean something, delivery roles read-only (`read`, and
|
|
22572
|
+
* `readDrafts` on every model), no duplicate (action, model) pairs.
|
|
22525
22573
|
*/
|
|
22526
22574
|
const validatePermissions = (kind, permissions) => {
|
|
22527
22575
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -22531,9 +22579,10 @@ const validatePermissions = (kind, permissions) => {
|
|
|
22531
22579
|
action: permission.action
|
|
22532
22580
|
};
|
|
22533
22581
|
const isGlobal = GLOBAL_ACTION_SET.has(permission.action);
|
|
22534
|
-
if (!isGlobal && !
|
|
22582
|
+
if (!isGlobal && !MODEL_ACTION_SET.has(permission.action)) throw invalidPermissions("Unknown action", at);
|
|
22535
22583
|
if (isGlobal && (permission.modelId !== null || permission.condition !== null || permission.fieldIds !== null)) throw invalidPermissions("Global actions take no model, condition or field list", at);
|
|
22536
|
-
if (kind === "delivery" && permission.action
|
|
22584
|
+
if (kind === "delivery" && !DELIVERY_ACTION_SET.has(permission.action)) throw invalidPermissions("Delivery roles can only read", at);
|
|
22585
|
+
if (permission.action === "readDrafts") validateReadDrafts(kind, permission, at);
|
|
22537
22586
|
if (permission.fieldIds !== null && !FIELD_SCOPED_ACTIONS.has(permission.action)) throw invalidPermissions("Field lists apply to read, create and update only", at);
|
|
22538
22587
|
const key = `${permission.action}\u0000${permission.modelId ?? "*"}`;
|
|
22539
22588
|
if (seen.has(key)) throw invalidPermissions("Each action may be granted once per model", at);
|
|
@@ -22885,7 +22934,11 @@ Type.Enum(SITE_ACTIONS);
|
|
|
22885
22934
|
/** What `me.sitePermissions` lists: site actions, and network actions a role on the site grants there. */
|
|
22886
22935
|
const SitePermissionSchema = Type.Enum([...SITE_ACTIONS, ...SITE_GRANTABLE_ACTION_LIST]);
|
|
22887
22936
|
const PermissionSchema$1 = Type.Object({
|
|
22888
|
-
action: Type.Enum([
|
|
22937
|
+
action: Type.Enum([
|
|
22938
|
+
...CONTENT_ACTIONS,
|
|
22939
|
+
READ_DRAFTS_ACTION,
|
|
22940
|
+
...GLOBAL_ACTIONS
|
|
22941
|
+
]),
|
|
22889
22942
|
modelId: Type.Union([Type.String({
|
|
22890
22943
|
minLength: 1,
|
|
22891
22944
|
maxLength: 100
|
|
@@ -38999,7 +39052,7 @@ const openStoredBundle = (storage, stored) => storage.get(stored.driver).getStre
|
|
|
38999
39052
|
const removeStoredBundle = (storage, stored) => storage.get(stored.driver).delete(stored.key);
|
|
39000
39053
|
//#endregion
|
|
39001
39054
|
//#region src/constants/version.ts
|
|
39002
|
-
const SHAPIO_VERSION = "0.
|
|
39055
|
+
const SHAPIO_VERSION = "0.5.1";
|
|
39003
39056
|
//#endregion
|
|
39004
39057
|
//#region src/repositories/transferExport.ts
|
|
39005
39058
|
/**
|
|
@@ -39791,21 +39844,21 @@ const importContent = async (run, initial) => {
|
|
|
39791
39844
|
});
|
|
39792
39845
|
};
|
|
39793
39846
|
/** Deletes live target entries of the bundle's models that the bundle does not have. */
|
|
39794
|
-
const prune = async (run,
|
|
39847
|
+
const prune = async (run, network) => {
|
|
39795
39848
|
const keep = /* @__PURE__ */ new Set();
|
|
39796
39849
|
const models = /* @__PURE__ */ new Set();
|
|
39797
39850
|
for await (const { record } of readBundle(await open$1(run))) if (record.type === "entry") {
|
|
39798
39851
|
keep.add(record.id);
|
|
39799
39852
|
models.add(record.modelId);
|
|
39800
39853
|
}
|
|
39801
|
-
const context = {
|
|
39854
|
+
const context = buildContentContext({
|
|
39802
39855
|
db: run.deps.db,
|
|
39803
|
-
|
|
39856
|
+
network,
|
|
39804
39857
|
permissions: SYSTEM_PERMISSIONS,
|
|
39805
39858
|
actor: SYSTEM_ACTOR,
|
|
39806
|
-
site: await getSiteRef(run.payload.siteId, run.deps.db)
|
|
39807
|
-
|
|
39808
|
-
};
|
|
39859
|
+
site: await getSiteRef(run.payload.siteId, run.deps.db)
|
|
39860
|
+
});
|
|
39861
|
+
const { snapshot } = context;
|
|
39809
39862
|
let failed = [];
|
|
39810
39863
|
for (let round = 0; round < PRUNE_ROUNDS; round += 1) {
|
|
39811
39864
|
failed = [];
|
|
@@ -39841,7 +39894,7 @@ const runPhase = async (run, phase) => {
|
|
|
39841
39894
|
case "media": return importMedia(run);
|
|
39842
39895
|
case "entries": return importEntryRows(run, await loadSiteSnapshot(run));
|
|
39843
39896
|
case "content": return importContent(run, await loadSiteSnapshot(run));
|
|
39844
|
-
case "prune": return run.payload.prune ? prune(run, await
|
|
39897
|
+
case "prune": return run.payload.prune ? prune(run, await loadSnapshot(run.deps.db)) : void 0;
|
|
39845
39898
|
case "cleanup": return removeStoredBundle(run.deps.storage, run.payload.bundle).catch((error) => run.job.log.warn({
|
|
39846
39899
|
err: describeError(error),
|
|
39847
39900
|
key: run.payload.bundle.key
|
|
@@ -41149,18 +41202,22 @@ const appAuthRoutes = async (app) => {
|
|
|
41149
41202
|
};
|
|
41150
41203
|
//#endregion
|
|
41151
41204
|
//#region src/controllers/delivery.ts
|
|
41205
|
+
const cacheControlOf = (request, noStore) => {
|
|
41206
|
+
if (noStore) return DRAFTS_CACHE_CONTROL;
|
|
41207
|
+
return request.principal.kind === "anonymous" ? "public, max-age=0, must-revalidate" : "private, max-age=0, must-revalidate";
|
|
41208
|
+
};
|
|
41152
41209
|
/**
|
|
41153
41210
|
* Delivery responses are cacheable by validators: a strong ETag over the exact body (so it changes whenever
|
|
41154
41211
|
* a head version, a populated target or the projection changes), `Vary` on the credentials that select the
|
|
41155
41212
|
* principal and on the `Shapio-Site` header that selects the site (`?site=` is part of the URL), and `private`
|
|
41156
|
-
* caching whenever a principal is authenticated (build plan §4.E6).
|
|
41213
|
+
* caching whenever a principal is authenticated (build plan §4.E6). `noStore`: draft reads, never stored.
|
|
41157
41214
|
*/
|
|
41158
|
-
const sendCacheable = (request, reply, payload) => {
|
|
41215
|
+
const sendCacheable = (request, reply, payload, { noStore = false } = {}) => {
|
|
41159
41216
|
const body = JSON.stringify(payload);
|
|
41160
41217
|
const etag = `"${createHash("sha256").update(body).digest("base64url").slice(0, 32)}"`;
|
|
41161
41218
|
reply.header("etag", etag);
|
|
41162
41219
|
appendVary(reply, DELIVERY_VARY);
|
|
41163
|
-
reply.header("cache-control", request
|
|
41220
|
+
reply.header("cache-control", cacheControlOf(request, noStore));
|
|
41164
41221
|
const match = request.headers["if-none-match"];
|
|
41165
41222
|
if (typeof match === "string" && match.split(",").some((candidate) => candidate.trim() === etag)) return reply.code(304).send();
|
|
41166
41223
|
return reply.type("application/json; charset=utf-8").send(body);
|
|
@@ -41170,15 +41227,17 @@ const usageOptions = (request) => ({
|
|
|
41170
41227
|
usage: request.server.usage,
|
|
41171
41228
|
defer: (task) => request.server.recordUsageAfterResponse(request.log, task)
|
|
41172
41229
|
});
|
|
41230
|
+
/** A delivery response, never stored by a cache when it carries drafts. */
|
|
41231
|
+
const sendDelivery = (request, reply, result) => sendCacheable(request, reply, result, { noStore: result.meta.publicationState === "draft" });
|
|
41173
41232
|
const listDelivery = async (request, reply) => {
|
|
41174
41233
|
const context = await contentContextFor(request);
|
|
41175
41234
|
const apiKey = apiKeyOfRoute(context.snapshot, request.params.modelKey);
|
|
41176
|
-
return
|
|
41235
|
+
return sendDelivery(request, reply, await listDelivery$1(context, apiKey, rawQueryOf(request), usageOptions(request)));
|
|
41177
41236
|
};
|
|
41178
41237
|
const getDelivery = async (request, reply) => {
|
|
41179
41238
|
const context = await contentContextFor(request);
|
|
41180
41239
|
const apiKey = apiKeyOfRoute(context.snapshot, request.params.modelKey);
|
|
41181
|
-
return
|
|
41240
|
+
return sendDelivery(request, reply, await getDelivery$1(context, apiKey, request.params.id, rawQueryOf(request), usageOptions(request)));
|
|
41182
41241
|
};
|
|
41183
41242
|
//#endregion
|
|
41184
41243
|
//#region src/controllers/deliveryWrites.ts
|
|
@@ -41846,6 +41905,16 @@ const itemParameters = (model, delivery) => [
|
|
|
41846
41905
|
},
|
|
41847
41906
|
description: "Rich-text shape: the JSON document, sanitized HTML rendered from it, or both"
|
|
41848
41907
|
},
|
|
41908
|
+
{
|
|
41909
|
+
name: "publicationState",
|
|
41910
|
+
in: "query",
|
|
41911
|
+
schema: {
|
|
41912
|
+
type: "string",
|
|
41913
|
+
enum: [...PUBLICATION_STATES],
|
|
41914
|
+
default: "published"
|
|
41915
|
+
},
|
|
41916
|
+
description: "Drafts mode: `draft` reads saved drafts instead of published content. Needs a delivery token whose role grants Read drafts (or an admin user); responses are never cached. Cannot be combined with `snapshot`."
|
|
41917
|
+
},
|
|
41849
41918
|
...seoParameters(model)
|
|
41850
41919
|
] : []
|
|
41851
41920
|
];
|
|
@@ -41871,7 +41940,12 @@ const modelPaths = (model) => {
|
|
|
41871
41940
|
type: "object",
|
|
41872
41941
|
properties: {
|
|
41873
41942
|
locale: { type: "string" },
|
|
41874
|
-
snapshot: { type: "integer" }
|
|
41943
|
+
snapshot: { type: "integer" },
|
|
41944
|
+
publicationState: {
|
|
41945
|
+
type: "string",
|
|
41946
|
+
enum: ["draft"],
|
|
41947
|
+
description: "Present on draft reads only"
|
|
41948
|
+
}
|
|
41875
41949
|
}
|
|
41876
41950
|
};
|
|
41877
41951
|
const listMeta = { allOf: [meta, {
|
|
@@ -42293,14 +42367,14 @@ const docsRoutes = async (app) => {
|
|
|
42293
42367
|
//#endregion
|
|
42294
42368
|
//#region src/db/migrations/20261001120000_create_platform_tables.ts
|
|
42295
42369
|
var _20261001120000_create_platform_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42296
|
-
down: () => down$
|
|
42297
|
-
up: () => up$
|
|
42370
|
+
down: () => down$34,
|
|
42371
|
+
up: () => up$34
|
|
42298
42372
|
});
|
|
42299
42373
|
/**
|
|
42300
42374
|
* Foundation tables shared by every package: durable versions, the job queue, the transactional outbox
|
|
42301
42375
|
* and the audit log. Statuses are text with CHECK constraints so new states need only a constraint change.
|
|
42302
42376
|
*/
|
|
42303
|
-
const up$
|
|
42377
|
+
const up$34 = async (db) => {
|
|
42304
42378
|
await db.schema.createTable("system_versions").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("schema_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("permissions_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42305
42379
|
await sql`insert into system_versions default values`.execute(db);
|
|
42306
42380
|
await db.schema.createTable("jobs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("type", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'running', 'succeeded', 'dead')`)).addColumn("priority", "integer", (col) => col.notNull().defaultTo(0)).addColumn("run_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("max_attempts", "integer", (col) => col.notNull().defaultTo(10).check(sql`max_attempts > 0`)).addColumn("idempotency_key", "text").addColumn("locked_by", "text").addColumn("locked_until", "timestamptz").addColumn("checkpoint", "jsonb").addColumn("result", "jsonb").addColumn("last_error", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
@@ -42322,7 +42396,7 @@ const up$33 = async (db) => {
|
|
|
42322
42396
|
"occurred_at desc"
|
|
42323
42397
|
]).execute();
|
|
42324
42398
|
};
|
|
42325
|
-
const down$
|
|
42399
|
+
const down$34 = async (db) => {
|
|
42326
42400
|
await db.schema.dropTable("audit_events").execute();
|
|
42327
42401
|
await db.schema.dropTable("outbox_events").execute();
|
|
42328
42402
|
await db.schema.dropTable("jobs").execute();
|
|
@@ -42331,8 +42405,8 @@ const down$33 = async (db) => {
|
|
|
42331
42405
|
//#endregion
|
|
42332
42406
|
//#region src/db/migrations/20261001130000_create_admin_identity.ts
|
|
42333
42407
|
var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll({
|
|
42334
|
-
down: () => down$
|
|
42335
|
-
up: () => up$
|
|
42408
|
+
down: () => down$33,
|
|
42409
|
+
up: () => up$33
|
|
42336
42410
|
});
|
|
42337
42411
|
/**
|
|
42338
42412
|
* Admin identity (package B, ADR 0005): admin users, roles and their permission grants, server-side
|
|
@@ -42341,7 +42415,7 @@ var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll(
|
|
|
42341
42415
|
*/
|
|
42342
42416
|
const withTimestamps$3 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
|
|
42343
42417
|
const uuidPrimaryKey$3 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42344
|
-
const up$
|
|
42418
|
+
const up$33 = async (db) => {
|
|
42345
42419
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_users")).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("name", "text", (col) => col.notNull().defaultTo("")).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("active").check(sql`status in ('active', 'disabled')`)).addColumn("last_login_at", "timestamptz").addColumn("password_changed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`))).execute();
|
|
42346
42420
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_roles")).addColumn("key", "text", (col) => col.notNull().unique().check(sql`key ~ '^[a-z][a-z0-9-]{0,62}$'`)).addColumn("name", "text", (col) => col.notNull()).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("kind", "text", (col) => col.notNull().defaultTo("admin").check(sql`kind in ('admin', 'delivery')`)).addColumn("is_system", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42347
42421
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_role_permissions")).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("cascade")).addColumn("action", "text", (col) => col.notNull()).addColumn("model_id", "text").addColumn("condition", "text", (col) => col.check(sql`condition in ('ownedByPrincipal')`)).addColumn("field_ids", sql`text[]`).addUniqueConstraint("admin_role_permissions_grant_uq", [
|
|
@@ -42361,7 +42435,7 @@ const up$32 = async (db) => {
|
|
|
42361
42435
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("api_tokens")).addColumn("name", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("restrict")).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("expires_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("revoked_at", "timestamptz")).execute();
|
|
42362
42436
|
await db.schema.createIndex("api_tokens_role_idx").on("api_tokens").column("role_id").execute();
|
|
42363
42437
|
};
|
|
42364
|
-
const down$
|
|
42438
|
+
const down$33 = async (db) => {
|
|
42365
42439
|
await db.schema.dropTable("api_tokens").execute();
|
|
42366
42440
|
await db.schema.dropTable("setup_tokens").execute();
|
|
42367
42441
|
await db.schema.dropTable("password_resets").execute();
|
|
@@ -42375,31 +42449,31 @@ const down$32 = async (db) => {
|
|
|
42375
42449
|
//#endregion
|
|
42376
42450
|
//#region src/db/migrations/20261001130500_create_system_settings.ts
|
|
42377
42451
|
var _20261001130500_create_system_settings_exports = /* @__PURE__ */ __exportAll({
|
|
42378
|
-
down: () => down$
|
|
42379
|
-
up: () => up$
|
|
42452
|
+
down: () => down$32,
|
|
42453
|
+
up: () => up$32
|
|
42380
42454
|
});
|
|
42381
42455
|
/**
|
|
42382
42456
|
* Instance-wide key/value settings that must survive restarts and be shared by every instance, e.g. the
|
|
42383
42457
|
* generated signing secret. General purpose: later packages add keys, not tables.
|
|
42384
42458
|
*/
|
|
42385
|
-
const up$
|
|
42459
|
+
const up$32 = async (db) => {
|
|
42386
42460
|
await db.schema.createTable("system_settings").addColumn("key", "text", (col) => col.primaryKey()).addColumn("value", "text", (col) => col.notNull()).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42387
42461
|
};
|
|
42388
|
-
const down$
|
|
42462
|
+
const down$32 = async (db) => {
|
|
42389
42463
|
await db.schema.dropTable("system_settings").execute();
|
|
42390
42464
|
};
|
|
42391
42465
|
//#endregion
|
|
42392
42466
|
//#region src/db/migrations/20261001140000_create_schema_tables.ts
|
|
42393
42467
|
var _20261001140000_create_schema_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42394
|
-
down: () => down$
|
|
42395
|
-
up: () => up$
|
|
42468
|
+
down: () => down$31,
|
|
42469
|
+
up: () => up$31
|
|
42396
42470
|
});
|
|
42397
42471
|
/**
|
|
42398
42472
|
* Schema registry (package D, ADR 0002): models and components (one table, `kind` discriminator), immutable
|
|
42399
42473
|
* schema revisions, the per-model active pointer, schema change jobs, locales and schema settings.
|
|
42400
42474
|
* Model definitions are data: creating or changing a model never runs DDL (CONTRIBUTING.md rule 1).
|
|
42401
42475
|
*/
|
|
42402
|
-
const up$
|
|
42476
|
+
const up$31 = async (db) => {
|
|
42403
42477
|
await db.schema.createTable("models").addColumn("id", "uuid", (col) => col.primaryKey()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('collection', 'singleton', 'component')`)).addColumn("api_key", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42404
42478
|
await sql`create unique index models_api_key_uq on models (lower(api_key)) where deleted_at is null`.execute(db);
|
|
42405
42479
|
await db.schema.createTable("schema_revisions").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("version", "integer", (col) => col.notNull().check(sql`version > 0`)).addColumn("definition", "jsonb", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("parent_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("created_by_type", "text", (col) => col.notNull()).addColumn("created_by_id", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addUniqueConstraint("schema_revisions_model_version_uq", ["model_id", "version"]).execute();
|
|
@@ -42427,7 +42501,7 @@ const up$30 = async (db) => {
|
|
|
42427
42501
|
await db.schema.createTable("schema_settings").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("read_only", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("read_only_reason", "text").addColumn("updated_by_type", "text").addColumn("updated_by_id", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42428
42502
|
await sql`insert into schema_settings default values`.execute(db);
|
|
42429
42503
|
};
|
|
42430
|
-
const down$
|
|
42504
|
+
const down$31 = async (db) => {
|
|
42431
42505
|
await db.schema.dropTable("schema_settings").execute();
|
|
42432
42506
|
await db.schema.dropTable("locales").execute();
|
|
42433
42507
|
await db.schema.dropTable("schema_change_jobs").execute();
|
|
@@ -42439,8 +42513,8 @@ const down$30 = async (db) => {
|
|
|
42439
42513
|
//#endregion
|
|
42440
42514
|
//#region src/db/migrations/20261001150000_create_content_tables.ts
|
|
42441
42515
|
var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42442
|
-
down: () => down$
|
|
42443
|
-
up: () => up$
|
|
42516
|
+
down: () => down$30,
|
|
42517
|
+
up: () => up$30
|
|
42444
42518
|
});
|
|
42445
42519
|
/**
|
|
42446
42520
|
* Content storage (package E, ADR 0001): entries, immutable content revisions, mutable heads (one per
|
|
@@ -42448,7 +42522,7 @@ var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll(
|
|
|
42448
42522
|
* and the publication log behind `?snapshot=N`. User content models never get tables of their own
|
|
42449
42523
|
* (CONTRIBUTING.md rule 1): their values are JSONB keyed by stable field IDs.
|
|
42450
42524
|
*/
|
|
42451
|
-
const up$
|
|
42525
|
+
const up$30 = async (db) => {
|
|
42452
42526
|
await db.schema.createTable("entries").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("owner_app_user_id", "uuid").addColumn("created_by_admin_id", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42453
42527
|
await sql`create index entries_model_idx on entries (model_id, created_at) where deleted_at is null`.execute(db);
|
|
42454
42528
|
await sql`create index entries_owner_idx on entries (owner_app_user_id) where owner_app_user_id is not null`.execute(db);
|
|
@@ -42512,7 +42586,7 @@ const up$29 = async (db) => {
|
|
|
42512
42586
|
await sql`create unique index publication_log_open_uq on publication_log (entry_id, locale) where to_seq is null`.execute(db);
|
|
42513
42587
|
await db.schema.createIndex("publication_log_model_seq_idx").on("publication_log").columns(["model_id", "from_seq"]).execute();
|
|
42514
42588
|
};
|
|
42515
|
-
const down$
|
|
42589
|
+
const down$30 = async (db) => {
|
|
42516
42590
|
await db.schema.dropTable("publication_log").execute();
|
|
42517
42591
|
await db.schema.dropTable("publication_state").execute();
|
|
42518
42592
|
await db.schema.dropTable("unique_values").execute();
|
|
@@ -42525,8 +42599,8 @@ const down$29 = async (db) => {
|
|
|
42525
42599
|
//#endregion
|
|
42526
42600
|
//#region src/db/migrations/20261001160000_create_media_tables.ts
|
|
42527
42601
|
var _20261001160000_create_media_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42528
|
-
down: () => down$
|
|
42529
|
-
up: () => up$
|
|
42602
|
+
down: () => down$29,
|
|
42603
|
+
up: () => up$29
|
|
42530
42604
|
});
|
|
42531
42605
|
/**
|
|
42532
42606
|
* Media library (package G): folders, assets, image variants, references from content and upload grants.
|
|
@@ -42537,7 +42611,7 @@ const withTimestamps$2 = (table) => table.addColumn("created_at", "timestamptz",
|
|
|
42537
42611
|
const uuidPrimaryKey$2 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42538
42612
|
const STORAGE_DRIVER_CHECK = sql`storage_driver in ('local', 's3')`;
|
|
42539
42613
|
const VISIBILITY_CHECK = sql`visibility in ('public', 'private')`;
|
|
42540
|
-
const up$
|
|
42614
|
+
const up$29 = async (db) => {
|
|
42541
42615
|
await withTimestamps$2(uuidPrimaryKey$2(db.schema.createTable("media_folders")).addColumn("parent_id", "uuid", (col) => col.references("media_folders.id").onDelete("restrict")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 255`)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42542
42616
|
await sql`create unique index media_folders_sibling_name_uq on media_folders
|
|
42543
42617
|
(coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
|
|
@@ -42566,7 +42640,7 @@ const up$28 = async (db) => {
|
|
|
42566
42640
|
]).execute();
|
|
42567
42641
|
await db.schema.createTable("media_upload_grants").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("asset_id", "uuid", (col) => col.notNull()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('create', 'replace')`)).addColumn("storage_driver", "text", (col) => col.notNull().check(STORAGE_DRIVER_CHECK)).addColumn("storage_key", "text", (col) => col.notNull().unique()).addColumn("original_filename", "text", (col) => col.notNull()).addColumn("declared_mime_type", "text", (col) => col.notNull()).addColumn("max_size_bytes", "bigint", (col) => col.notNull()).addColumn("folder_id", "uuid").addColumn("visibility", "text", (col) => col.notNull().check(VISIBILITY_CHECK)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_by_principal", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'consumed', 'rejected', 'expired')`)).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("consumed_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42568
42642
|
};
|
|
42569
|
-
const down$
|
|
42643
|
+
const down$29 = async (db) => {
|
|
42570
42644
|
await db.schema.dropTable("media_upload_grants").execute();
|
|
42571
42645
|
await db.schema.dropTable("media_references").execute();
|
|
42572
42646
|
await db.schema.dropTable("media_variants").execute();
|
|
@@ -42576,8 +42650,8 @@ const down$28 = async (db) => {
|
|
|
42576
42650
|
//#endregion
|
|
42577
42651
|
//#region src/db/migrations/20261001170000_create_publishing_tables.ts
|
|
42578
42652
|
var _20261001170000_create_publishing_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42579
|
-
down: () => down$
|
|
42580
|
-
up: () => up$
|
|
42653
|
+
down: () => down$28,
|
|
42654
|
+
up: () => up$28
|
|
42581
42655
|
});
|
|
42582
42656
|
/**
|
|
42583
42657
|
* Publishing (package H): scheduled publications, releases, webhooks and their delivery log, deployment
|
|
@@ -42616,13 +42690,13 @@ const createPreviewTables = async (db) => {
|
|
|
42616
42690
|
await uuidPrimaryKey$1(db.schema.createTable("preview_tokens")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("entry_id", "uuid", (col) => col.references("entries.id").onDelete("cascade")).addColumn("locale", "text").addColumn("connection_id", "uuid", (col) => col.references("deployment_connections.id").onDelete("set null")).addColumn("delivery_role_id", "uuid", (col) => col.references("admin_roles.id").onDelete("set null")).addColumn("created_by", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("revoked_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42617
42691
|
await db.schema.createIndex("preview_tokens_entry_idx").on("preview_tokens").columns(["entry_id", "created_at desc"]).execute();
|
|
42618
42692
|
};
|
|
42619
|
-
const up$
|
|
42693
|
+
const up$28 = async (db) => {
|
|
42620
42694
|
await createSchedulingTables(db);
|
|
42621
42695
|
await createWebhookTables(db);
|
|
42622
42696
|
await createDeploymentTables(db);
|
|
42623
42697
|
await createPreviewTables(db);
|
|
42624
42698
|
};
|
|
42625
|
-
const down$
|
|
42699
|
+
const down$28 = async (db) => {
|
|
42626
42700
|
await db.schema.dropTable("preview_tokens").execute();
|
|
42627
42701
|
await db.schema.dropTable("deployment_runs").execute();
|
|
42628
42702
|
await db.schema.dropTable("deployment_connections").execute();
|
|
@@ -42635,8 +42709,8 @@ const down$27 = async (db) => {
|
|
|
42635
42709
|
//#endregion
|
|
42636
42710
|
//#region src/db/migrations/20261001180000_create_app_users.ts
|
|
42637
42711
|
var _20261001180000_create_app_users_exports = /* @__PURE__ */ __exportAll({
|
|
42638
|
-
down: () => down$
|
|
42639
|
-
up: () => up$
|
|
42712
|
+
down: () => down$27,
|
|
42713
|
+
up: () => up$27
|
|
42640
42714
|
});
|
|
42641
42715
|
/**
|
|
42642
42716
|
* App users (package I, ADR 0005): the end users of sites and apps built on Shapio, with their own roles
|
|
@@ -42687,7 +42761,7 @@ const createCredentials = async (db) => {
|
|
|
42687
42761
|
}
|
|
42688
42762
|
await db.schema.createTable("app_login_codes").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("code_hash", "text", (col) => col.notNull().unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42689
42763
|
};
|
|
42690
|
-
const up$
|
|
42764
|
+
const up$27 = async (db) => {
|
|
42691
42765
|
await createUsers(db);
|
|
42692
42766
|
await createRoles(db);
|
|
42693
42767
|
await createCredentials(db);
|
|
@@ -42697,7 +42771,7 @@ const up$26 = async (db) => {
|
|
|
42697
42771
|
foreign key (owner_app_user_id) references app_users (id) on delete set null
|
|
42698
42772
|
`.execute(db);
|
|
42699
42773
|
};
|
|
42700
|
-
const down$
|
|
42774
|
+
const down$27 = async (db) => {
|
|
42701
42775
|
await sql`alter table entries drop constraint if exists entries_owner_app_user_fk`.execute(db);
|
|
42702
42776
|
await db.schema.dropTable("app_login_codes").execute();
|
|
42703
42777
|
await db.schema.dropTable("app_password_resets").execute();
|
|
@@ -42712,8 +42786,8 @@ const down$26 = async (db) => {
|
|
|
42712
42786
|
//#endregion
|
|
42713
42787
|
//#region src/db/migrations/20261002120000_create_extension_hook_runs.ts
|
|
42714
42788
|
var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __exportAll({
|
|
42715
|
-
down: () => down$
|
|
42716
|
-
up: () => up$
|
|
42789
|
+
down: () => down$26,
|
|
42790
|
+
up: () => up$26
|
|
42717
42791
|
});
|
|
42718
42792
|
/**
|
|
42719
42793
|
* Extension points (package K, ADR 0009): the ledger of post-commit lifecycle hooks (`afterCreate`,
|
|
@@ -42721,17 +42795,17 @@ var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __expor
|
|
|
42721
42795
|
* as the hook's own database work, so a hook re-run after a worker crash finds its row and is skipped:
|
|
42722
42796
|
* exactly once for everything the hook does through its transaction.
|
|
42723
42797
|
*/
|
|
42724
|
-
const up$
|
|
42798
|
+
const up$26 = async (db) => {
|
|
42725
42799
|
await db.schema.createTable("extension_hook_runs").addColumn("event_id", "uuid", (col) => col.notNull()).addColumn("hook", "text", (col) => col.notNull()).addColumn("job_id", "uuid").addColumn("completed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("extension_hook_runs_pkey", ["event_id", "hook"]).execute();
|
|
42726
42800
|
};
|
|
42727
|
-
const down$
|
|
42801
|
+
const down$26 = async (db) => {
|
|
42728
42802
|
await db.schema.dropTable("extension_hook_runs").execute();
|
|
42729
42803
|
};
|
|
42730
42804
|
//#endregion
|
|
42731
42805
|
//#region src/db/migrations/20261002130000_app_auth_token_version_and_login_code_pkce.ts
|
|
42732
42806
|
var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__PURE__ */ __exportAll({
|
|
42733
|
-
down: () => down$
|
|
42734
|
-
up: () => up$
|
|
42807
|
+
down: () => down$25,
|
|
42808
|
+
up: () => up$25
|
|
42735
42809
|
});
|
|
42736
42810
|
/**
|
|
42737
42811
|
* App users (ADR 0005):
|
|
@@ -42742,20 +42816,20 @@ var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__P
|
|
|
42742
42816
|
* sign-in; the one-time login code is only exchanged with the matching verifier. Codes live a minute, so
|
|
42743
42817
|
* any pending ones (issued without a challenge) are dropped rather than given a placeholder.
|
|
42744
42818
|
*/
|
|
42745
|
-
const up$
|
|
42819
|
+
const up$25 = async (db) => {
|
|
42746
42820
|
await db.schema.alterTable("app_users").addColumn("token_version", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
42747
42821
|
await sql`delete from app_login_codes`.execute(db);
|
|
42748
42822
|
await db.schema.alterTable("app_login_codes").addColumn("code_challenge", "text", (col) => col.notNull()).execute();
|
|
42749
42823
|
};
|
|
42750
|
-
const down$
|
|
42824
|
+
const down$25 = async (db) => {
|
|
42751
42825
|
await db.schema.alterTable("app_login_codes").dropColumn("code_challenge").execute();
|
|
42752
42826
|
await db.schema.alterTable("app_users").dropColumn("token_version").execute();
|
|
42753
42827
|
};
|
|
42754
42828
|
//#endregion
|
|
42755
42829
|
//#region src/db/migrations/20261002180000_add_publication_log_diff_indexes.ts
|
|
42756
42830
|
var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
42757
|
-
down: () => down$
|
|
42758
|
-
up: () => up$
|
|
42831
|
+
down: () => down$24,
|
|
42832
|
+
up: () => up$24
|
|
42759
42833
|
});
|
|
42760
42834
|
/**
|
|
42761
42835
|
* The snapshot diff (repositories/snapshotDiff.ts) finds the (entry, locale) pairs whose live period opened
|
|
@@ -42764,7 +42838,7 @@ var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ _
|
|
|
42764
42838
|
* - `(entry_id, locale, from_seq)`: "the row live at seq N" is the latest row starting at or before N.
|
|
42765
42839
|
* Periods that opened in the range use the existing `(model_id, from_seq)` index.
|
|
42766
42840
|
*/
|
|
42767
|
-
const up$
|
|
42841
|
+
const up$24 = async (db) => {
|
|
42768
42842
|
await sql`create index publication_log_to_seq_idx on publication_log (to_seq) where to_seq is not null`.execute(db);
|
|
42769
42843
|
await db.schema.createIndex("publication_log_entry_locale_from_idx").on("publication_log").columns([
|
|
42770
42844
|
"entry_id",
|
|
@@ -42772,15 +42846,15 @@ const up$23 = async (db) => {
|
|
|
42772
42846
|
"from_seq"
|
|
42773
42847
|
]).execute();
|
|
42774
42848
|
};
|
|
42775
|
-
const down$
|
|
42849
|
+
const down$24 = async (db) => {
|
|
42776
42850
|
await db.schema.dropIndex("publication_log_entry_locale_from_idx").execute();
|
|
42777
42851
|
await db.schema.dropIndex("publication_log_to_seq_idx").execute();
|
|
42778
42852
|
};
|
|
42779
42853
|
//#endregion
|
|
42780
42854
|
//#region src/db/migrations/20261002180100_create_usage_tables.ts
|
|
42781
42855
|
var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42782
|
-
down: () => down$
|
|
42783
|
-
up: () => up$
|
|
42856
|
+
down: () => down$23,
|
|
42857
|
+
up: () => up$23
|
|
42784
42858
|
});
|
|
42785
42859
|
/**
|
|
42786
42860
|
* Field usage from delivery traffic (plan developer-face §5): daily counters written by the in-memory
|
|
@@ -42788,7 +42862,7 @@ var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
|
42788
42862
|
* payloads. `principal_key` is `token:<id>`, `app_users` or `anonymous`. Pruned by the retention job
|
|
42789
42863
|
* after USAGE_RETENTION_DAYS.
|
|
42790
42864
|
*/
|
|
42791
|
-
const up$
|
|
42865
|
+
const up$23 = async (db) => {
|
|
42792
42866
|
await db.schema.createTable("field_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("field_path", "text", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("selection", "text", (col) => col.notNull().check(sql`selection in ('explicit', 'implicit')`)).addColumn("reads", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("field_reads_pkey", [
|
|
42793
42867
|
"day",
|
|
42794
42868
|
"model_id",
|
|
@@ -42799,15 +42873,15 @@ const up$22 = async (db) => {
|
|
|
42799
42873
|
await db.schema.createIndex("field_reads_model_day_idx").on("field_reads").columns(["model_id", "day"]).execute();
|
|
42800
42874
|
await db.schema.createTable("token_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("requests", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_snapshot", "bigint").addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("token_reads_pkey", ["day", "principal_key"]).execute();
|
|
42801
42875
|
};
|
|
42802
|
-
const down$
|
|
42876
|
+
const down$23 = async (db) => {
|
|
42803
42877
|
await db.schema.dropTable("token_reads").execute();
|
|
42804
42878
|
await db.schema.dropTable("field_reads").execute();
|
|
42805
42879
|
};
|
|
42806
42880
|
//#endregion
|
|
42807
42881
|
//#region src/db/migrations/20261002180200_create_change_sets.ts
|
|
42808
42882
|
var _20261002180200_create_change_sets_exports = /* @__PURE__ */ __exportAll({
|
|
42809
|
-
down: () => down$
|
|
42810
|
-
up: () => up$
|
|
42883
|
+
down: () => down$22,
|
|
42884
|
+
up: () => up$22
|
|
42811
42885
|
});
|
|
42812
42886
|
/**
|
|
42813
42887
|
* Change sets (developer-face plan §5, ADR 0010): schema drafts and entry publications that ship as ONE
|
|
@@ -42986,7 +43060,7 @@ const migrateReleases = async (db) => {
|
|
|
42986
43060
|
await sql`drop table release_items`.execute(db);
|
|
42987
43061
|
await sql`drop table releases`.execute(db);
|
|
42988
43062
|
};
|
|
42989
|
-
const up$
|
|
43063
|
+
const up$22 = async (db) => {
|
|
42990
43064
|
await createTables(db);
|
|
42991
43065
|
await linkExecutionRecords(db);
|
|
42992
43066
|
await allowConversionRevisions(db);
|
|
@@ -43058,7 +43132,7 @@ const restoreReleases = async (db) => {
|
|
|
43058
43132
|
await sql`update webhooks set events = array_remove(array_remove(events, 'change_set.shipping'),
|
|
43059
43133
|
'change_set.discarded')`.execute(db);
|
|
43060
43134
|
};
|
|
43061
|
-
const down$
|
|
43135
|
+
const down$22 = async (db) => {
|
|
43062
43136
|
await restoreReleases(db);
|
|
43063
43137
|
await sql`alter table content_revisions drop constraint content_revisions_reason_check`.execute(db);
|
|
43064
43138
|
await sql`alter table content_revisions add constraint content_revisions_reason_check
|
|
@@ -43079,8 +43153,8 @@ const down$21 = async (db) => {
|
|
|
43079
43153
|
//#endregion
|
|
43080
43154
|
//#region src/db/migrations/20261002190000_create_editor_presence_and_content_health.ts
|
|
43081
43155
|
var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PURE__ */ __exportAll({
|
|
43082
|
-
down: () => down$
|
|
43083
|
-
up: () => up$
|
|
43156
|
+
down: () => down$21,
|
|
43157
|
+
up: () => up$21
|
|
43084
43158
|
});
|
|
43085
43159
|
/**
|
|
43086
43160
|
* Editor experience (plan editor-experience §9):
|
|
@@ -43091,7 +43165,7 @@ var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PU
|
|
|
43091
43165
|
* - `content_health_findings`: open and recently resolved findings of the health rules per entry and
|
|
43092
43166
|
* locale (`*` for the entry as a whole). An evaluation upserts what it found and resolves the rest.
|
|
43093
43167
|
*/
|
|
43094
|
-
const up$
|
|
43168
|
+
const up$21 = async (db) => {
|
|
43095
43169
|
await sql`
|
|
43096
43170
|
create unlogged table editor_presence (
|
|
43097
43171
|
entry_id uuid not null,
|
|
@@ -43128,15 +43202,15 @@ const up$20 = async (db) => {
|
|
|
43128
43202
|
await sql`create index content_health_findings_resolved_idx on content_health_findings (resolved_at)
|
|
43129
43203
|
where resolved_at is not null`.execute(db);
|
|
43130
43204
|
};
|
|
43131
|
-
const down$
|
|
43205
|
+
const down$21 = async (db) => {
|
|
43132
43206
|
await sql`drop table content_health_findings`.execute(db);
|
|
43133
43207
|
await sql`drop table editor_presence`.execute(db);
|
|
43134
43208
|
};
|
|
43135
43209
|
//#endregion
|
|
43136
43210
|
//#region src/db/migrations/20261003120000_add_changes_ship_permission.ts
|
|
43137
43211
|
var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __exportAll({
|
|
43138
|
-
down: () => down$
|
|
43139
|
-
up: () => up$
|
|
43212
|
+
down: () => down$20,
|
|
43213
|
+
up: () => up$20
|
|
43140
43214
|
});
|
|
43141
43215
|
/**
|
|
43142
43216
|
* `changes.ship` (agentic plan §I): shipping a change set (and scheduling a ship) becomes its own global
|
|
@@ -43144,7 +43218,7 @@ var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __expo
|
|
|
43144
43218
|
* live. Every role that holds `changes.manage` today keeps what it could do: it gets `changes.ship` too.
|
|
43145
43219
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43146
43220
|
*/
|
|
43147
|
-
const up$
|
|
43221
|
+
const up$20 = async (db) => {
|
|
43148
43222
|
await sql`
|
|
43149
43223
|
with granted as (
|
|
43150
43224
|
insert into admin_role_permissions (role_id, action, model_id, condition, field_ids)
|
|
@@ -43158,7 +43232,7 @@ const up$19 = async (db) => {
|
|
|
43158
43232
|
where exists (select 1 from granted)
|
|
43159
43233
|
`.execute(db);
|
|
43160
43234
|
};
|
|
43161
|
-
const down$
|
|
43235
|
+
const down$20 = async (db) => {
|
|
43162
43236
|
await sql`
|
|
43163
43237
|
with revoked as (
|
|
43164
43238
|
delete from admin_role_permissions where action = 'changes.ship' returning 1
|
|
@@ -43170,8 +43244,8 @@ const down$19 = async (db) => {
|
|
|
43170
43244
|
//#endregion
|
|
43171
43245
|
//#region src/db/migrations/20261003130000_create_sites.ts
|
|
43172
43246
|
var _20261003130000_create_sites_exports = /* @__PURE__ */ __exportAll({
|
|
43173
|
-
down: () => down$
|
|
43174
|
-
up: () => up$
|
|
43247
|
+
down: () => down$19,
|
|
43248
|
+
up: () => up$19
|
|
43175
43249
|
});
|
|
43176
43250
|
/**
|
|
43177
43251
|
* Sites (plan agentic-ecosystem §H, ADR 0011): one instance hosts many sites with one schema and one login.
|
|
@@ -43359,7 +43433,7 @@ const siteRoleAssignments = async (db) => {
|
|
|
43359
43433
|
`.execute(db);
|
|
43360
43434
|
await sql`alter table admin_invitations drop column role_ids`.execute(db);
|
|
43361
43435
|
};
|
|
43362
|
-
const up$
|
|
43436
|
+
const up$19 = async (db) => {
|
|
43363
43437
|
await createSites(db);
|
|
43364
43438
|
await addSiteColumns(db);
|
|
43365
43439
|
await tieCopiesToEntries(db);
|
|
@@ -43372,7 +43446,7 @@ const assertSingleSite = async (db) => {
|
|
|
43372
43446
|
const { rows } = await sql`select count(*)::text as count from sites`.execute(db);
|
|
43373
43447
|
if (Number(rows[0]?.count ?? 0) > 1) throw new Error("Cannot roll back sites while more than one site exists: merging them would merge their snapshot ledgers and uniqueness and widen role assignments. Delete the other sites first.");
|
|
43374
43448
|
};
|
|
43375
|
-
const down$
|
|
43449
|
+
const down$19 = async (db) => {
|
|
43376
43450
|
await assertSingleSite(db);
|
|
43377
43451
|
await sql`alter table admin_invitations add column role_ids uuid[] not null default '{}'::uuid[]`.execute(db);
|
|
43378
43452
|
await sql`
|
|
@@ -43443,8 +43517,8 @@ const down$18 = async (db) => {
|
|
|
43443
43517
|
//#endregion
|
|
43444
43518
|
//#region src/db/migrations/20261003140200_enqueue_field_index_layout.ts
|
|
43445
43519
|
var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __exportAll({
|
|
43446
|
-
down: () => down$
|
|
43447
|
-
up: () => up$
|
|
43520
|
+
down: () => down$18,
|
|
43521
|
+
up: () => up$18
|
|
43448
43522
|
});
|
|
43449
43523
|
/**
|
|
43450
43524
|
* Field index layout v2 (sites plan §H): per-field expression indexes on `entry_heads` lead with `site_id`,
|
|
@@ -43456,7 +43530,7 @@ var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __expor
|
|
|
43456
43530
|
*/
|
|
43457
43531
|
const JOB_TYPE$1 = "schema.fieldIndexLayout";
|
|
43458
43532
|
const IDEMPOTENCY_KEY$1 = "schema.fieldIndexLayout:v2";
|
|
43459
|
-
const up$
|
|
43533
|
+
const up$18 = async (db) => {
|
|
43460
43534
|
await sql`
|
|
43461
43535
|
insert into jobs (type, payload, max_attempts, idempotency_key)
|
|
43462
43536
|
select ${JOB_TYPE$1}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY$1}
|
|
@@ -43469,14 +43543,14 @@ const up$17 = async (db) => {
|
|
|
43469
43543
|
`.execute(db);
|
|
43470
43544
|
};
|
|
43471
43545
|
/** The layout job is idempotent; rolling back only forgets it if it has not run yet. */
|
|
43472
|
-
const down$
|
|
43546
|
+
const down$18 = async (db) => {
|
|
43473
43547
|
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY$1} and status = 'pending'`.execute(db);
|
|
43474
43548
|
};
|
|
43475
43549
|
//#endregion
|
|
43476
43550
|
//#region src/db/migrations/20261003140500_change_set_items_site_and_preview_entry.ts
|
|
43477
43551
|
var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE__ */ __exportAll({
|
|
43478
|
-
down: () => down$
|
|
43479
|
-
up: () => up$
|
|
43552
|
+
down: () => down$17,
|
|
43553
|
+
up: () => up$17
|
|
43480
43554
|
});
|
|
43481
43555
|
/**
|
|
43482
43556
|
* Sites, publishing (plan agentic-ecosystem §H, package G5).
|
|
@@ -43487,7 +43561,7 @@ var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE
|
|
|
43487
43561
|
* - `preview_tokens.entry_id` becomes required: a preview token previews one entry of one site. Model-wide
|
|
43488
43562
|
* tokens (short-lived by design) are deleted; whoever needs one opens the entry's preview again.
|
|
43489
43563
|
*/
|
|
43490
|
-
const up$
|
|
43564
|
+
const up$17 = async (db) => {
|
|
43491
43565
|
await sql`alter table change_sets add constraint change_sets_id_site_uq unique (id, site_id)`.execute(db);
|
|
43492
43566
|
await sql`alter table change_set_items add column site_id uuid`.execute(db);
|
|
43493
43567
|
await sql`
|
|
@@ -43503,7 +43577,7 @@ const up$16 = async (db) => {
|
|
|
43503
43577
|
await sql`delete from preview_tokens where entry_id is null`.execute(db);
|
|
43504
43578
|
await sql`alter table preview_tokens alter column entry_id set not null`.execute(db);
|
|
43505
43579
|
};
|
|
43506
|
-
const down$
|
|
43580
|
+
const down$17 = async (db) => {
|
|
43507
43581
|
await sql`alter table preview_tokens alter column entry_id drop not null`.execute(db);
|
|
43508
43582
|
await sql`alter table change_set_items drop constraint change_set_items_entry_site_fk`.execute(db);
|
|
43509
43583
|
await sql`alter table change_set_items drop constraint change_set_items_set_site_fk`.execute(db);
|
|
@@ -43513,8 +43587,8 @@ const down$16 = async (db) => {
|
|
|
43513
43587
|
//#endregion
|
|
43514
43588
|
//#region src/db/migrations/20261003150000_widen_deployment_providers.ts
|
|
43515
43589
|
var _20261003150000_widen_deployment_providers_exports = /* @__PURE__ */ __exportAll({
|
|
43516
|
-
down: () => down$
|
|
43517
|
-
up: () => up$
|
|
43590
|
+
down: () => down$16,
|
|
43591
|
+
up: () => up$16
|
|
43518
43592
|
});
|
|
43519
43593
|
/**
|
|
43520
43594
|
* Deploy adapters (plan agentic-ecosystem §B2): deployment connections may use the Vercel and Netlify
|
|
@@ -43529,11 +43603,11 @@ const replaceCheck = async (db, check) => {
|
|
|
43529
43603
|
check (${check}) not valid`.execute(db);
|
|
43530
43604
|
await sql`alter table deployment_connections validate constraint deployment_connections_provider_check`.execute(db);
|
|
43531
43605
|
};
|
|
43532
|
-
const up$
|
|
43606
|
+
const up$16 = async (db) => {
|
|
43533
43607
|
await replaceCheck(db, WIDENED);
|
|
43534
43608
|
};
|
|
43535
43609
|
/** Refuses while Vercel or Netlify connections exist: a down migration never deletes user data. */
|
|
43536
|
-
const down$
|
|
43610
|
+
const down$16 = async (db) => {
|
|
43537
43611
|
const { rows } = await sql`
|
|
43538
43612
|
select count(*)::text as count from deployment_connections where provider in ('vercel', 'netlify')
|
|
43539
43613
|
`.execute(db);
|
|
@@ -43544,8 +43618,8 @@ const down$15 = async (db) => {
|
|
|
43544
43618
|
//#endregion
|
|
43545
43619
|
//#region src/db/migrations/20261003160000_create_assist_runs.ts
|
|
43546
43620
|
var _20261003160000_create_assist_runs_exports = /* @__PURE__ */ __exportAll({
|
|
43547
|
-
down: () => down$
|
|
43548
|
-
up: () => up$
|
|
43621
|
+
down: () => down$15,
|
|
43622
|
+
up: () => up$15
|
|
43549
43623
|
});
|
|
43550
43624
|
/**
|
|
43551
43625
|
* Editor assists (plan agentic-ecosystem §A0, §I): one `assist_runs` row per assist request (who, which
|
|
@@ -43561,14 +43635,14 @@ const replaceSourceCheck = async (db, check) => {
|
|
|
43561
43635
|
await sql`alter table change_sets add constraint change_sets_source_check check (${check}) not valid`.execute(db);
|
|
43562
43636
|
await sql`alter table change_sets validate constraint change_sets_source_check`.execute(db);
|
|
43563
43637
|
};
|
|
43564
|
-
const up$
|
|
43638
|
+
const up$15 = async (db) => {
|
|
43565
43639
|
await db.schema.createTable("assist_runs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("site_id", "uuid", (col) => col.notNull().references("sites.id").onDelete("cascade")).addColumn("actor_type", "text", (col) => col.notNull()).addColumn("actor_id", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(ACTIONS)).addColumn("rule", "text").addColumn("provider", "text", (col) => col.notNull()).addColumn("model", "text", (col) => col.notNull()).addColumn("input_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("output_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("duration_ms", "integer", (col) => col.notNull().defaultTo(0)).addColumn("status", "text", (col) => col.notNull().check(sql`status in ('queued', 'running', 'succeeded', 'failed')`)).addColumn("error_code", "text").addColumn("job_id", "uuid", (col) => col.references("jobs.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
43566
43640
|
await db.schema.createIndex("assist_runs_site_created_idx").on("assist_runs").columns(["site_id", "created_at"]).execute();
|
|
43567
43641
|
await db.schema.createIndex("assist_runs_created_idx").on("assist_runs").column("created_at").execute();
|
|
43568
43642
|
await replaceSourceCheck(db, SOURCES_WIDENED);
|
|
43569
43643
|
};
|
|
43570
43644
|
/** Refuses while change sets opened by assist exist: a down migration never deletes user data. */
|
|
43571
|
-
const down$
|
|
43645
|
+
const down$15 = async (db) => {
|
|
43572
43646
|
const { rows } = await sql`
|
|
43573
43647
|
select count(*)::text as count from change_sets where source = 'assist'
|
|
43574
43648
|
`.execute(db);
|
|
@@ -43580,8 +43654,8 @@ const down$14 = async (db) => {
|
|
|
43580
43654
|
//#endregion
|
|
43581
43655
|
//#region src/db/migrations/20261003170000_audit_events_seq.ts
|
|
43582
43656
|
var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43583
|
-
down: () => down$
|
|
43584
|
-
up: () => up$
|
|
43657
|
+
down: () => down$14,
|
|
43658
|
+
up: () => up$14
|
|
43585
43659
|
});
|
|
43586
43660
|
/**
|
|
43587
43661
|
* `audit_events.seq`: a monotonic tiebreaker after `occurred_at`, so events written in the same instant (one
|
|
@@ -43590,17 +43664,17 @@ var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43590
43664
|
* are numbered in the order the rewrite reads them. No index: the audit queries filter and sort by
|
|
43591
43665
|
* `occurred_at` first, and `seq` only breaks ties.
|
|
43592
43666
|
*/
|
|
43593
|
-
const up$
|
|
43667
|
+
const up$14 = async (db) => {
|
|
43594
43668
|
await db.schema.alterTable("audit_events").addColumn("seq", "bigint", (col) => col.notNull().generatedAlwaysAsIdentity()).execute();
|
|
43595
43669
|
};
|
|
43596
|
-
const down$
|
|
43670
|
+
const down$14 = async (db) => {
|
|
43597
43671
|
await db.schema.alterTable("audit_events").dropColumn("seq").execute();
|
|
43598
43672
|
};
|
|
43599
43673
|
//#endregion
|
|
43600
43674
|
//#region src/db/migrations/20261004120000_models_site_scope.ts
|
|
43601
43675
|
var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43602
|
-
down: () => down$
|
|
43603
|
-
up: () => up$
|
|
43676
|
+
down: () => down$13,
|
|
43677
|
+
up: () => up$13
|
|
43604
43678
|
});
|
|
43605
43679
|
/**
|
|
43606
43680
|
* Per-site schema (plan site-schema): a definition belongs to one site (`models.site_id`) or is shared by every
|
|
@@ -43613,7 +43687,7 @@ var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43613
43687
|
* over `lower(api_key)` cannot hold. Share or delete them first; then `down` is lossless.
|
|
43614
43688
|
*/
|
|
43615
43689
|
const NIL_UUID$1 = "00000000-0000-0000-0000-000000000000";
|
|
43616
|
-
const up$
|
|
43690
|
+
const up$13 = async (db) => {
|
|
43617
43691
|
await sql`alter table models add column site_id uuid references sites (id)`.execute(db);
|
|
43618
43692
|
await sql`create index models_site_idx on models (site_id)`.execute(db);
|
|
43619
43693
|
await sql`drop index models_api_key_uq`.execute(db);
|
|
@@ -43623,7 +43697,7 @@ const up$12 = async (db) => {
|
|
|
43623
43697
|
`.execute(db);
|
|
43624
43698
|
await sql`alter table schema_drafts add column shared boolean not null default false`.execute(db);
|
|
43625
43699
|
};
|
|
43626
|
-
const down$
|
|
43700
|
+
const down$13 = async (db) => {
|
|
43627
43701
|
const { rows } = await sql`select count(*)::text as count from models where site_id is not null`.execute(db);
|
|
43628
43702
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
43629
43703
|
await sql`alter table schema_drafts drop column shared`.execute(db);
|
|
@@ -43635,8 +43709,8 @@ const down$12 = async (db) => {
|
|
|
43635
43709
|
//#endregion
|
|
43636
43710
|
//#region src/db/migrations/20261004130000_site_seo_settings.ts
|
|
43637
43711
|
var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43638
|
-
down: () => down$
|
|
43639
|
-
up: () => up$
|
|
43712
|
+
down: () => down$12,
|
|
43713
|
+
up: () => up$12
|
|
43640
43714
|
});
|
|
43641
43715
|
/**
|
|
43642
43716
|
* SEO defaults per site (plan seo-fields): `sites.seo_defaults` holds the site's default texts per locale, its
|
|
@@ -43647,7 +43721,7 @@ var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43647
43721
|
* about what a site publishes) gets it; the built-in owner and admin roles get it from `ensureSystemRoles`.
|
|
43648
43722
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43649
43723
|
*/
|
|
43650
|
-
const up$
|
|
43724
|
+
const up$12 = async (db) => {
|
|
43651
43725
|
await sql`alter table sites add column seo_defaults jsonb`.execute(db);
|
|
43652
43726
|
await sql`
|
|
43653
43727
|
with granted as (
|
|
@@ -43662,7 +43736,7 @@ const up$11 = async (db) => {
|
|
|
43662
43736
|
where exists (select 1 from granted)
|
|
43663
43737
|
`.execute(db);
|
|
43664
43738
|
};
|
|
43665
|
-
const down$
|
|
43739
|
+
const down$12 = async (db) => {
|
|
43666
43740
|
await sql`
|
|
43667
43741
|
with revoked as (
|
|
43668
43742
|
delete from admin_role_permissions where action = 'site.settings' returning 1
|
|
@@ -43675,8 +43749,8 @@ const down$11 = async (db) => {
|
|
|
43675
43749
|
//#endregion
|
|
43676
43750
|
//#region src/db/migrations/20261005120000_entry_heads_entry_created_at.ts
|
|
43677
43751
|
var _20261005120000_entry_heads_entry_created_at_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43678
|
-
down: () => down$
|
|
43679
|
-
up: () => up$
|
|
43752
|
+
down: () => down$11,
|
|
43753
|
+
up: () => up$11
|
|
43680
43754
|
});
|
|
43681
43755
|
/**
|
|
43682
43756
|
* Delivery perf 2 (ADR 0001 amendment): the entry's `created_at` on each head (`entry_created_at`), so a list
|
|
@@ -43693,7 +43767,7 @@ const INDEX$2 = "entry_heads_entry_order_idx";
|
|
|
43693
43767
|
const JOB_TYPE = "schema.entryOrderIndex";
|
|
43694
43768
|
const IDEMPOTENCY_KEY = "schema.entryOrderIndex:v1";
|
|
43695
43769
|
const BATCH$1 = 5e3;
|
|
43696
|
-
const up$
|
|
43770
|
+
const up$11 = async (db) => {
|
|
43697
43771
|
await sql`alter table entry_heads add column entry_created_at timestamptz`.execute(db);
|
|
43698
43772
|
for (;;) {
|
|
43699
43773
|
const result = await sql`
|
|
@@ -43718,12 +43792,25 @@ const up$10 = async (db) => {
|
|
|
43718
43792
|
create index ${sql.id(INDEX$2)} on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)
|
|
43719
43793
|
`.execute(db);
|
|
43720
43794
|
};
|
|
43721
|
-
const down$
|
|
43795
|
+
const down$11 = async (db) => {
|
|
43722
43796
|
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
|
|
43723
43797
|
await sql`drop index if exists ${sql.id(INDEX$2)}`.execute(db);
|
|
43724
43798
|
await sql`alter table entry_heads drop column entry_created_at`.execute(db);
|
|
43725
43799
|
};
|
|
43726
43800
|
//#endregion
|
|
43801
|
+
//#region src/db/migrations/20261006120000_system_versions_release.ts
|
|
43802
|
+
var _20261006120000_system_versions_release_exports = /* @__PURE__ */ __exportAll({
|
|
43803
|
+
dialectNeutral: () => true,
|
|
43804
|
+
down: () => down$10,
|
|
43805
|
+
up: () => up$10
|
|
43806
|
+
});
|
|
43807
|
+
const up$10 = async (db) => {
|
|
43808
|
+
await db.schema.alterTable("system_versions").addColumn("release", "text").execute();
|
|
43809
|
+
};
|
|
43810
|
+
const down$10 = async (db) => {
|
|
43811
|
+
await db.schema.alterTable("system_versions").dropColumn("release").execute();
|
|
43812
|
+
};
|
|
43813
|
+
//#endregion
|
|
43727
43814
|
//#region src/db/migrations/mysql/0001_baseline.ts
|
|
43728
43815
|
var _0001_baseline_exports$1 = /* @__PURE__ */ __exportAll({
|
|
43729
43816
|
down: () => down$9,
|
|
@@ -45025,7 +45112,8 @@ const MYSQL_MIGRATIONS = {
|
|
|
45025
45112
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$1,
|
|
45026
45113
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$1,
|
|
45027
45114
|
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1,
|
|
45028
|
-
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$1
|
|
45115
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$1,
|
|
45116
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
45029
45117
|
};
|
|
45030
45118
|
//#endregion
|
|
45031
45119
|
//#region src/db/migrations/sqlite/0001_baseline.ts
|
|
@@ -46348,7 +46436,8 @@ const SQLITE_MIGRATIONS = {
|
|
|
46348
46436
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports,
|
|
46349
46437
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports,
|
|
46350
46438
|
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports,
|
|
46351
|
-
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports
|
|
46439
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports,
|
|
46440
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
46352
46441
|
};
|
|
46353
46442
|
//#endregion
|
|
46354
46443
|
//#region src/db/migrations/index.ts
|
|
@@ -46381,7 +46470,8 @@ const MIGRATIONS = {
|
|
|
46381
46470
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$2,
|
|
46382
46471
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$2,
|
|
46383
46472
|
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2,
|
|
46384
|
-
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$2
|
|
46473
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$2,
|
|
46474
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
46385
46475
|
};
|
|
46386
46476
|
const PROVIDERS = {
|
|
46387
46477
|
postgres: { getMigrations: () => Promise.resolve({ ...MIGRATIONS }) },
|
|
@@ -46684,6 +46774,24 @@ const parseByteRange = (header, size) => {
|
|
|
46684
46774
|
};
|
|
46685
46775
|
};
|
|
46686
46776
|
//#endregion
|
|
46777
|
+
//#region src/media/contentDisposition.ts
|
|
46778
|
+
/**
|
|
46779
|
+
* A Content-Disposition value that names the file safely: an ASCII fallback plus the RFC 5987 UTF-8 form,
|
|
46780
|
+
* so quotes, semicolons or line breaks in a filename can never inject header parameters.
|
|
46781
|
+
*/
|
|
46782
|
+
const contentDisposition = (filename, type = "inline") => {
|
|
46783
|
+
return `${type}; filename="${filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
46784
|
+
};
|
|
46785
|
+
/** Types a browser only ever displays passively: opening one directly cannot run script. */
|
|
46786
|
+
const PASSIVE_INLINE_TYPE = /^(image\/(png|jpeg|gif|webp|avif|bmp|tiff|x-icon|vnd\.microsoft\.icon)|video\/.+|audio\/.+)$/;
|
|
46787
|
+
/**
|
|
46788
|
+
* Content-Disposition to store on an object in a bucket. Public objects can be served straight from the
|
|
46789
|
+
* bucket or a CDN, where Shapio's sandboxing headers are absent; anything that could run as a page there
|
|
46790
|
+
* (SVG, HTML, XML, PDF...) is stored as an attachment, so opening it downloads it. `<img>` tags still show
|
|
46791
|
+
* an SVG, since embedding ignores Content-Disposition.
|
|
46792
|
+
*/
|
|
46793
|
+
const bucketContentDisposition = (contentType) => PASSIVE_INLINE_TYPE.test(contentType.toLowerCase()) ? void 0 : "attachment";
|
|
46794
|
+
//#endregion
|
|
46687
46795
|
//#region src/services/mediaFiles.ts
|
|
46688
46796
|
const notFound = () => new AppError(404, "NOT_FOUND", "Media file not found");
|
|
46689
46797
|
const findFile = async (key) => {
|
|
@@ -47111,6 +47219,16 @@ const insertIfAbsent = async (key, value, trx = db) => {
|
|
|
47111
47219
|
value
|
|
47112
47220
|
}).onConflict((oc) => oc.column("key").doNothing()).returning("key").executeTakeFirst() !== void 0;
|
|
47113
47221
|
};
|
|
47222
|
+
/** Creates or replaces a setting. */
|
|
47223
|
+
const upsertValue = async (key, value, trx = db) => {
|
|
47224
|
+
await trx.insertInto("system_settings").values({
|
|
47225
|
+
key,
|
|
47226
|
+
value
|
|
47227
|
+
}).onConflict((oc) => oc.column("key").doUpdateSet({
|
|
47228
|
+
value,
|
|
47229
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
47230
|
+
})).execute();
|
|
47231
|
+
};
|
|
47114
47232
|
//#endregion
|
|
47115
47233
|
//#region src/services/signingSecret.ts
|
|
47116
47234
|
const SIGNING_SECRET_SETTING = "signing_secret";
|
|
@@ -47616,20 +47734,6 @@ var StartupCheckError = class extends Error {
|
|
|
47616
47734
|
this.name = "StartupCheckError";
|
|
47617
47735
|
}
|
|
47618
47736
|
};
|
|
47619
|
-
/** `host:port/database` (PostgreSQL, MySQL) or the file path (SQLite) from DATABASE_URL, never the credentials. */
|
|
47620
|
-
const describeDatabaseTarget = (connectionString) => {
|
|
47621
|
-
if (dialectOfUrl(connectionString) === "sqlite") {
|
|
47622
|
-
const location = sqliteLocationOfUrl(connectionString);
|
|
47623
|
-
return location.kind === "memory" ? "an in-memory database" : location.path;
|
|
47624
|
-
}
|
|
47625
|
-
try {
|
|
47626
|
-
const url = new URL(connectionString);
|
|
47627
|
-
const defaultPort = dialectOfUrl(connectionString) === "mysql" ? "3306" : "5432";
|
|
47628
|
-
return `${url.hostname}:${url.port || defaultPort}${url.pathname}`;
|
|
47629
|
-
} catch {
|
|
47630
|
-
return "the configured database";
|
|
47631
|
-
}
|
|
47632
|
-
};
|
|
47633
47737
|
const NETWORK_REASONS = {
|
|
47634
47738
|
ECONNREFUSED: "connection refused",
|
|
47635
47739
|
ENOTFOUND: "host not found",
|
|
@@ -49127,6 +49231,45 @@ const createPublishingJobHandlers = (environment) => [
|
|
|
49127
49231
|
];
|
|
49128
49232
|
/** Outbox subscribers: webhook deliveries and deployment triggers follow domain events (ADR 0007). */
|
|
49129
49233
|
const PUBLISHING_OUTBOX_SUBSCRIBERS = [webhookOutboxSubscriber, deploymentOutboxSubscriber];
|
|
49234
|
+
Type.Object({
|
|
49235
|
+
format: Type.Literal(1),
|
|
49236
|
+
publicUrl: Type.String(),
|
|
49237
|
+
basePath: Type.String(),
|
|
49238
|
+
media: Type.Object({
|
|
49239
|
+
publicBaseUrl: Type.Union([Type.String(), Type.Null()]),
|
|
49240
|
+
s3: Type.Union([Type.Object({
|
|
49241
|
+
bucket: Type.String(),
|
|
49242
|
+
region: Type.Union([Type.String(), Type.Null()]),
|
|
49243
|
+
endpoint: Type.Union([Type.String(), Type.Null()]),
|
|
49244
|
+
forcePathStyle: Type.Boolean()
|
|
49245
|
+
}), Type.Null()])
|
|
49246
|
+
})
|
|
49247
|
+
});
|
|
49248
|
+
const toDeliveryDescriptor = ({ server, storage }) => ({
|
|
49249
|
+
format: 1,
|
|
49250
|
+
publicUrl: server.publicUrl,
|
|
49251
|
+
basePath: server.basePath,
|
|
49252
|
+
media: {
|
|
49253
|
+
publicBaseUrl: storage.publicBaseUrl ?? null,
|
|
49254
|
+
s3: storage.s3 ? {
|
|
49255
|
+
bucket: storage.s3.bucket,
|
|
49256
|
+
region: storage.s3.region ?? null,
|
|
49257
|
+
endpoint: storage.s3.endpoint ?? null,
|
|
49258
|
+
forcePathStyle: storage.s3.forcePathStyle
|
|
49259
|
+
} : null
|
|
49260
|
+
}
|
|
49261
|
+
});
|
|
49262
|
+
/**
|
|
49263
|
+
* Called by the server at every start, after migrating: records its release and its delivery descriptor
|
|
49264
|
+
* (rewritten only when it changed), in one transaction.
|
|
49265
|
+
*/
|
|
49266
|
+
const publishServerRelease = async (database, release, source) => {
|
|
49267
|
+
const descriptor = JSON.stringify(toDeliveryDescriptor(source));
|
|
49268
|
+
await database.transaction().execute(async (trx) => {
|
|
49269
|
+
await setRelease(release, trx);
|
|
49270
|
+
if (await findValue("delivery_descriptor", trx) !== descriptor) await upsertValue(DELIVERY_DESCRIPTOR_SETTING, descriptor, trx);
|
|
49271
|
+
});
|
|
49272
|
+
};
|
|
49130
49273
|
//#endregion
|
|
49131
49274
|
//#region src/tls/certificates.ts
|
|
49132
49275
|
/**
|
|
@@ -49287,7 +49430,7 @@ const logStartupSummary = (config, logger) => {
|
|
|
49287
49430
|
};
|
|
49288
49431
|
logger.info(summary, `Shapio ${summary.version} running at ${summary.url} (mode ${summary.mode}, storage ${summary.storage}, worker ${summary.worker}, tls ${summary.tls})`);
|
|
49289
49432
|
};
|
|
49290
|
-
const createConfiguredWorker = (config, db, logger, signingSecret, extensions) => {
|
|
49433
|
+
const createConfiguredWorker = async (config, db, logger, signingSecret, extensions) => {
|
|
49291
49434
|
const publishing = createPublishingJobEnvironment(createPublishingRuntime({
|
|
49292
49435
|
db,
|
|
49293
49436
|
signingSecret,
|
|
@@ -49300,7 +49443,7 @@ const createConfiguredWorker = (config, db, logger, signingSecret, extensions) =
|
|
|
49300
49443
|
urls: createUrlBuilder(config.server),
|
|
49301
49444
|
transport: createEmailTransport(config.email, logger.child({ component: "email" }))
|
|
49302
49445
|
};
|
|
49303
|
-
const storage = createMediaStorage(config.storage, { urls: createUrlBuilder(config.server) });
|
|
49446
|
+
const storage = await createMediaStorage(config.storage, { urls: createUrlBuilder(config.server) });
|
|
49304
49447
|
return createWorker({
|
|
49305
49448
|
db,
|
|
49306
49449
|
handlers: createJobHandlers([
|
|
@@ -49381,6 +49524,7 @@ const startServer = async (config) => {
|
|
|
49381
49524
|
log: logger
|
|
49382
49525
|
});
|
|
49383
49526
|
const signingSecret = await resolveSigningSecret(db, config.sessionSecret, logger);
|
|
49527
|
+
await publishServerRelease(db, SHAPIO_VERSION, config);
|
|
49384
49528
|
extensions = await startExtensions(config, db, logger, project);
|
|
49385
49529
|
const app = await buildApp(config, {
|
|
49386
49530
|
db,
|
|
@@ -49390,7 +49534,7 @@ const startServer = async (config) => {
|
|
|
49390
49534
|
schemaListen: config.schema.listen,
|
|
49391
49535
|
...tls.initial ? { tls: tls.initial } : {}
|
|
49392
49536
|
});
|
|
49393
|
-
const worker = config.worker.mode === "inline" ? createConfiguredWorker(config, db, logger, signingSecret, extensions) : void 0;
|
|
49537
|
+
const worker = config.worker.mode === "inline" ? await createConfiguredWorker(config, db, logger, signingSecret, extensions) : void 0;
|
|
49394
49538
|
const tlsRuntime = tls;
|
|
49395
49539
|
const extensionRuntime = extensions;
|
|
49396
49540
|
let closing;
|
|
@@ -49464,7 +49608,7 @@ const startDedicatedWorker = async (config) => {
|
|
|
49464
49608
|
}
|
|
49465
49609
|
const signingSecret = await resolveSigningSecret(db, config.sessionSecret, logger);
|
|
49466
49610
|
const extensions = await startExtensions(config, db, logger, project);
|
|
49467
|
-
const worker = createConfiguredWorker(config, db, logger, signingSecret, extensions);
|
|
49611
|
+
const worker = await createConfiguredWorker(config, db, logger, signingSecret, extensions);
|
|
49468
49612
|
await ensureRetentionScheduled(db);
|
|
49469
49613
|
await ensureHealthSweepScheduled(db);
|
|
49470
49614
|
worker.start();
|
|
@@ -49478,6 +49622,6 @@ const startDedicatedWorker = async (config) => {
|
|
|
49478
49622
|
return worker;
|
|
49479
49623
|
};
|
|
49480
49624
|
//#endregion
|
|
49481
|
-
export {
|
|
49625
|
+
export { parseLockFile as $, buildEditorManifest as A, RESERVED_DEFINITION_API_KEYS as B, loopbackUrl as C, BEFORE_EVENTS as D, AFTER_EVENTS as E, lockById$5 as F, THEME_VARIANTS as G, RESERVED_TYPE_NAMES as H, update$8 as I, hashDefinition as J, checkThemeContrast as K, STORAGE_DRIVERS$1 as L, isValidStorageKey as M, listForAssets as N, EXTENSION_JOB_PREFIX as O, listLiveByDriver as P, parseDefinition as Q, describeError as R, createUrlBuilder as S, loadProjectConfig as T, SCHEMA_KIND_DIRECTORIES as U, RESERVED_FIELD_API_KEYS as V, SITES_DIRECTORY as W, lockEntriesForSite as X, lockCoversSite as Y, mergePulledLock as Z, listAllSites as _, loadConfig as a, toTypeName as at, generateToken as b, bucketContentDisposition as c, withSiteCovered as ct, migrateToLatest as d, createStableId as dt, schemaFilePath as et, SHAPIO_VERSION as f, validateRichText as ft, createSite$1 as g, ObjectNotFoundError as h, assertDatabaseReachable as i, suggestPlural as it, encodeKeyPath as j, EXTENSION_ROUTE_ROOT as k, contentDisposition as l, RICHTEXT_FORMAT as lt, InvalidStorageKeyError as m, sqliteDriverOf as mt, startServer as n, serializeDefinition as nt, loadEnvFileIfPresent as o, upgradeLockFile as ot, createMediaStorage as p, createDb as pt, foldApiKey as q, copyObjectVerified as r, serializeLockFile as rt, buildApp as s, validateSchema as st, startDedicatedWorker as t, scopeDirectory as tt, getPendingMigrations as u, SAFE_LINK_PATTERN as ut, createAdminDirect as v, SITE_KEY_PATTERN as w, createLogger as x, SYSTEM_CLI_ACTOR as y, LOCK_FILE_PATH as z };
|
|
49482
49626
|
|
|
49483
|
-
//# sourceMappingURL=worker-
|
|
49627
|
+
//# sourceMappingURL=worker-BU-V4qbH.js.map
|