katecms 0.0.0-stage → 1.0.0-beta.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/README.md +5 -3
- package/dist/module.d.mts +93 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +579 -0
- package/dist/runtime/assets/img/katecms-logo.svg +17 -0
- package/dist/runtime/composables/getCollection.d.ts +62 -0
- package/dist/runtime/composables/getCollection.js +167 -0
- package/dist/runtime/composables/getOptions.d.ts +22 -0
- package/dist/runtime/composables/getOptions.js +22 -0
- package/dist/runtime/composables/getQueryCollection.d.ts +95 -0
- package/dist/runtime/composables/getQueryCollection.js +199 -0
- package/dist/runtime/composables/getTerms.d.ts +79 -0
- package/dist/runtime/composables/getTerms.js +148 -0
- package/dist/runtime/composables/useAuth.d.ts +14 -0
- package/dist/runtime/composables/useAuth.js +33 -0
- package/dist/runtime/composables/useAuthCooldown.d.ts +6 -0
- package/dist/runtime/composables/useAuthCooldown.js +44 -0
- package/dist/runtime/composables/useCmsPaths.d.ts +15 -0
- package/dist/runtime/composables/useCmsPaths.js +21 -0
- package/dist/runtime/composables/useKateCmsExtensions.d.ts +49 -0
- package/dist/runtime/composables/useKateCmsExtensions.js +29 -0
- package/dist/runtime/composables/useMediaLibrary.d.ts +84 -0
- package/dist/runtime/composables/useMediaLibrary.js +140 -0
- package/dist/runtime/composables/useMenuAccess.d.ts +8 -0
- package/dist/runtime/composables/useMenuAccess.js +16 -0
- package/dist/runtime/internal/admin/AdminHeader.d.vue.ts +24 -0
- package/dist/runtime/internal/admin/AdminHeader.vue +91 -0
- package/dist/runtime/internal/admin/AdminHeader.vue.d.ts +24 -0
- package/dist/runtime/internal/admin/AppMenu.d.vue.ts +3 -0
- package/dist/runtime/internal/admin/AppMenu.vue +244 -0
- package/dist/runtime/internal/admin/AppMenu.vue.d.ts +3 -0
- package/dist/runtime/internal/admin/AppUser.d.vue.ts +3 -0
- package/dist/runtime/internal/admin/AppUser.vue +227 -0
- package/dist/runtime/internal/admin/AppUser.vue.d.ts +3 -0
- package/dist/runtime/internal/admin/CollectionEntryForm.d.vue.ts +41 -0
- package/dist/runtime/internal/admin/CollectionEntryForm.vue +916 -0
- package/dist/runtime/internal/admin/CollectionEntryForm.vue.d.ts +41 -0
- package/dist/runtime/internal/admin/ConfirmDialog.d.vue.ts +28 -0
- package/dist/runtime/internal/admin/ConfirmDialog.vue +136 -0
- package/dist/runtime/internal/admin/ConfirmDialog.vue.d.ts +28 -0
- package/dist/runtime/internal/admin/DictionaryTermForm.d.vue.ts +33 -0
- package/dist/runtime/internal/admin/DictionaryTermForm.vue +328 -0
- package/dist/runtime/internal/admin/DictionaryTermForm.vue.d.ts +33 -0
- package/dist/runtime/internal/admin/EntryRevisions.d.vue.ts +15 -0
- package/dist/runtime/internal/admin/EntryRevisions.vue +125 -0
- package/dist/runtime/internal/admin/EntryRevisions.vue.d.ts +15 -0
- package/dist/runtime/internal/admin/MediaLibraryFilters.d.vue.ts +16 -0
- package/dist/runtime/internal/admin/MediaLibraryFilters.vue +153 -0
- package/dist/runtime/internal/admin/MediaLibraryFilters.vue.d.ts +16 -0
- package/dist/runtime/internal/admin/MediaLibraryModal.d.vue.ts +65 -0
- package/dist/runtime/internal/admin/MediaLibraryModal.vue +451 -0
- package/dist/runtime/internal/admin/MediaLibraryModal.vue.d.ts +65 -0
- package/dist/runtime/internal/admin/OptionPageForm.d.vue.ts +26 -0
- package/dist/runtime/internal/admin/OptionPageForm.vue +222 -0
- package/dist/runtime/internal/admin/OptionPageForm.vue.d.ts +26 -0
- package/dist/runtime/internal/admin/UserAvatarField.d.vue.ts +12 -0
- package/dist/runtime/internal/admin/UserAvatarField.vue +305 -0
- package/dist/runtime/internal/admin/UserAvatarField.vue.d.ts +12 -0
- package/dist/runtime/internal/fields/AccordionField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/AccordionField.vue +220 -0
- package/dist/runtime/internal/fields/AccordionField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/ButtonGroupField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/ButtonGroupField.vue +109 -0
- package/dist/runtime/internal/fields/ButtonGroupField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/CheckboxField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/CheckboxField.vue +190 -0
- package/dist/runtime/internal/fields/CheckboxField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/ColorPickerField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/ColorPickerField.vue +791 -0
- package/dist/runtime/internal/fields/ColorPickerField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/DatePickerField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/DatePickerField.vue +26 -0
- package/dist/runtime/internal/fields/DatePickerField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/DateTimePickerField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/DateTimePickerField.vue +26 -0
- package/dist/runtime/internal/fields/DateTimePickerField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/DictionaryField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/DictionaryField.vue +345 -0
- package/dist/runtime/internal/fields/DictionaryField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/EmailField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/EmailField.vue +21 -0
- package/dist/runtime/internal/fields/EmailField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/FileField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/FileField.vue +408 -0
- package/dist/runtime/internal/fields/FileField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/FlexibleContentField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/FlexibleContentField.vue +1114 -0
- package/dist/runtime/internal/fields/FlexibleContentField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/GalleryField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/GalleryField.vue +584 -0
- package/dist/runtime/internal/fields/GalleryField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/GroupField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/GroupField.vue +111 -0
- package/dist/runtime/internal/fields/GroupField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/ImageField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/ImageField.vue +638 -0
- package/dist/runtime/internal/fields/ImageField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/ImageSelectField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/ImageSelectField.vue +179 -0
- package/dist/runtime/internal/fields/ImageSelectField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/LinkField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/LinkField.vue +303 -0
- package/dist/runtime/internal/fields/LinkField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/NumberField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/NumberField.vue +25 -0
- package/dist/runtime/internal/fields/NumberField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/OembedField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/OembedField.vue +161 -0
- package/dist/runtime/internal/fields/OembedField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/OpenStreetMapField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/OpenStreetMapField.vue +1502 -0
- package/dist/runtime/internal/fields/OpenStreetMapField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/PageLinkField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/PageLinkField.vue +153 -0
- package/dist/runtime/internal/fields/PageLinkField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/PasswordField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/PasswordField.vue +108 -0
- package/dist/runtime/internal/fields/PasswordField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/PostObjectField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/PostObjectField.vue +151 -0
- package/dist/runtime/internal/fields/PostObjectField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/RadioField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/RadioField.vue +163 -0
- package/dist/runtime/internal/fields/RadioField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/RangeField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/RangeField.vue +48 -0
- package/dist/runtime/internal/fields/RangeField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/RelationshipField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/RelationshipField.vue +525 -0
- package/dist/runtime/internal/fields/RelationshipField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/RepeaterField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/RepeaterField.vue +856 -0
- package/dist/runtime/internal/fields/RepeaterField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/SelectField.d.vue.ts +21 -0
- package/dist/runtime/internal/fields/SelectField.vue +101 -0
- package/dist/runtime/internal/fields/SelectField.vue.d.ts +21 -0
- package/dist/runtime/internal/fields/SeparatorField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/SeparatorField.vue +63 -0
- package/dist/runtime/internal/fields/SeparatorField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/TabField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/TabField.vue +210 -0
- package/dist/runtime/internal/fields/TabField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/TextField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/TextField.vue +22 -0
- package/dist/runtime/internal/fields/TextField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/TextareaField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/TextareaField.vue +62 -0
- package/dist/runtime/internal/fields/TextareaField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/TimePickerField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/TimePickerField.vue +24 -0
- package/dist/runtime/internal/fields/TimePickerField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/TrueFalseField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/TrueFalseField.vue +151 -0
- package/dist/runtime/internal/fields/TrueFalseField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/UrlField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/UrlField.vue +21 -0
- package/dist/runtime/internal/fields/UrlField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/UserField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/UserField.vue +164 -0
- package/dist/runtime/internal/fields/UserField.vue.d.ts +0 -0
- package/dist/runtime/internal/fields/WysiwygField.d.vue.ts +0 -0
- package/dist/runtime/internal/fields/WysiwygField.vue +971 -0
- package/dist/runtime/internal/fields/WysiwygField.vue.d.ts +0 -0
- package/dist/runtime/internal/parts/DateTimeControl.d.vue.ts +90 -0
- package/dist/runtime/internal/parts/DateTimeControl.vue +568 -0
- package/dist/runtime/internal/parts/DateTimeControl.vue.d.ts +90 -0
- package/dist/runtime/internal/parts/FieldAddon.d.vue.ts +0 -0
- package/dist/runtime/internal/parts/FieldAddon.vue +43 -0
- package/dist/runtime/internal/parts/FieldAddon.vue.d.ts +0 -0
- package/dist/runtime/internal/parts/FieldInstructions.d.vue.ts +0 -0
- package/dist/runtime/internal/parts/FieldInstructions.vue +24 -0
- package/dist/runtime/internal/parts/FieldInstructions.vue.d.ts +0 -0
- package/dist/runtime/internal/parts/FieldLabel.d.vue.ts +0 -0
- package/dist/runtime/internal/parts/FieldLabel.vue +40 -0
- package/dist/runtime/internal/parts/FieldLabel.vue.d.ts +0 -0
- package/dist/runtime/internal/parts/FieldWrapper.d.vue.ts +0 -0
- package/dist/runtime/internal/parts/FieldWrapper.vue +47 -0
- package/dist/runtime/internal/parts/FieldWrapper.vue.d.ts +0 -0
- package/dist/runtime/internal/parts/ImageCropper.d.vue.ts +17 -0
- package/dist/runtime/internal/parts/ImageCropper.vue +363 -0
- package/dist/runtime/internal/parts/ImageCropper.vue.d.ts +17 -0
- package/dist/runtime/internal/parts/InputControl.d.vue.ts +0 -0
- package/dist/runtime/internal/parts/InputControl.vue +163 -0
- package/dist/runtime/internal/parts/InputControl.vue.d.ts +0 -0
- package/dist/runtime/internal/ui/UiButton.d.vue.ts +35 -0
- package/dist/runtime/internal/ui/UiButton.vue +204 -0
- package/dist/runtime/internal/ui/UiButton.vue.d.ts +35 -0
- package/dist/runtime/internal/ui/UiNotice.d.vue.ts +40 -0
- package/dist/runtime/internal/ui/UiNotice.vue +253 -0
- package/dist/runtime/internal/ui/UiNotice.vue.d.ts +40 -0
- package/dist/runtime/internal/ui/UiSelect.d.vue.ts +61 -0
- package/dist/runtime/internal/ui/UiSelect.vue +1212 -0
- package/dist/runtime/internal/ui/UiSelect.vue.d.ts +61 -0
- package/dist/runtime/middleware/auth-guard.d.ts +9 -0
- package/dist/runtime/middleware/auth-guard.js +37 -0
- package/dist/runtime/pages/admin/collections/[id].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/collections/[id].vue +423 -0
- package/dist/runtime/pages/admin/collections/[id].vue.d.ts +3 -0
- package/dist/runtime/pages/admin/collections/create.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/collections/create.vue +179 -0
- package/dist/runtime/pages/admin/collections/create.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/collections/index.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/collections/index.vue +828 -0
- package/dist/runtime/pages/admin/collections/index.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/demo-fields.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/demo-fields.vue +1327 -0
- package/dist/runtime/pages/admin/demo-fields.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/dictionaries/[id].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/dictionaries/[id].vue +213 -0
- package/dist/runtime/pages/admin/dictionaries/[id].vue.d.ts +3 -0
- package/dist/runtime/pages/admin/dictionaries/index.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/dictionaries/index.vue +387 -0
- package/dist/runtime/pages/admin/dictionaries/index.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/index.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/index.vue +138 -0
- package/dist/runtime/pages/admin/index.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/login.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/login.vue +373 -0
- package/dist/runtime/pages/admin/login.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/media.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/media.vue +1054 -0
- package/dist/runtime/pages/admin/media.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/options/[page].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/options/[page].vue +151 -0
- package/dist/runtime/pages/admin/options/[page].vue.d.ts +3 -0
- package/dist/runtime/pages/admin/register.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/register.vue +391 -0
- package/dist/runtime/pages/admin/register.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/users/[id].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/users/[id].vue +337 -0
- package/dist/runtime/pages/admin/users/[id].vue.d.ts +3 -0
- package/dist/runtime/pages/admin/users/create.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/users/create.vue +201 -0
- package/dist/runtime/pages/admin/users/create.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/users/index.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/users/index.vue +329 -0
- package/dist/runtime/pages/admin/users/index.vue.d.ts +3 -0
- package/dist/runtime/pages/index.d.vue.ts +3 -0
- package/dist/runtime/pages/index.vue +399 -0
- package/dist/runtime/pages/index.vue.d.ts +3 -0
- package/dist/runtime/server/api/_katecms/auth/login.post.d.ts +5 -0
- package/dist/runtime/server/api/_katecms/auth/login.post.js +44 -0
- package/dist/runtime/server/api/_katecms/auth/logout.post.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/auth/logout.post.js +6 -0
- package/dist/runtime/server/api/_katecms/auth/register.post.d.ts +19 -0
- package/dist/runtime/server/api/_katecms/auth/register.post.js +83 -0
- package/dist/runtime/server/api/_katecms/auth/state.get.d.ts +9 -0
- package/dist/runtime/server/api/_katecms/auth/state.get.js +12 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].delete.d.ts +15 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].delete.js +59 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].get.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].get.js +50 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].patch.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/[id].patch.js +130 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/revisions/[revisionId].post.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/revisions/[revisionId].post.js +123 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/revisions.get.d.ts +6 -0
- package/dist/runtime/server/api/_katecms/collections/[collection]/revisions.get.js +31 -0
- package/dist/runtime/server/api/_katecms/collections/[collection].get.d.ts +8 -0
- package/dist/runtime/server/api/_katecms/collections/[collection].get.js +135 -0
- package/dist/runtime/server/api/_katecms/collections/[collection].post.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/collections/[collection].post.js +91 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].delete.d.ts +7 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].delete.js +31 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].get.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].get.js +45 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].patch.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].patch.js +83 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary].get.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary].get.js +38 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary].post.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/dictionaries/[dictionary].post.js +64 -0
- package/dist/runtime/server/api/_katecms/media/[id]/regenerate.post.d.ts +8 -0
- package/dist/runtime/server/api/_katecms/media/[id]/regenerate.post.js +30 -0
- package/dist/runtime/server/api/_katecms/media/[id].delete.d.ts +7 -0
- package/dist/runtime/server/api/_katecms/media/[id].delete.js +22 -0
- package/dist/runtime/server/api/_katecms/media/[id].get.d.ts +9 -0
- package/dist/runtime/server/api/_katecms/media/[id].get.js +17 -0
- package/dist/runtime/server/api/_katecms/media/[id].patch.d.ts +6 -0
- package/dist/runtime/server/api/_katecms/media/[id].patch.js +48 -0
- package/dist/runtime/server/api/_katecms/media/index.get.d.ts +5 -0
- package/dist/runtime/server/api/_katecms/media/index.get.js +75 -0
- package/dist/runtime/server/api/_katecms/media/index.post.d.ts +9 -0
- package/dist/runtime/server/api/_katecms/media/index.post.js +84 -0
- package/dist/runtime/server/api/_katecms/media/regenerate.post.d.ts +7 -0
- package/dist/runtime/server/api/_katecms/media/regenerate.post.js +30 -0
- package/dist/runtime/server/api/_katecms/menu.get.d.ts +8 -0
- package/dist/runtime/server/api/_katecms/menu.get.js +14 -0
- package/dist/runtime/server/api/_katecms/oembed.get.d.ts +2 -0
- package/dist/runtime/server/api/_katecms/oembed.get.js +53 -0
- package/dist/runtime/server/api/_katecms/options/[page].get.d.ts +7 -0
- package/dist/runtime/server/api/_katecms/options/[page].get.js +28 -0
- package/dist/runtime/server/api/_katecms/options/[page].patch.d.ts +4 -0
- package/dist/runtime/server/api/_katecms/options/[page].patch.js +36 -0
- package/dist/runtime/server/api/_katecms/query.get.d.ts +24 -0
- package/dist/runtime/server/api/_katecms/query.get.js +270 -0
- package/dist/runtime/server/api/_katecms/relation/entries.get.d.ts +17 -0
- package/dist/runtime/server/api/_katecms/relation/entries.get.js +74 -0
- package/dist/runtime/server/api/_katecms/relation/terms.get.d.ts +17 -0
- package/dist/runtime/server/api/_katecms/relation/terms.get.js +51 -0
- package/dist/runtime/server/api/_katecms/resolve.get.d.ts +17 -0
- package/dist/runtime/server/api/_katecms/resolve.get.js +116 -0
- package/dist/runtime/server/api/_katecms/terms.get.d.ts +20 -0
- package/dist/runtime/server/api/_katecms/terms.get.js +201 -0
- package/dist/runtime/server/api/_katecms/users/[id].delete.d.ts +11 -0
- package/dist/runtime/server/api/_katecms/users/[id].delete.js +45 -0
- package/dist/runtime/server/api/_katecms/users/[id].patch.d.ts +19 -0
- package/dist/runtime/server/api/_katecms/users/[id].patch.js +129 -0
- package/dist/runtime/server/api/_katecms/users.get.d.ts +10 -0
- package/dist/runtime/server/api/_katecms/users.get.js +34 -0
- package/dist/runtime/server/api/_katecms/users.post.d.ts +10 -0
- package/dist/runtime/server/api/_katecms/users.post.js +62 -0
- package/dist/runtime/server/api/uploads/[...key].get.d.ts +3 -0
- package/dist/runtime/server/api/uploads/[...key].get.js +28 -0
- package/dist/runtime/server/kit.d.ts +5 -0
- package/dist/runtime/server/kit.js +5 -0
- package/dist/runtime/server/middleware/admin-headers.d.ts +3 -0
- package/dist/runtime/server/middleware/admin-headers.js +12 -0
- package/dist/runtime/server/plugins/auto-migrate.d.ts +2 -0
- package/dist/runtime/server/plugins/auto-migrate.js +45 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/auth-attempts.d.ts +22 -0
- package/dist/runtime/server/utils/auth-attempts.js +42 -0
- package/dist/runtime/server/utils/auth-rate-limit.d.ts +11 -0
- package/dist/runtime/server/utils/auth-rate-limit.js +36 -0
- package/dist/runtime/server/utils/client-address.d.ts +16 -0
- package/dist/runtime/server/utils/client-address.js +23 -0
- package/dist/runtime/server/utils/collections.d.ts +42 -0
- package/dist/runtime/server/utils/collections.js +300 -0
- package/dist/runtime/server/utils/db.d.ts +3 -0
- package/dist/runtime/server/utils/db.js +25 -0
- package/dist/runtime/server/utils/dictionaries.d.ts +53 -0
- package/dist/runtime/server/utils/dictionaries.js +301 -0
- package/dist/runtime/server/utils/media-list.d.ts +18 -0
- package/dist/runtime/server/utils/media-list.js +84 -0
- package/dist/runtime/server/utils/media.d.ts +12 -0
- package/dist/runtime/server/utils/media.js +77 -0
- package/dist/runtime/server/utils/oembed-fetch.d.ts +4 -0
- package/dist/runtime/server/utils/oembed-fetch.js +142 -0
- package/dist/runtime/server/utils/options.d.ts +17 -0
- package/dist/runtime/server/utils/options.js +66 -0
- package/dist/runtime/server/utils/permissions.d.ts +13 -0
- package/dist/runtime/server/utils/permissions.js +20 -0
- package/dist/runtime/server/utils/public-fields-query.d.ts +6 -0
- package/dist/runtime/server/utils/public-fields-query.js +8 -0
- package/dist/runtime/server/utils/public-term.d.ts +8 -0
- package/dist/runtime/server/utils/public-term.js +80 -0
- package/dist/runtime/server/utils/regenerate.d.ts +12 -0
- package/dist/runtime/server/utils/regenerate.js +40 -0
- package/dist/runtime/server/utils/relation.d.ts +18 -0
- package/dist/runtime/server/utils/relation.js +77 -0
- package/dist/runtime/server/utils/request-address.d.ts +6 -0
- package/dist/runtime/server/utils/request-address.js +10 -0
- package/dist/runtime/server/utils/revisions.d.ts +20 -0
- package/dist/runtime/server/utils/revisions.js +172 -0
- package/dist/runtime/server/utils/session.d.ts +18 -0
- package/dist/runtime/server/utils/session.js +71 -0
- package/dist/runtime/server/utils/upload-headers.d.ts +2 -0
- package/dist/runtime/server/utils/upload-headers.js +30 -0
- package/dist/runtime/server/utils/users.d.ts +13 -0
- package/dist/runtime/server/utils/users.js +47 -0
- package/dist/runtime/utils/admin-users.d.ts +20 -0
- package/dist/runtime/utils/admin-users.js +19 -0
- package/dist/runtime/utils/auth-error.d.ts +6 -0
- package/dist/runtime/utils/auth-error.js +11 -0
- package/dist/runtime/utils/cms-paths.d.ts +5 -0
- package/dist/runtime/utils/cms-paths.js +13 -0
- package/dist/runtime/utils/collections.d.ts +176 -0
- package/dist/runtime/utils/collections.js +30 -0
- package/dist/runtime/utils/dictionaries.d.ts +65 -0
- package/dist/runtime/utils/dictionaries.js +14 -0
- package/dist/runtime/utils/field-errors.d.ts +4 -0
- package/dist/runtime/utils/field-errors.js +9 -0
- package/dist/runtime/utils/field-values.d.ts +19 -0
- package/dist/runtime/utils/field-values.js +82 -0
- package/dist/runtime/utils/helpers.d.ts +4 -0
- package/dist/runtime/utils/helpers.js +20 -0
- package/dist/runtime/utils/media-library-query.d.ts +54 -0
- package/dist/runtime/utils/media-library-query.js +190 -0
- package/dist/runtime/utils/options.d.ts +19 -0
- package/dist/runtime/utils/options.js +3 -0
- package/dist/runtime/utils/preview.d.ts +22 -0
- package/dist/runtime/utils/preview.js +33 -0
- package/dist/runtime/utils/relation.d.ts +33 -0
- package/dist/runtime/utils/relation.js +23 -0
- package/dist/runtime/utils/slugify.d.ts +2 -0
- package/dist/runtime/utils/slugify.js +4 -0
- package/dist/types.d.mts +7 -0
- package/package.json +84 -3
- package/src/admin-components.ts +124 -0
- package/src/module.ts +592 -0
- package/src/runtime/assets/img/katecms-logo.svg +17 -0
- package/src/runtime/assets/styles/_functions.scss +70 -0
- package/src/runtime/assets/styles/_index.scss +3 -0
- package/src/runtime/assets/styles/_mixins.scss +256 -0
- package/src/runtime/assets/styles/_variables.scss +35 -0
- package/src/runtime/composables/getCollection.ts +253 -0
- package/src/runtime/composables/getOptions.ts +42 -0
- package/src/runtime/composables/getQueryCollection.ts +325 -0
- package/src/runtime/composables/getTerms.ts +231 -0
- package/src/runtime/composables/useAuth.ts +43 -0
- package/src/runtime/composables/useAuthCooldown.ts +52 -0
- package/src/runtime/composables/useCmsPaths.ts +24 -0
- package/src/runtime/composables/useKateCmsExtensions.ts +81 -0
- package/src/runtime/composables/useMediaLibrary.ts +168 -0
- package/src/runtime/composables/useMenuAccess.ts +25 -0
- package/src/runtime/internal/admin/AdminHeader.vue +106 -0
- package/src/runtime/internal/admin/AppMenu.vue +290 -0
- package/src/runtime/internal/admin/AppUser.vue +260 -0
- package/src/runtime/internal/admin/CollectionEntryForm.vue +1044 -0
- package/src/runtime/internal/admin/ConfirmDialog.vue +164 -0
- package/src/runtime/internal/admin/DictionaryTermForm.vue +400 -0
- package/src/runtime/internal/admin/EntryRevisions.vue +145 -0
- package/src/runtime/internal/admin/MediaLibraryFilters.vue +186 -0
- package/src/runtime/internal/admin/MediaLibraryModal.vue +526 -0
- package/src/runtime/internal/admin/OptionPageForm.vue +277 -0
- package/src/runtime/internal/admin/UserAvatarField.vue +337 -0
- package/src/runtime/internal/fields/AccordionField.vue +258 -0
- package/src/runtime/internal/fields/ButtonGroupField.vue +144 -0
- package/src/runtime/internal/fields/CheckboxField.vue +228 -0
- package/src/runtime/internal/fields/ColorPickerField.vue +926 -0
- package/src/runtime/internal/fields/DatePickerField.vue +33 -0
- package/src/runtime/internal/fields/DateTimePickerField.vue +33 -0
- package/src/runtime/internal/fields/DictionaryField.vue +403 -0
- package/src/runtime/internal/fields/EmailField.vue +28 -0
- package/src/runtime/internal/fields/FileField.vue +480 -0
- package/src/runtime/internal/fields/FlexibleContentField.vue +1296 -0
- package/src/runtime/internal/fields/GalleryField.vue +677 -0
- package/src/runtime/internal/fields/GroupField.vue +136 -0
- package/src/runtime/internal/fields/ImageField.vue +730 -0
- package/src/runtime/internal/fields/ImageSelectField.vue +213 -0
- package/src/runtime/internal/fields/LinkField.vue +338 -0
- package/src/runtime/internal/fields/NumberField.vue +32 -0
- package/src/runtime/internal/fields/OembedField.vue +187 -0
- package/src/runtime/internal/fields/OpenStreetMapField.vue +1716 -0
- package/src/runtime/internal/fields/PageLinkField.vue +186 -0
- package/src/runtime/internal/fields/PasswordField.vue +127 -0
- package/src/runtime/internal/fields/PostObjectField.vue +187 -0
- package/src/runtime/internal/fields/RadioField.vue +195 -0
- package/src/runtime/internal/fields/RangeField.vue +62 -0
- package/src/runtime/internal/fields/RelationshipField.vue +604 -0
- package/src/runtime/internal/fields/RepeaterField.vue +999 -0
- package/src/runtime/internal/fields/SelectField.vue +118 -0
- package/src/runtime/internal/fields/SeparatorField.vue +77 -0
- package/src/runtime/internal/fields/TabField.vue +249 -0
- package/src/runtime/internal/fields/TextField.vue +29 -0
- package/src/runtime/internal/fields/TextareaField.vue +61 -0
- package/src/runtime/internal/fields/TimePickerField.vue +31 -0
- package/src/runtime/internal/fields/TrueFalseField.vue +181 -0
- package/src/runtime/internal/fields/UrlField.vue +28 -0
- package/src/runtime/internal/fields/UserField.vue +203 -0
- package/src/runtime/internal/fields/WysiwygField.vue +1061 -0
- package/src/runtime/internal/parts/DateTimeControl.vue +657 -0
- package/src/runtime/internal/parts/FieldAddon.vue +60 -0
- package/src/runtime/internal/parts/FieldInstructions.vue +38 -0
- package/src/runtime/internal/parts/FieldLabel.vue +56 -0
- package/src/runtime/internal/parts/FieldWrapper.vue +63 -0
- package/src/runtime/internal/parts/ImageCropper.vue +415 -0
- package/src/runtime/internal/parts/InputControl.vue +189 -0
- package/src/runtime/internal/ui/UiButton.vue +274 -0
- package/src/runtime/internal/ui/UiNotice.vue +302 -0
- package/src/runtime/internal/ui/UiSelect.vue +1420 -0
- package/src/runtime/middleware/auth-guard.ts +52 -0
- package/src/runtime/pages/admin/collections/[id].vue +486 -0
- package/src/runtime/pages/admin/collections/create.vue +208 -0
- package/src/runtime/pages/admin/collections/index.vue +955 -0
- package/src/runtime/pages/admin/demo-fields.vue +1354 -0
- package/src/runtime/pages/admin/dictionaries/[id].vue +246 -0
- package/src/runtime/pages/admin/dictionaries/index.vue +438 -0
- package/src/runtime/pages/admin/index.vue +155 -0
- package/src/runtime/pages/admin/login.vue +159 -0
- package/src/runtime/pages/admin/media.vue +1176 -0
- package/src/runtime/pages/admin/options/[page].vue +174 -0
- package/src/runtime/pages/admin/register.vue +179 -0
- package/src/runtime/pages/admin/users/[id].vue +382 -0
- package/src/runtime/pages/admin/users/create.vue +232 -0
- package/src/runtime/pages/admin/users/index.vue +374 -0
- package/src/runtime/pages/index.vue +265 -0
- package/src/runtime/server/api/_katecms/auth/login.post.ts +62 -0
- package/src/runtime/server/api/_katecms/auth/logout.post.ts +7 -0
- package/src/runtime/server/api/_katecms/auth/register.post.ts +112 -0
- package/src/runtime/server/api/_katecms/auth/state.get.ts +19 -0
- package/src/runtime/server/api/_katecms/collections/[collection]/[id].delete.ts +77 -0
- package/src/runtime/server/api/_katecms/collections/[collection]/[id].get.ts +57 -0
- package/src/runtime/server/api/_katecms/collections/[collection]/[id].patch.ts +157 -0
- package/src/runtime/server/api/_katecms/collections/[collection]/revisions/[revisionId].post.ts +133 -0
- package/src/runtime/server/api/_katecms/collections/[collection]/revisions.get.ts +35 -0
- package/src/runtime/server/api/_katecms/collections/[collection].get.ts +177 -0
- package/src/runtime/server/api/_katecms/collections/[collection].post.ts +111 -0
- package/src/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].delete.ts +37 -0
- package/src/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].get.ts +51 -0
- package/src/runtime/server/api/_katecms/dictionaries/[dictionary]/[id].patch.ts +101 -0
- package/src/runtime/server/api/_katecms/dictionaries/[dictionary].get.ts +43 -0
- package/src/runtime/server/api/_katecms/dictionaries/[dictionary].post.ts +78 -0
- package/src/runtime/server/api/_katecms/media/[id]/regenerate.post.ts +39 -0
- package/src/runtime/server/api/_katecms/media/[id].delete.ts +28 -0
- package/src/runtime/server/api/_katecms/media/[id].get.ts +25 -0
- package/src/runtime/server/api/_katecms/media/[id].patch.ts +60 -0
- package/src/runtime/server/api/_katecms/media/index.get.ts +96 -0
- package/src/runtime/server/api/_katecms/media/index.post.ts +104 -0
- package/src/runtime/server/api/_katecms/media/regenerate.post.ts +38 -0
- package/src/runtime/server/api/_katecms/menu.get.ts +19 -0
- package/src/runtime/server/api/_katecms/oembed.get.ts +62 -0
- package/src/runtime/server/api/_katecms/options/[page].get.ts +35 -0
- package/src/runtime/server/api/_katecms/options/[page].patch.ts +46 -0
- package/src/runtime/server/api/_katecms/query.get.ts +332 -0
- package/src/runtime/server/api/_katecms/relation/entries.get.ts +98 -0
- package/src/runtime/server/api/_katecms/relation/terms.get.ts +61 -0
- package/src/runtime/server/api/_katecms/resolve.get.ts +148 -0
- package/src/runtime/server/api/_katecms/terms.get.ts +258 -0
- package/src/runtime/server/api/_katecms/users/[id].delete.ts +57 -0
- package/src/runtime/server/api/_katecms/users/[id].patch.ts +177 -0
- package/src/runtime/server/api/_katecms/users.get.ts +48 -0
- package/src/runtime/server/api/_katecms/users.post.ts +85 -0
- package/src/runtime/server/api/uploads/[...key].get.ts +33 -0
- package/src/runtime/server/kit.ts +5 -0
- package/src/runtime/server/middleware/admin-headers.ts +14 -0
- package/src/runtime/server/plugins/auto-migrate.ts +53 -0
- package/src/runtime/server/tsconfig.json +3 -0
- package/src/runtime/server/utils/auth-attempts.ts +71 -0
- package/src/runtime/server/utils/auth-rate-limit.ts +48 -0
- package/src/runtime/server/utils/client-address.ts +41 -0
- package/src/runtime/server/utils/collections.ts +381 -0
- package/src/runtime/server/utils/db.ts +36 -0
- package/src/runtime/server/utils/dictionaries.ts +397 -0
- package/src/runtime/server/utils/media-list.ts +131 -0
- package/src/runtime/server/utils/media.ts +94 -0
- package/src/runtime/server/utils/oembed-fetch.ts +157 -0
- package/src/runtime/server/utils/options.ts +86 -0
- package/src/runtime/server/utils/permissions.ts +44 -0
- package/src/runtime/server/utils/public-fields-query.ts +14 -0
- package/src/runtime/server/utils/public-term.ts +98 -0
- package/src/runtime/server/utils/regenerate.ts +56 -0
- package/src/runtime/server/utils/relation.ts +96 -0
- package/src/runtime/server/utils/request-address.ts +16 -0
- package/src/runtime/server/utils/revisions.ts +256 -0
- package/src/runtime/server/utils/session.ts +112 -0
- package/src/runtime/server/utils/upload-headers.ts +35 -0
- package/src/runtime/server/utils/users.ts +67 -0
- package/src/runtime/utils/admin-users.ts +43 -0
- package/src/runtime/utils/auth-error.ts +22 -0
- package/src/runtime/utils/cms-paths.ts +17 -0
- package/src/runtime/utils/collections.ts +228 -0
- package/src/runtime/utils/dictionaries.ts +89 -0
- package/src/runtime/utils/field-errors.ts +12 -0
- package/src/runtime/utils/field-values.ts +101 -0
- package/src/runtime/utils/helpers.ts +33 -0
- package/src/runtime/utils/media-library-query.ts +263 -0
- package/src/runtime/utils/options.ts +25 -0
- package/src/runtime/utils/preview.ts +56 -0
- package/src/runtime/utils/relation.ts +60 -0
- package/src/runtime/utils/slugify.ts +10 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Fields, OptionFields, OptionPageDefinition, OptionPages, RoleHolder, RoleMap } from '@katecms/core';
|
|
2
|
+
import { fail, readFields } from './collections.js';
|
|
3
|
+
import type { AdminOptionMeta } from '../../utils/options.js';
|
|
4
|
+
export { fail, readFields };
|
|
5
|
+
export declare function getOptionPages(): OptionPages;
|
|
6
|
+
export declare function getOptionPageDefinition(slug: string): OptionPageDefinition | undefined;
|
|
7
|
+
export declare function requireOptionPage(slug: string): OptionPageDefinition;
|
|
8
|
+
export declare function readOptionPageSlug(raw: string | undefined): string;
|
|
9
|
+
export declare function toOptionMeta(slug: string, def: OptionPageDefinition): AdminOptionMeta;
|
|
10
|
+
export declare function optionPageFields(fields: OptionFields | undefined): Fields;
|
|
11
|
+
export declare function toStoredOptionValues(value: unknown): Record<string, unknown>;
|
|
12
|
+
export declare function resolveOptionValues(fields: Fields, stored: unknown): Record<string, unknown>;
|
|
13
|
+
export declare function requireOptionFields(fields: Fields, values: Record<string, unknown>): void;
|
|
14
|
+
export declare function optionFlags(actor: RoleHolder | null, roles: RoleMap, slug: string): {
|
|
15
|
+
canRead: boolean;
|
|
16
|
+
canUpdate: boolean;
|
|
17
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import {
|
|
3
|
+
buildFormValues,
|
|
4
|
+
can,
|
|
5
|
+
formatRequiredFieldsError,
|
|
6
|
+
validateRequiredFields
|
|
7
|
+
} from "@katecms/core";
|
|
8
|
+
import { fail, readFields } from "./collections.js";
|
|
9
|
+
export { fail, readFields };
|
|
10
|
+
export function getOptionPages() {
|
|
11
|
+
return useRuntimeConfig().katecms?.options ?? {};
|
|
12
|
+
}
|
|
13
|
+
export function getOptionPageDefinition(slug) {
|
|
14
|
+
return getOptionPages()[slug];
|
|
15
|
+
}
|
|
16
|
+
export function requireOptionPage(slug) {
|
|
17
|
+
const page = getOptionPageDefinition(slug);
|
|
18
|
+
if (!page) {
|
|
19
|
+
fail(404, "Options page not found.");
|
|
20
|
+
}
|
|
21
|
+
return page;
|
|
22
|
+
}
|
|
23
|
+
export function readOptionPageSlug(raw) {
|
|
24
|
+
const slug = typeof raw === "string" ? raw.trim() : "";
|
|
25
|
+
if (!slug) {
|
|
26
|
+
fail(400, "Options page is required.");
|
|
27
|
+
}
|
|
28
|
+
return slug;
|
|
29
|
+
}
|
|
30
|
+
export function toOptionMeta(slug, def) {
|
|
31
|
+
return {
|
|
32
|
+
slug,
|
|
33
|
+
label: def.label,
|
|
34
|
+
fields: def.fields ?? {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function optionPageFields(fields) {
|
|
38
|
+
return fields ?? {};
|
|
39
|
+
}
|
|
40
|
+
export function toStoredOptionValues(value) {
|
|
41
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
42
|
+
return { ...value };
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
export function resolveOptionValues(fields, stored) {
|
|
47
|
+
return {
|
|
48
|
+
...buildFormValues(fields),
|
|
49
|
+
...toStoredOptionValues(stored)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function requireOptionFields(fields, values) {
|
|
53
|
+
const issues = validateRequiredFields(fields, values);
|
|
54
|
+
if (issues.length > 0) {
|
|
55
|
+
fail(422, formatRequiredFieldsError(issues));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function optionFlags(actor, roles, slug) {
|
|
59
|
+
if (!actor) {
|
|
60
|
+
return { canRead: false, canUpdate: false };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
canRead: can(actor, "options", "read", { roles, scope: slug }),
|
|
64
|
+
canUpdate: can(actor, "options", "update", { roles, scope: slug })
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Resource, RoleAction, RoleHolder, RoleMap, SafeUser } from '@katecms/core';
|
|
2
|
+
/**
|
|
3
|
+
* Like {@link can} but throws 403 when the actor lacks the permission.
|
|
4
|
+
* Pass `scope` for keyed resources (`collections`, `dictionaries`, `options`).
|
|
5
|
+
*/
|
|
6
|
+
export declare function requirePermission(user: RoleHolder, resource: Resource, action: RoleAction, roles: RoleMap, scope?: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Like {@link canOnEntry} but throws 403. Used for update/delete of a stored
|
|
9
|
+
* collection row so authors can only change their own entries.
|
|
10
|
+
*/
|
|
11
|
+
export declare function requireEntryPermission(user: SafeUser, resource: Resource, action: RoleAction, entry: {
|
|
12
|
+
authorId: number | null;
|
|
13
|
+
}, roles: RoleMap, scope?: string): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createError } from "h3";
|
|
2
|
+
import { can, canOnEntry } from "@katecms/core";
|
|
3
|
+
export function requirePermission(user, resource, action, roles, scope) {
|
|
4
|
+
if (!can(user, resource, action, { roles, scope })) {
|
|
5
|
+
throw createError({
|
|
6
|
+
statusCode: 403,
|
|
7
|
+
statusMessage: "Forbidden",
|
|
8
|
+
message: "Insufficient permissions."
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function requireEntryPermission(user, resource, action, entry, roles, scope) {
|
|
13
|
+
if (!canOnEntry(user, resource, action, entry, { roles, scope, userId: user.id })) {
|
|
14
|
+
throw createError({
|
|
15
|
+
statusCode: 403,
|
|
16
|
+
statusMessage: "Forbidden",
|
|
17
|
+
message: "Insufficient permissions."
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FieldsQuery } from '@katecms/core';
|
|
2
|
+
/**
|
|
3
|
+
* Public collection and term queries accept field filters.
|
|
4
|
+
* Regexp compares are rejected: a pattern bound into PostgreSQL `~` can stall the database.
|
|
5
|
+
*/
|
|
6
|
+
export declare function rejectPublicRegexp(query: FieldsQuery | undefined): FieldsQuery | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Database } from '@katecms/core';
|
|
3
|
+
import type { ResolveTermResponse } from '../../utils/collections.js';
|
|
4
|
+
/**
|
|
5
|
+
* Public term URL follows the dictionary `rewrite` (`/{dictionary}/%name%` by default).
|
|
6
|
+
* A collection rewrite that matches the same path wins before this is called.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePublicDictionaryTerm(db: Kysely<Database>, path: string): Promise<ResolveTermResponse | undefined>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPublicPath,
|
|
3
|
+
buildSlugPathMap,
|
|
4
|
+
isCollectionPublic,
|
|
5
|
+
isDictionaryPublic,
|
|
6
|
+
listCollectionsByDictionary,
|
|
7
|
+
listDictionaries,
|
|
8
|
+
matchCollectionPath
|
|
9
|
+
} from "@katecms/core";
|
|
10
|
+
import { getCollections } from "./collections.js";
|
|
11
|
+
import { getDictionaries } from "./dictionaries.js";
|
|
12
|
+
export async function resolvePublicDictionaryTerm(db, path) {
|
|
13
|
+
const dictionaries = Object.fromEntries(
|
|
14
|
+
Object.entries(getDictionaries()).filter(([, def2]) => isDictionaryPublic(def2))
|
|
15
|
+
);
|
|
16
|
+
const matched = matchCollectionPath(dictionaries, path);
|
|
17
|
+
if (!matched) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
const type = matched.collectionSlug;
|
|
21
|
+
const def = dictionaries[type];
|
|
22
|
+
if (!def) {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
const slugPath = matched.slugPath;
|
|
26
|
+
const rows = await listDictionaries(db, type);
|
|
27
|
+
const slugPaths = buildSlugPathMap(rows);
|
|
28
|
+
const row = rows.find((item) => (slugPaths.get(item.id) ?? item.slug) === slugPath);
|
|
29
|
+
if (!row) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
const term = {
|
|
33
|
+
id: row.id,
|
|
34
|
+
type: row.type,
|
|
35
|
+
slug: row.slug,
|
|
36
|
+
slugPath,
|
|
37
|
+
title: row.title,
|
|
38
|
+
fields: row.fields ?? {},
|
|
39
|
+
parentId: row.parent_id
|
|
40
|
+
};
|
|
41
|
+
const linked = await listCollectionsByDictionary(db, row.id);
|
|
42
|
+
const collections = getCollections();
|
|
43
|
+
const entries = [];
|
|
44
|
+
const types = [...new Set(linked.filter((item) => item.status === "published").map((item) => item.type))];
|
|
45
|
+
for (const collectionType of types) {
|
|
46
|
+
const collection = collections[collectionType];
|
|
47
|
+
if (!collection || !isCollectionPublic(collection)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const family = await db.selectFrom("collections").select(["id", "slug", "parent_id"]).where("type", "=", collectionType).where("status", "!=", "trash").execute();
|
|
51
|
+
const paths = buildSlugPathMap(family);
|
|
52
|
+
for (const item of linked) {
|
|
53
|
+
if (item.type !== collectionType || item.status !== "published") {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const itemPath = paths.get(item.id) ?? item.slug;
|
|
57
|
+
if (!itemPath) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
entries.push({
|
|
61
|
+
id: item.id,
|
|
62
|
+
title: item.title,
|
|
63
|
+
path: buildPublicPath(collection.rewrite, collectionType, itemPath)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
kind: "term",
|
|
69
|
+
dictionary: {
|
|
70
|
+
slug: type,
|
|
71
|
+
labels: {
|
|
72
|
+
name: def.labels.name,
|
|
73
|
+
singularName: def.labels.singularName || def.labels.name
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
term,
|
|
77
|
+
entries,
|
|
78
|
+
path: buildPublicPath(def.rewrite, type, slugPath)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StoredMediaVariant } from '@katecms/core';
|
|
2
|
+
export interface RegenerateTarget {
|
|
3
|
+
id: number;
|
|
4
|
+
filename: string;
|
|
5
|
+
mime_type: string;
|
|
6
|
+
variants: StoredMediaVariant[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Rebuild one item's variants from the current media config and persist
|
|
10
|
+
* `ready` / `none` / `failed`. Safe to run after the HTTP response.
|
|
11
|
+
*/
|
|
12
|
+
export declare function regenerateMedia(target: RegenerateTarget): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasImageWork,
|
|
3
|
+
isProcessableImage,
|
|
4
|
+
regenerateVariants,
|
|
5
|
+
updateMedia
|
|
6
|
+
} from "@katecms/core";
|
|
7
|
+
import { getDb } from "./db.js";
|
|
8
|
+
import { getMediaConfig, getMediaStorage } from "./media.js";
|
|
9
|
+
export async function regenerateMedia(target) {
|
|
10
|
+
const { id, filename, mime_type: mimeType, variants: oldVariants } = target;
|
|
11
|
+
if (!isProcessableImage(mimeType)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const storage = getMediaStorage();
|
|
15
|
+
const config = getMediaConfig();
|
|
16
|
+
const willGenerate = hasImageWork(config);
|
|
17
|
+
try {
|
|
18
|
+
let variants = [];
|
|
19
|
+
if (willGenerate) {
|
|
20
|
+
variants = await regenerateVariants({
|
|
21
|
+
storage,
|
|
22
|
+
key: filename,
|
|
23
|
+
mimeType,
|
|
24
|
+
oldVariants,
|
|
25
|
+
config
|
|
26
|
+
});
|
|
27
|
+
} else {
|
|
28
|
+
await Promise.all(oldVariants.map((variant) => storage.delete(variant.filename).catch(() => {
|
|
29
|
+
})));
|
|
30
|
+
}
|
|
31
|
+
await updateMedia(getDb(), id, {
|
|
32
|
+
variants,
|
|
33
|
+
variants_status: willGenerate ? "ready" : "none"
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("[katecms] failed to regenerate image variants", error);
|
|
37
|
+
await updateMedia(getDb(), id, { variants_status: "failed" }).catch(() => {
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Collection, CollectionDefinition, MediaItem, RelationEntryValue } from '@katecms/core';
|
|
2
|
+
export declare const RELATION_PICKER_LIMIT = 500;
|
|
3
|
+
export declare function readStringList(raw: unknown): string[];
|
|
4
|
+
/** First image found in `fields` for the given field keys (`image` or `gallery`). */
|
|
5
|
+
export declare function pickFeaturedImage(fields: unknown, keys: string[]): MediaItem | null;
|
|
6
|
+
export declare function toPickerEntry(row: Collection, def: CollectionDefinition, slugPath: string, extras?: {
|
|
7
|
+
featuredImage?: MediaItem | null;
|
|
8
|
+
termIds?: number[];
|
|
9
|
+
}): RelationEntryValue & {
|
|
10
|
+
termIds?: number[];
|
|
11
|
+
featuredImage?: MediaItem | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function collectionArchiveOption(slug: string, def: CollectionDefinition): {
|
|
14
|
+
url: string;
|
|
15
|
+
title: string;
|
|
16
|
+
type: string;
|
|
17
|
+
} | null;
|
|
18
|
+
export declare function pathMapForRows(rows: Collection[]): Map<number, string>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPublicPath,
|
|
3
|
+
buildSlugPathMap,
|
|
4
|
+
isImageMedia,
|
|
5
|
+
isMediaItem,
|
|
6
|
+
resolveArchivePath,
|
|
7
|
+
toRelationEntryValue
|
|
8
|
+
} from "@katecms/core";
|
|
9
|
+
export const RELATION_PICKER_LIMIT = 500;
|
|
10
|
+
export function readStringList(raw) {
|
|
11
|
+
const parts = Array.isArray(raw) ? raw : typeof raw === "string" ? raw.split(",") : [];
|
|
12
|
+
return [...new Set(parts.map((item) => String(item).trim()).filter(Boolean))];
|
|
13
|
+
}
|
|
14
|
+
export function pickFeaturedImage(fields, keys) {
|
|
15
|
+
if (!keys.length || !fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const record = fields;
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
const value = record[key];
|
|
21
|
+
if (isMediaItem(value) && isImageMedia(value)) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
const image = value.find((item) => isMediaItem(item) && isImageMedia(item));
|
|
26
|
+
if (image) {
|
|
27
|
+
return image;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function toPickerEntry(row, def, slugPath, extras) {
|
|
34
|
+
const path = slugPath || row.slug;
|
|
35
|
+
const entry = toRelationEntryValue({
|
|
36
|
+
id: row.id,
|
|
37
|
+
type: row.type,
|
|
38
|
+
status: row.status,
|
|
39
|
+
title: row.title,
|
|
40
|
+
slug: row.slug,
|
|
41
|
+
slugPath: path,
|
|
42
|
+
url: def.public === false ? "" : buildPublicPath(def.rewrite, row.type, path),
|
|
43
|
+
fields: row.fields ?? {},
|
|
44
|
+
parentId: row.parent_id,
|
|
45
|
+
menuOrder: row.menu_order,
|
|
46
|
+
authorId: row.author_id,
|
|
47
|
+
createdAt: row.created_at,
|
|
48
|
+
updatedAt: row.updated_at,
|
|
49
|
+
publishedAt: row.published_at
|
|
50
|
+
});
|
|
51
|
+
if (extras?.featuredImage !== void 0) {
|
|
52
|
+
entry.featuredImage = extras.featuredImage;
|
|
53
|
+
}
|
|
54
|
+
if (extras?.termIds) {
|
|
55
|
+
entry.termIds = extras.termIds;
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
export function collectionArchiveOption(slug, def) {
|
|
60
|
+
const url = resolveArchivePath(def, slug);
|
|
61
|
+
if (!url || def.public === false) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const name = def.labels.name;
|
|
65
|
+
return {
|
|
66
|
+
type: slug,
|
|
67
|
+
title: `${name} archive`,
|
|
68
|
+
url
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function pathMapForRows(rows) {
|
|
72
|
+
return buildSlugPathMap(rows.map((row) => ({
|
|
73
|
+
id: row.id,
|
|
74
|
+
slug: row.slug,
|
|
75
|
+
parent_id: row.parent_id
|
|
76
|
+
})));
|
|
77
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getRequestHeader, getRequestIP } from "h3";
|
|
2
|
+
import { clientAddress, trustedProxyHops } from "./client-address.js";
|
|
3
|
+
export function eventClientAddress(event) {
|
|
4
|
+
const hops = trustedProxyHops();
|
|
5
|
+
return clientAddress({
|
|
6
|
+
forwardedFor: hops > 0 ? getRequestHeader(event, "x-forwarded-for") : void 0,
|
|
7
|
+
hops,
|
|
8
|
+
socketAddress: getRequestIP(event)
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Collection, CollectionStatus, Database } from '@katecms/core';
|
|
3
|
+
import type { EntryRevisionItem } from '../../utils/collections.js';
|
|
4
|
+
export interface EntryRevisionData {
|
|
5
|
+
title: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
status: CollectionStatus;
|
|
8
|
+
parentId: number | null;
|
|
9
|
+
menuOrder: number;
|
|
10
|
+
publishedAt: string | null;
|
|
11
|
+
authorId: number | null;
|
|
12
|
+
fields: Record<string, unknown>;
|
|
13
|
+
dictionaryIds: Record<string, number[]>;
|
|
14
|
+
}
|
|
15
|
+
export declare function entryRevisionSnapshot(row: Collection, dictionaryIds: Record<string, number[]>): EntryRevisionData;
|
|
16
|
+
export declare function readEntryRevisionData(value: unknown): EntryRevisionData;
|
|
17
|
+
/** Store the saved entry when it differs from the latest revision. The first change also keeps the previous state. */
|
|
18
|
+
export declare function recordEntryRevision(db: Kysely<Database>, entryId: number, actorId: number, previous: EntryRevisionData | null, next: EntryRevisionData): Promise<void>;
|
|
19
|
+
export declare function listEntryRevisions(db: Kysely<Database>, entryId: number): Promise<EntryRevisionItem[]>;
|
|
20
|
+
export declare function revisionAuthorId(db: Kysely<Database>, authorId: number | null): Promise<number | null>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCollectionRevision,
|
|
3
|
+
findUserById
|
|
4
|
+
} from "@katecms/core";
|
|
5
|
+
import { fail } from "./collections.js";
|
|
6
|
+
const KEEP_REVISIONS = 50;
|
|
7
|
+
export function entryRevisionSnapshot(row, dictionaryIds) {
|
|
8
|
+
return {
|
|
9
|
+
title: row.title,
|
|
10
|
+
slug: row.slug,
|
|
11
|
+
status: row.status,
|
|
12
|
+
parentId: row.parent_id,
|
|
13
|
+
menuOrder: row.menu_order,
|
|
14
|
+
publishedAt: asIso(row.published_at),
|
|
15
|
+
authorId: row.author_id,
|
|
16
|
+
fields: isRecord(row.fields) ? row.fields : {},
|
|
17
|
+
dictionaryIds: sortDictionaryIds(dictionaryIds)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function readEntryRevisionData(value) {
|
|
21
|
+
const snapshot = asSnapshot(value);
|
|
22
|
+
if (!snapshot) {
|
|
23
|
+
fail(422, "This revision cannot be restored.");
|
|
24
|
+
}
|
|
25
|
+
return snapshot;
|
|
26
|
+
}
|
|
27
|
+
export async function recordEntryRevision(db, entryId, actorId, previous, next) {
|
|
28
|
+
const latest = await db.selectFrom("collection_revisions").select("data").where("collection_id", "=", entryId).orderBy("created_at", "desc").orderBy("id", "desc").executeTakeFirst();
|
|
29
|
+
const latestSnapshot = latest ? asSnapshot(latest.data) : null;
|
|
30
|
+
if (previous && !sameSnapshot(previous, next) && (!latestSnapshot || !sameSnapshot(latestSnapshot, previous))) {
|
|
31
|
+
await insertRevision(db, entryId, null, previous);
|
|
32
|
+
}
|
|
33
|
+
if (!latestSnapshot || !sameSnapshot(latestSnapshot, next)) {
|
|
34
|
+
await insertRevision(db, entryId, actorId, next);
|
|
35
|
+
}
|
|
36
|
+
await pruneRevisions(db, entryId);
|
|
37
|
+
}
|
|
38
|
+
export async function listEntryRevisions(db, entryId) {
|
|
39
|
+
const rows = await db.selectFrom("collection_revisions").select(["id", "created_at", "data", "author_id"]).where("collection_id", "=", entryId).orderBy("created_at", "desc").orderBy("id", "desc").limit(KEEP_REVISIONS).execute();
|
|
40
|
+
const authorIds = [...new Set(rows.flatMap((row) => row.author_id == null ? [] : [row.author_id]))];
|
|
41
|
+
const authors = authorIds.length ? await db.selectFrom("users").select(["id", "name", "email"]).where("id", "in", authorIds).execute() : [];
|
|
42
|
+
const names = new Map(authors.map((user) => [user.id, authorLabel(user.name, user.email)]));
|
|
43
|
+
return rows.map((row) => {
|
|
44
|
+
const snapshot = asSnapshot(row.data);
|
|
45
|
+
return {
|
|
46
|
+
id: Number(row.id),
|
|
47
|
+
createdAt: asIso(row.created_at) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
48
|
+
authorName: row.author_id == null ? "Unknown" : names.get(row.author_id) ?? "Unknown",
|
|
49
|
+
title: snapshot?.title || "Revision",
|
|
50
|
+
status: snapshot?.status ?? "draft"
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export async function revisionAuthorId(db, authorId) {
|
|
55
|
+
if (authorId == null) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const user = await findUserById(db, authorId);
|
|
59
|
+
return user ? authorId : null;
|
|
60
|
+
}
|
|
61
|
+
async function insertRevision(db, entryId, authorId, data) {
|
|
62
|
+
await createCollectionRevision(db, {
|
|
63
|
+
collection_id: entryId,
|
|
64
|
+
author_id: authorId,
|
|
65
|
+
data,
|
|
66
|
+
created_at: /* @__PURE__ */ new Date()
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function pruneRevisions(db, entryId) {
|
|
70
|
+
const keep = await db.selectFrom("collection_revisions").select("id").where("collection_id", "=", entryId).orderBy("created_at", "desc").orderBy("id", "desc").limit(KEEP_REVISIONS).execute();
|
|
71
|
+
if (keep.length < KEEP_REVISIONS) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await db.deleteFrom("collection_revisions").where("collection_id", "=", entryId).where("id", "not in", keep.map((row) => Number(row.id))).execute();
|
|
75
|
+
}
|
|
76
|
+
function sameSnapshot(left, right) {
|
|
77
|
+
return canonical(left) === canonical(right);
|
|
78
|
+
}
|
|
79
|
+
function canonical(value) {
|
|
80
|
+
return JSON.stringify(sortValue(value));
|
|
81
|
+
}
|
|
82
|
+
function sortValue(value) {
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
return value.map((item) => sortValue(item));
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(value)) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
const sorted = {};
|
|
90
|
+
for (const key of Object.keys(value).sort()) {
|
|
91
|
+
sorted[key] = sortValue(value[key]);
|
|
92
|
+
}
|
|
93
|
+
return sorted;
|
|
94
|
+
}
|
|
95
|
+
function asSnapshot(value) {
|
|
96
|
+
if (!isRecord(value)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (typeof value.title !== "string" || !value.title.trim()) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (typeof value.slug !== "string" || !value.slug.trim()) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (value.status !== "published" && value.status !== "draft" && value.status !== "trash") {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const fields = value.fields == null ? {} : value.fields;
|
|
109
|
+
if (!isRecord(fields)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
title: value.title.trim(),
|
|
114
|
+
slug: value.slug.trim(),
|
|
115
|
+
status: value.status,
|
|
116
|
+
parentId: wholeId(value.parentId),
|
|
117
|
+
menuOrder: Number.isInteger(value.menuOrder) ? Number(value.menuOrder) : 0,
|
|
118
|
+
publishedAt: typeof value.publishedAt === "string" && value.publishedAt ? value.publishedAt : null,
|
|
119
|
+
authorId: wholeId(value.authorId),
|
|
120
|
+
fields,
|
|
121
|
+
dictionaryIds: sortDictionaryIds(readDictionaryIds(value.dictionaryIds))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function readDictionaryIds(value) {
|
|
125
|
+
if (!isRecord(value)) {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
const ids = {};
|
|
129
|
+
for (const [slug, raw] of Object.entries(value)) {
|
|
130
|
+
if (!Array.isArray(raw)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
ids[slug] = raw.map(wholeId).filter((id) => id != null);
|
|
134
|
+
}
|
|
135
|
+
return ids;
|
|
136
|
+
}
|
|
137
|
+
function sortDictionaryIds(value) {
|
|
138
|
+
const sorted = {};
|
|
139
|
+
for (const slug of Object.keys(value).sort()) {
|
|
140
|
+
sorted[slug] = [...value[slug] ?? []].sort((left, right) => left - right);
|
|
141
|
+
}
|
|
142
|
+
return sorted;
|
|
143
|
+
}
|
|
144
|
+
function wholeId(value) {
|
|
145
|
+
const id = typeof value === "number" ? value : Number(value);
|
|
146
|
+
return Number.isInteger(id) && id > 0 ? id : null;
|
|
147
|
+
}
|
|
148
|
+
function authorLabel(name, email) {
|
|
149
|
+
if (typeof name === "string" && name.trim()) {
|
|
150
|
+
return name.trim();
|
|
151
|
+
}
|
|
152
|
+
if (typeof email === "string" && email.trim()) {
|
|
153
|
+
return email.trim();
|
|
154
|
+
}
|
|
155
|
+
return "Unknown";
|
|
156
|
+
}
|
|
157
|
+
function asIso(value) {
|
|
158
|
+
if (value == null || value === "") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (value instanceof Date) {
|
|
162
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === "string") {
|
|
165
|
+
const date = new Date(value);
|
|
166
|
+
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function isRecord(value) {
|
|
171
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
172
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { SafeUser } from '@katecms/core';
|
|
3
|
+
/** Persist the authenticated user's id and password session version. */
|
|
4
|
+
export declare function setUserSession(event: H3Event, userId: number, sessionVersion?: number): Promise<void>;
|
|
5
|
+
/** Drop the session cookie (logout). */
|
|
6
|
+
export declare function clearUserSession(event: H3Event): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the currently authenticated user from the session cookie.
|
|
9
|
+
*
|
|
10
|
+
* Returns null when there is no session, the referenced user no longer exists,
|
|
11
|
+
* or the account is banned — so callers can treat all of these as "logged out".
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCurrentUser(event: H3Event): Promise<SafeUser | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Like {@link getCurrentUser} but throws a 401 when there is no authenticated
|
|
16
|
+
* user, so protected handlers can assume a non-null result.
|
|
17
|
+
*/
|
|
18
|
+
export declare function requireUser(event: H3Event): Promise<SafeUser>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createError, getRequestURL, useSession } from "h3";
|
|
2
|
+
import { findUserById, toSafeUser } from "@katecms/core";
|
|
3
|
+
import { getDb } from "./db.js";
|
|
4
|
+
const SESSION_COOKIE = "katecms_session";
|
|
5
|
+
const MIN_PASSWORD_LENGTH = 32;
|
|
6
|
+
const SESSION_MAX_AGE = 60 * 60 * 24 * 7;
|
|
7
|
+
const DEV_FALLBACK = "katecms-dev-session-password-change-me";
|
|
8
|
+
let warnedMissingPassword = false;
|
|
9
|
+
function getSessionPassword() {
|
|
10
|
+
const password = typeof process.env.MIRAICMS_SESSION_PASSWORD === "string" ? process.env.MIRAICMS_SESSION_PASSWORD : "";
|
|
11
|
+
if (password.length >= MIN_PASSWORD_LENGTH) {
|
|
12
|
+
return password;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.NODE_ENV === "production") {
|
|
15
|
+
throw createError({
|
|
16
|
+
statusCode: 500,
|
|
17
|
+
statusMessage: `MIRAICMS_SESSION_PASSWORD must be set to a random string of at least ${MIN_PASSWORD_LENGTH} characters.`
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (!warnedMissingPassword) {
|
|
21
|
+
warnedMissingPassword = true;
|
|
22
|
+
console.warn(
|
|
23
|
+
`[KateCMS] MIRAICMS_SESSION_PASSWORD is missing or shorter than ${MIN_PASSWORD_LENGTH} characters; using a development fallback. Set it before deploying.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return DEV_FALLBACK;
|
|
27
|
+
}
|
|
28
|
+
function getCmsSession(event) {
|
|
29
|
+
return useSession(event, {
|
|
30
|
+
name: SESSION_COOKIE,
|
|
31
|
+
password: getSessionPassword(),
|
|
32
|
+
maxAge: SESSION_MAX_AGE,
|
|
33
|
+
cookie: {
|
|
34
|
+
path: "/",
|
|
35
|
+
httpOnly: true,
|
|
36
|
+
secure: getRequestURL(event).protocol === "https:",
|
|
37
|
+
sameSite: "lax"
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function setUserSession(event, userId, sessionVersion = 0) {
|
|
42
|
+
const session = await getCmsSession(event);
|
|
43
|
+
await session.update({ userId, sessionVersion });
|
|
44
|
+
}
|
|
45
|
+
export async function clearUserSession(event) {
|
|
46
|
+
const session = await getCmsSession(event);
|
|
47
|
+
await session.clear();
|
|
48
|
+
}
|
|
49
|
+
export async function getCurrentUser(event) {
|
|
50
|
+
const session = await getCmsSession(event);
|
|
51
|
+
const userId = session.data.userId;
|
|
52
|
+
if (!userId) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const user = await findUserById(getDb(), userId);
|
|
56
|
+
if (!user || user.status === "banned") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const sessionVersion = session.data.sessionVersion ?? 0;
|
|
60
|
+
if ((user.session_version ?? 0) !== sessionVersion) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return toSafeUser(user);
|
|
64
|
+
}
|
|
65
|
+
export async function requireUser(event) {
|
|
66
|
+
const user = await getCurrentUser(event);
|
|
67
|
+
if (!user) {
|
|
68
|
+
throw createError({ statusCode: 401, statusMessage: "Unauthorized", message: "Authentication required." });
|
|
69
|
+
}
|
|
70
|
+
return user;
|
|
71
|
+
}
|