@open-commune/web 0.1.3

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.
Files changed (132) hide show
  1. package/README.md +41 -0
  2. package/app.config.ts +10 -0
  3. package/app.vue +13 -0
  4. package/assets/css/main.css +13 -0
  5. package/components/data/display/row/data-avatar.vue +23 -0
  6. package/components/data/display/row/data-badge.vue +34 -0
  7. package/components/data/display/row/data-color.vue +17 -0
  8. package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
  9. package/components/data/display/row/data-label.vue +37 -0
  10. package/components/data/display/row/data-last-update.vue +45 -0
  11. package/components/data/display/row/data-spacer.vue +4 -0
  12. package/components/data/display/row/data-summary.vue +44 -0
  13. package/components/data/display/row/data-text.vue +14 -0
  14. package/components/data/display/row/data-toggle.vue +16 -0
  15. package/components/data/display/row.vue +111 -0
  16. package/components/data/lists/data-list.vue +85 -0
  17. package/components/data/lists/data-lists-table.vue +155 -0
  18. package/components/elements/avatars/avatar-circular.vue +68 -0
  19. package/components/elements/avatars/avatar-with-text.vue +47 -0
  20. package/components/elements/badges/badge.vue +85 -0
  21. package/components/elements/buttons/primary.vue +32 -0
  22. package/components/elements/buttons/secondary.vue +35 -0
  23. package/components/elements/buttons/tertiary.vue +25 -0
  24. package/components/elements/dropdowns/dots.vue +75 -0
  25. package/components/elements/media/image.vue +82 -0
  26. package/components/elements/notifications/default.vue +90 -0
  27. package/components/forms/handlers/handler-collections.vue +189 -0
  28. package/components/forms/handlers/handler-publication-status.vue +193 -0
  29. package/components/forms/handlers/handler-records.vue +138 -0
  30. package/components/forms/handlers/handler-roles.vue +176 -0
  31. package/components/forms/handlers/handler-schedules.vue +218 -0
  32. package/components/forms/handlers/handler-steps.vue +147 -0
  33. package/components/forms/handlers/handler-tokens.vue +79 -0
  34. package/components/forms/inputs/input-color-picker.vue +159 -0
  35. package/components/forms/inputs/input-editor.vue +363 -0
  36. package/components/forms/inputs/input-files-media.vue +150 -0
  37. package/components/forms/inputs/input-files.vue +98 -0
  38. package/components/forms/inputs/input-json.vue +65 -0
  39. package/components/forms/inputs/input-location.vue +179 -0
  40. package/components/forms/inputs/input-text-area.vue +65 -0
  41. package/components/forms/inputs/input-text.vue +122 -0
  42. package/components/forms/inputs/input-toggle.vue +125 -0
  43. package/components/forms/inputs/media-section/media-editor.vue +95 -0
  44. package/components/forms/layouts/form.vue +203 -0
  45. package/components/forms/layouts/layout.vue +47 -0
  46. package/components/forms/layouts/modal.vue +96 -0
  47. package/components/forms/layouts/navigation.vue +48 -0
  48. package/components/forms/layouts/section.vue +118 -0
  49. package/components/forms/selects/select-collection-enum.vue +195 -0
  50. package/components/forms/selects/select-date.vue +245 -0
  51. package/components/forms/selects/select-enum.vue +186 -0
  52. package/components/forms/selects/select-record.vue +274 -0
  53. package/components/layout/headings/headings-collection.vue +29 -0
  54. package/components/layout/headings/headings-record.vue +33 -0
  55. package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
  56. package/components/layout/navigations/primary.vue +132 -0
  57. package/components/layout/notifications/notifications-list.vue +19 -0
  58. package/composables/core/domains/use-definitions.ts +57 -0
  59. package/composables/core/domains/use-entries.ts +62 -0
  60. package/composables/core/domains/use-media.ts +82 -0
  61. package/composables/core/use-core.ts +12 -0
  62. package/composables/use-ability.js +38 -0
  63. package/composables/use-models.js +27 -0
  64. package/composables/use-session.ts +80 -0
  65. package/composables/use-timer.js +62 -0
  66. package/composables/use-trpc.js +17 -0
  67. package/editor/index.ts +44 -0
  68. package/editor/views/CalloutView.vue +22 -0
  69. package/editor/views/DetailsContentView.vue +11 -0
  70. package/editor/views/DetailsSummaryView.vue +11 -0
  71. package/editor/views/DetailsView.vue +72 -0
  72. package/editor/views/EmbedView.vue +53 -0
  73. package/editor/views/FileView.vue +79 -0
  74. package/editor/views/ImageView.vue +56 -0
  75. package/editor/views/UploadView.vue +71 -0
  76. package/layouts/default.vue +21 -0
  77. package/layouts/entrance.vue +10 -0
  78. package/layouts/record.vue +21 -0
  79. package/middleware/00.auth.global.js +16 -0
  80. package/middleware/01.instance.global.js +21 -0
  81. package/middleware/03.verify-permissions.global.js +11 -0
  82. package/middleware/04.prefetch-page.global.js +18 -0
  83. package/middleware/05.headings-page.global.js +25 -0
  84. package/models/_commun.js +243 -0
  85. package/models/_factory.js +34 -0
  86. package/models/collection.js +87 -0
  87. package/models/entrance.js +42 -0
  88. package/models/media.js +82 -0
  89. package/models/organization.js +92 -0
  90. package/models/record.js +165 -0
  91. package/models/role.js +66 -0
  92. package/models/token.js +56 -0
  93. package/models/user.js +97 -0
  94. package/nuxt.config.ts +151 -0
  95. package/package.json +71 -0
  96. package/pages/[collection]/[record]/index.vue +88 -0
  97. package/pages/[collection]/index.vue +55 -0
  98. package/pages/_lab/editor.vue +149 -0
  99. package/pages/auth/callback.vue +31 -0
  100. package/pages/index/index.vue +85 -0
  101. package/pages/index/password/define/[token].vue +79 -0
  102. package/pages/index/password/recover/index.vue +72 -0
  103. package/pages/index/welcome/[token].vue +90 -0
  104. package/pages/index.vue +44 -0
  105. package/pages/management/index.vue +15 -0
  106. package/pages/management/members.vue +176 -0
  107. package/pages/management/organizations.vue +192 -0
  108. package/pages/management/roles.vue +183 -0
  109. package/pages/overview.vue +15 -0
  110. package/pages/settings.vue +197 -0
  111. package/plugins/01.session.client.ts +5 -0
  112. package/plugins/02.vue-query.ts +25 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/logo-black.svg +1 -0
  115. package/public/logo-white.svg +1 -0
  116. package/server/api/trpc/[...trpc].ts +28 -0
  117. package/server/api/v1/content/deployment.get.ts +45 -0
  118. package/server/api/v1/content/records.get.ts +12 -0
  119. package/server/middleware/0.security.ts +19 -0
  120. package/server/middleware/1.cors.ts +31 -0
  121. package/server/middleware/2.session.ts +20 -0
  122. package/server/middleware/3.api-token.ts +20 -0
  123. package/server/plugins/core.ts +8 -0
  124. package/server/routes/_tasks/[name].post.ts +20 -0
  125. package/server/routes/health.get.ts +6 -0
  126. package/server/tasks/deploy.ts +26 -0
  127. package/server/types.d.ts +8 -0
  128. package/server/utils/core.ts +18 -0
  129. package/store/layout/headings.js +21 -0
  130. package/store/layout/modal.js +80 -0
  131. package/store/layout/notifications.js +52 -0
  132. package/store/layout/workspace.js +80 -0
@@ -0,0 +1,183 @@
1
+ <template>
2
+ <section class="w-full">
3
+ <!-- Rôles figés par Commun (admin / rédacteur) : consultation seule. -->
4
+ <p class="mb-6 rounded-md bg-amber-50 border border-amber-200 px-4 py-3 text-sm text-amber-800">
5
+ Les rôles sont désormais prédéfinis (administrateur, rédacteur) et ne sont plus personnalisables.
6
+ </p>
7
+ <default-table
8
+ v-if="!$route.params.event"
9
+ :headers="TABLE_HEADERS"
10
+ :filters="filters"
11
+ :items="records"
12
+ :actions="abilities"
13
+ :pagination-size="limit"
14
+ display-search-bar
15
+ paginated-content
16
+ @fetch-more="_fetchMore"
17
+ @update="_openModalForm('update', $event)"
18
+ @remove="_handle('remove', $event)"
19
+ />
20
+ </section>
21
+ </template>
22
+
23
+ <script setup>
24
+ import buttonSecondary from '~/components/elements/buttons/secondary'
25
+ import defaultTable from '~/components/data/lists/data-lists-table'
26
+ import { useModalStore } from '~/store/layout/modal'
27
+
28
+ definePageMeta({
29
+ permissions: ['manage:all'],
30
+ preFetch: ['Role'],
31
+ headings: {
32
+ title: 'Membres',
33
+ description: 'Retrouvez & managez ici toutes les membres de la plateforme.'
34
+ }
35
+ })
36
+
37
+ const { Role } = useModels()
38
+ const $route = useRoute()
39
+ const $modalStore = useModalStore()
40
+
41
+ // Prepare constants
42
+ const TABLE_HEADERS = [
43
+ {
44
+ title: 'Nom',
45
+ property: 'name',
46
+ component: 'data-text'
47
+ },
48
+ {
49
+ title: '',
50
+ component: 'actions'
51
+ }
52
+ ]
53
+
54
+ // Prepare reactive data
55
+ const limit = ref(25)
56
+
57
+ // Prepare computed
58
+ const records = computed(() => Role.repo.all())
59
+ const filters = computed(() => [])
60
+ // Rôles figés : aucune action d'édition/suppression proposée.
61
+ const abilities = computed(() => [])
62
+
63
+ // Prepare methods
64
+ const _fetchMore = async (skip = 0) => {
65
+ await Role.list(undefined, undefined, { skip, limit: limit.value })
66
+ }
67
+
68
+ // Prepare methods
69
+ const _handle = async (action, data = {}) => {
70
+ const { _id } = data
71
+ switch (action) {
72
+ case 'add':
73
+ await Role.create(data)
74
+ break
75
+ case 'update':
76
+ await Role.update(_id, data)
77
+ break
78
+ case 'remove':
79
+ await Role.remove(_id)
80
+ break
81
+ default:
82
+ throw new Error('E_UNHANDLED')
83
+ }
84
+ }
85
+
86
+ const _openModalForm = (action, record) => {
87
+ $modalStore.open({
88
+ onSubmit: async (data) => _handle(action, data),
89
+ details: {
90
+ title: `${action === 'add' ? 'Ajouter' : 'Modifier'} un rôle`,
91
+ subTitle: 'Reinseignez les informations de ce rôle.',
92
+ acceptButton: `${action === 'add' ? 'Ajouter' : 'Modifier'}`,
93
+ closeButton: 'Annuler'
94
+ },
95
+ attributes: [
96
+ {
97
+ property: 'name',
98
+ componentOptions: {
99
+ label: 'Nom du rôle',
100
+ name: 'name'
101
+ },
102
+ component: 'input-text'
103
+ },
104
+ {
105
+ property: 'description',
106
+ componentOptions: {
107
+ label: 'Description rôle',
108
+ name: 'description'
109
+ },
110
+ component: 'input-text'
111
+ },
112
+ {
113
+ property: 'permissions',
114
+ componentOptions: {
115
+ label: 'Permissions du rôle',
116
+ name: 'permissions',
117
+ items: [
118
+ 'manage:all',
119
+ 'read:deployments',
120
+ 'create:deployments',
121
+ 'admin:organizations',
122
+ 'create:organizations',
123
+ 'read:organizations',
124
+ 'update:organizations',
125
+ 'create:collections',
126
+ 'read:collections',
127
+ 'update:collections',
128
+ 'delete:collections',
129
+ 'publish:records',
130
+ 'create:records',
131
+ 'read:records',
132
+ 'update:records',
133
+ 'delete:records',
134
+ 'create:media',
135
+ 'read:media',
136
+ 'update:media',
137
+ 'delete:media',
138
+ 'create:users',
139
+ 'read:users',
140
+ 'update:users',
141
+ 'delete:users',
142
+ 'create:roles',
143
+ 'read:roles',
144
+ 'update:roles',
145
+ 'delete:roles',
146
+ 'create:tokens',
147
+ 'read:tokens',
148
+ 'update:tokens',
149
+ 'delete:tokens'
150
+ ],
151
+ enableMultiple: true,
152
+ object: false,
153
+ placeHolder: 'Ajouter une ou plusieurs permissions',
154
+ },
155
+ component: 'select-enum'
156
+ },
157
+ {
158
+ property: 'onlyOwn',
159
+ componentOptions: {
160
+ label: 'Restriction créateur',
161
+ description: 'Restreindre le champs d\'action d\'un utilisateur à ses propres ressources uniquement.',
162
+ name: 'onlyOwn',
163
+ property: 'onlyOwn',
164
+ readOnly: true
165
+ },
166
+ component: 'input-toggle'
167
+ },
168
+ {
169
+ property: 'mfa',
170
+ componentOptions: {
171
+ label: 'Authentification multi-facteur',
172
+ description: 'Vérifier l\'authentification d\'un utilisateur via un facteur supplémentaire (authenticator, sms, ...).',
173
+ name: 'mfa',
174
+ property: 'mfa',
175
+ readOnly: true
176
+ },
177
+ component: 'input-toggle'
178
+ }
179
+ ],
180
+ record
181
+ })
182
+ }
183
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div class="relative block w-full">
3
+
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ definePageMeta({
9
+ permissions: ['entrance:self'],
10
+ headings: {
11
+ title: 'Bienvenue',
12
+ description: ''
13
+ }
14
+ })
15
+ </script>
@@ -0,0 +1,197 @@
1
+ <template>
2
+ <forms-layout
3
+ v-bind="{ record, ...ORGANIZATION_FORM }"
4
+ class="mt-4"
5
+ />
6
+ </template>
7
+
8
+ <script setup>
9
+ import { useWorkspaceStore } from '~/store/layout/workspace'
10
+ import formsLayout from '~/components/forms/layouts/layout'
11
+
12
+ definePageMeta({
13
+ permissions: ['manage:all'],
14
+ preFetch: ['Organization'],
15
+ headings: {
16
+ title: 'Paramètres',
17
+ description: 'Retrouvez ici tous les paramètres de l\'organisation.'
18
+ }
19
+ })
20
+
21
+ const $workspaceStore = useWorkspaceStore()
22
+ const ORGANIZATION_FORM = ref({
23
+ layout: [
24
+ {
25
+ icon: '',
26
+ sections: [
27
+ {
28
+ componentOptions: {
29
+ title: 'Logo de l\'organization',
30
+ description: 'Cliquez sur votre logo pour ajouter une image depuis vos fichiers.',
31
+ hint: 'Le logo est optionnel mais fortement recommandé.',
32
+ attributes: [
33
+ {
34
+ property: 'logo',
35
+ componentOptions: {
36
+ name: 'cover',
37
+ limit: 1,
38
+ label: 'Logo de l\'organization'
39
+ },
40
+ component: 'input-files-media'
41
+ }
42
+ ]
43
+ },
44
+ component: 'form-section-default'
45
+ },
46
+ {
47
+ componentOptions: {
48
+ title: 'Nom de l\'organization',
49
+ description: 'Ces informations seront visibles publiquement, soyez donc attentif avec ce que vous partagerez.',
50
+ hint: 'Merci de ne pas dépasser 128 charactères.',
51
+ attributes: [
52
+ {
53
+ property: 'name',
54
+ componentOptions: {
55
+ previewSlug: false,
56
+ editableSlug: false,
57
+ trim: true,
58
+ placeHolder: 'Nom de l\'organisation',
59
+ required: true
60
+ },
61
+ component: 'input-text'
62
+ }
63
+ ]
64
+ },
65
+ component: 'form-section-default'
66
+ }
67
+ // {
68
+ // componentOptions: {
69
+ // title: 'Localisation de l\'organization',
70
+ // description: 'Ces informations seront visibles publiquement, soyez donc attentif avec ce que vous partagerez.',
71
+ // attributes: [
72
+ // {
73
+ // property: 'location',
74
+ // componentOptions: {
75
+ // placeHolder: 'Localisation de l\'organisation',
76
+ // name: 'organization'
77
+ // },
78
+ // component: 'input-location'
79
+ // }
80
+ // ]
81
+ // },
82
+ // component: 'form-section-default'
83
+ // }
84
+ ],
85
+ title: 'General'
86
+ },
87
+ {
88
+ icon: '',
89
+ sections: [
90
+ {
91
+ componentOptions: {
92
+ title: 'Collections',
93
+ description: 'Ces collections permettent de définir les différents jeux de données de l\'organisation.',
94
+ hint: '🚦 Soyez vigilant lorsque vous modifiez ce champs.',
95
+ attributes: [
96
+ {
97
+ property: 'collections',
98
+ componentOptions: {},
99
+ component: 'handler-collections'
100
+ }
101
+ ],
102
+ noAction: true
103
+ },
104
+ component: 'form-section-default'
105
+ }
106
+ ],
107
+ title: 'Collections'
108
+ },
109
+ {
110
+ icon: '',
111
+ sections: [
112
+ {
113
+ componentOptions: {
114
+ title: 'Les thématiques',
115
+ description: 'Vous pouvez modifier ici les thématiques de cette organisation.',
116
+ attributes: [
117
+ {
118
+ componentOptions: {
119
+ collection: 'labels',
120
+ alwaysPublish: true
121
+ },
122
+ component: 'handler-records'
123
+ }
124
+ ],
125
+ noAction: true
126
+ },
127
+ component: 'form-section-default'
128
+ }
129
+ ],
130
+ title: 'Thématiques'
131
+ },
132
+ {
133
+ icon: '',
134
+ sections: [
135
+ {
136
+ componentOptions: {
137
+ title: 'Deployment settings',
138
+ description: 'Renseignez la définition du déploiement.',
139
+ attributes: [
140
+ {
141
+ property: 'deployment',
142
+ componentOptions: {
143
+ label: 'Paramètres de déploiement',
144
+ name: 'deployment'
145
+ },
146
+ component: 'input-json'
147
+ }
148
+ ]
149
+ },
150
+ component: 'form-section-default'
151
+ },
152
+ {
153
+ componentOptions: {
154
+ title: 'Tokens de déploiement',
155
+ description: 'Ces tokens permettent d\'autres applications à accéder à un contenu restreint de cette organisation.Soyez attentif!',
156
+ attributes: [
157
+ {
158
+ componentOptions: {},
159
+ component: 'handler-tokens'
160
+ }
161
+ ],
162
+ noAction: true
163
+ },
164
+ component: 'form-section-default'
165
+ }
166
+ ],
167
+ title: 'Déploiement'
168
+ },
169
+ {
170
+ icon: '',
171
+ sections: [
172
+ {
173
+ componentOptions: {
174
+ title: 'Configuration de l\'injecteur',
175
+ description: 'Définissez les différentes pipelines de données ici',
176
+ attributes: [
177
+ {
178
+ property: 'injector',
179
+ componentOptions: {
180
+ label: 'Définition de l\'injecteur',
181
+ name: 'injector'
182
+ },
183
+ component: 'input-json'
184
+ }
185
+ ]
186
+ },
187
+ component: 'form-section-default'
188
+ },
189
+ ],
190
+ title: 'Synchronisation des données'
191
+ }
192
+ ]
193
+ })
194
+
195
+ // Prepare computed
196
+ const record = computed(() => $workspaceStore.workspace)
197
+ </script>
@@ -0,0 +1,5 @@
1
+ // Restores the session BEFORE routing (the guard reads its state): one
2
+ // blocking auth.me on boot when a token is present.
3
+ export default defineNuxtPlugin(async () => {
4
+ await useSession().restore()
5
+ })
@@ -0,0 +1,25 @@
1
+ import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
2
+
3
+ /**
4
+ * Query cache — server state lives here, never in a store. Pages talk to the
5
+ * domain composables only.
6
+ *
7
+ * Key convention (prefix invalidation):
8
+ * ['definitions'] · ['entries', collectionId] · ['entries', 'byId', id]
9
+ * A write mutates then invalidates its domain prefix; every view showing it
10
+ * refreshes without a reload.
11
+ */
12
+ export default defineNuxtPlugin((nuxtApp) => {
13
+ const queryClient = new QueryClient({
14
+ defaultOptions: {
15
+ queries: {
16
+ // Freshness window, not eviction: data stays cached (gcTime) and is
17
+ // revalidated on next use once older than this.
18
+ staleTime: 30_000,
19
+ retry: 1,
20
+ refetchOnWindowFocus: false,
21
+ },
22
+ },
23
+ })
24
+ nuxtApp.vueApp.use(VueQueryPlugin, { queryClient })
25
+ })
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436.25 186.76"><g id="Calque_2" data-name="Calque 2"><g id="Calque_1-2" data-name="Calque 1"><path d="M0,78.58H16.89q14.6,0,19.63,1.26a21.85,21.85,0,0,1,11.9,7.38q4.59,5.55,4.58,14t-4.49,13.93a22,22,0,0,1-12.39,7.42Q30.33,124,14.54,124H8.47v39.49H0Zm8.47,8.24v28.85l14.43.17q8.64,0,12.67-1.57a13.08,13.08,0,0,0,6.36-5.15,14.56,14.56,0,0,0,0-15.77,12.72,12.72,0,0,0-6.1-5q-3.78-1.49-12.36-1.49Z"/><path d="M98.22,97.93a31.17,31.17,0,0,1,24,10.47,33.74,33.74,0,0,1-.51,45.65q-9.16,9.81-23.53,9.81t-23.55-9.81a33.69,33.69,0,0,1-.49-45.59A31.09,31.09,0,0,1,98.22,97.93Zm0,7.9A23.16,23.16,0,0,0,81,113.27a24.87,24.87,0,0,0-7.21,18A25.83,25.83,0,0,0,77.07,144a23.16,23.16,0,0,0,8.9,9.11,25.43,25.43,0,0,0,24.59,0A23,23,0,0,0,119.4,144a26,26,0,0,0,3.26-12.73,24.87,24.87,0,0,0-7.21-18A23.16,23.16,0,0,0,98.22,105.83Z"/><path d="M144.7,99.53h8.13v29.3q0,10.77,1.14,14.77a16.44,16.44,0,0,0,6.58,9.13,20.13,20.13,0,0,0,11.68,3.35,19.61,19.61,0,0,0,11.45-3.26,16.68,16.68,0,0,0,6.47-8.59q1.24-3.6,1.25-15.4V99.53h8.25v30.85q0,13-3.07,19.55a23.2,23.2,0,0,1-9.12,10.24q-6.08,3.69-15.23,3.69t-15.34-3.69a22.81,22.81,0,0,1-9.16-10.36q-3-6.66-3-20.06Z"/><path d="M216.25,76.75h8.12v87.06h-8.12Z"/><path d="M258.52,101.1a35.52,35.52,0,0,1,14.39-2.94,31,31,0,0,1,22.9,9.61,31.8,31.8,0,0,1,9.5,23.18,32.34,32.34,0,0,1-9.45,23.47,30.6,30.6,0,0,1-22.72,9.67A31.09,31.09,0,0,1,259.35,161a33.81,33.81,0,0,1-11.39-9.3v35h-8V127.29a43.43,43.43,0,0,1,6.18-16.89A36.22,36.22,0,0,1,258.52,101.1Zm-3.92,12.11a25,25,0,0,0-7.22,18.26,25.93,25.93,0,0,0,3.21,12.85,23.37,23.37,0,0,0,9.16,9.1,25.31,25.31,0,0,0,12.59,3.35,24.06,24.06,0,0,0,12.28-3.35,24.57,24.57,0,0,0,9.16-9.45,25.71,25.71,0,0,0,3.4-12.73,25.39,25.39,0,0,0-3.38-12.71,24.63,24.63,0,0,0-9.07-9.3,24.24,24.24,0,0,0-12.5-3.35A23.75,23.75,0,0,0,254.6,113.21Z"/><path d="M401.3,4.32c-37.74-13.13-70.44,5.59-79.08,30.43-3.34,9.26-.69,14.91,2.11,18,5.32,5.88,14.82,6.74,19.87,5.48a14.89,14.89,0,0,0,4-1.73,9.79,9.79,0,0,0,1.15,3.84,7.13,7.13,0,0,0,6.43,3.57c3.73,0,7.56-2,11.54-6a9.2,9.2,0,0,0,.84,2.11,7.81,7.81,0,0,0,7.09,3.84c8.75,0,15.57-10.58,22.16-20.81.94-1.46,1.86-2.89,2.76-4.25,1.77,3.74,3.68,8.52,3.91,11.84a7,7,0,0,1-1.76,5.43,4.81,4.81,0,0,1-2.85,1.63c-2,0-2-.54-2-.94a2,2,0,0,1,.45-.6,3.29,3.29,0,0,0-4.12-5.12,7.7,7.7,0,0,0-2.89,5.6c0,3.52,2.24,7.63,8.57,7.63a11.09,11.09,0,0,0,7.65-3.71,13.62,13.62,0,0,0,3.51-10.36c-.51-7.65-6.92-18.92-7.19-19.4a3.28,3.28,0,0,0-5.5-.31c-2,2.74-4.07,5.92-6.05,9-5.11,7.94-11.48,17.81-16.64,17.81-1.07,0-1.27-.36-1.4-.57-.84-1.46-.43-5.44,1.51-8.83,1.56-2.72,3.09-5.54,4.23-7.68.23-.42.46-.87.69-1.3.55-1,.93-1.77,1.1-2.11a3.94,3.94,0,0,0,.3-.88,3.21,3.21,0,0,0-5.64-2.84,27,27,0,0,0-1.37,2.37c-.18.35-.39.75-.62,1.17-7,13-13.73,20.67-18.27,20.67-.57,0-.65-.14-.74-.29-.83-1.44-.29-5.72,1.65-9.09,3.06-5.35,6.43-11.2,6.43-11.2a4.59,4.59,0,0,0,.24-.59,3.27,3.27,0,0,0-5.9-2.76c-.94,1.56-1.82,3-2.66,4.47-4.56,7.71-7.57,12.8-12.24,14-3.41.85-10.13.09-13.39-3.51-2.33-2.58-2.6-6.41-.79-11.42C336,15.06,365.23-1.27,399.14,10.53c19.65,6.83,35.93,24.11,29,43.92-7.89,22.69-36.71,30.73-61.51,30.73h-1.24a45.27,45.27,0,0,1-8-.79l-.47-.1a11.66,11.66,0,0,0-7.84,1.07L330.2,95.14c5.05-9.58,9-19.17,7.18-23.2A3.36,3.36,0,0,0,333,70.18a3.2,3.2,0,0,0-1.69,4.25c.51,2.92-5.89,16.33-12.19,27a3.28,3.28,0,0,0,2.83,5,3.35,3.35,0,0,0,1.51-.36l28.64-14.86a5.09,5.09,0,0,1,3.42-.47l.63.13a52.64,52.64,0,0,0,9.2.91c27.36.29,59.86-8.78,69-35.14C440.68,38.53,431.33,14.77,401.3,4.32Z"/></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436.25 186.76"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="Calque_2" data-name="Calque 2"><g id="Calque_1-2" data-name="Calque 1"><path class="cls-1" d="M0,78.58H16.89q14.6,0,19.63,1.26a21.85,21.85,0,0,1,11.9,7.38q4.59,5.55,4.58,14t-4.49,13.93a22,22,0,0,1-12.39,7.42Q30.33,124,14.54,124H8.47v39.49H0Zm8.47,8.24v28.85l14.43.17q8.64,0,12.67-1.57a13.08,13.08,0,0,0,6.36-5.15,14.56,14.56,0,0,0,0-15.77,12.72,12.72,0,0,0-6.1-5q-3.78-1.49-12.36-1.49Z"/><path class="cls-1" d="M98.22,97.93a31.17,31.17,0,0,1,24,10.47,33.74,33.74,0,0,1-.51,45.65q-9.16,9.81-23.53,9.81t-23.55-9.81a33.69,33.69,0,0,1-.49-45.59A31.09,31.09,0,0,1,98.22,97.93Zm0,7.9A23.16,23.16,0,0,0,81,113.27a24.87,24.87,0,0,0-7.21,18A25.83,25.83,0,0,0,77.07,144a23.16,23.16,0,0,0,8.9,9.11,25.43,25.43,0,0,0,24.59,0A23,23,0,0,0,119.4,144a26,26,0,0,0,3.26-12.73,24.87,24.87,0,0,0-7.21-18A23.16,23.16,0,0,0,98.22,105.83Z"/><path class="cls-1" d="M144.7,99.53h8.13v29.3q0,10.77,1.14,14.77a16.44,16.44,0,0,0,6.58,9.13,20.13,20.13,0,0,0,11.68,3.35,19.61,19.61,0,0,0,11.45-3.26,16.68,16.68,0,0,0,6.47-8.59q1.24-3.6,1.25-15.4V99.53h8.25v30.85q0,13-3.07,19.55a23.2,23.2,0,0,1-9.12,10.24q-6.08,3.69-15.23,3.69t-15.34-3.69a22.81,22.81,0,0,1-9.16-10.36q-3-6.66-3-20.06Z"/><path class="cls-1" d="M216.25,76.75h8.12v87.06h-8.12Z"/><path class="cls-1" d="M258.52,101.1a35.52,35.52,0,0,1,14.39-2.94,31,31,0,0,1,22.9,9.61,31.8,31.8,0,0,1,9.5,23.18,32.34,32.34,0,0,1-9.45,23.47,30.6,30.6,0,0,1-22.72,9.67A31.09,31.09,0,0,1,259.35,161a33.81,33.81,0,0,1-11.39-9.3v35h-8V127.29a43.43,43.43,0,0,1,6.18-16.89A36.22,36.22,0,0,1,258.52,101.1Zm-3.92,12.11a25,25,0,0,0-7.22,18.26,25.93,25.93,0,0,0,3.21,12.85,23.37,23.37,0,0,0,9.16,9.1,25.31,25.31,0,0,0,12.59,3.35,24.06,24.06,0,0,0,12.28-3.35,24.57,24.57,0,0,0,9.16-9.45,25.71,25.71,0,0,0,3.4-12.73,25.39,25.39,0,0,0-3.38-12.71,24.63,24.63,0,0,0-9.07-9.3,24.24,24.24,0,0,0-12.5-3.35A23.75,23.75,0,0,0,254.6,113.21Z"/><path class="cls-1" d="M401.3,4.32c-37.74-13.13-70.44,5.59-79.08,30.43-3.34,9.26-.69,14.91,2.11,18,5.32,5.88,14.82,6.74,19.87,5.48a14.89,14.89,0,0,0,4-1.73,9.79,9.79,0,0,0,1.15,3.84,7.13,7.13,0,0,0,6.43,3.57c3.73,0,7.56-2,11.54-6a9.2,9.2,0,0,0,.84,2.11,7.81,7.81,0,0,0,7.09,3.84c8.75,0,15.57-10.58,22.16-20.81.94-1.46,1.86-2.89,2.76-4.25,1.77,3.74,3.68,8.52,3.91,11.84a7,7,0,0,1-1.76,5.43,4.81,4.81,0,0,1-2.85,1.63c-2,0-2-.54-2-.94a2,2,0,0,1,.45-.6,3.29,3.29,0,0,0-4.12-5.12,7.7,7.7,0,0,0-2.89,5.6c0,3.52,2.24,7.63,8.57,7.63a11.09,11.09,0,0,0,7.65-3.71,13.62,13.62,0,0,0,3.51-10.36c-.51-7.65-6.92-18.92-7.19-19.4a3.28,3.28,0,0,0-5.5-.31c-2,2.74-4.07,5.92-6.05,9-5.11,7.94-11.48,17.81-16.64,17.81-1.07,0-1.27-.36-1.4-.57-.84-1.46-.43-5.44,1.51-8.83,1.56-2.72,3.09-5.54,4.23-7.68.23-.42.46-.87.69-1.3.55-1,.93-1.77,1.1-2.11a3.94,3.94,0,0,0,.3-.88,3.21,3.21,0,0,0-5.64-2.84,27,27,0,0,0-1.37,2.37c-.18.35-.39.75-.62,1.17-7,13-13.73,20.67-18.27,20.67-.57,0-.65-.14-.74-.29-.83-1.44-.29-5.72,1.65-9.09,3.06-5.35,6.43-11.2,6.43-11.2a4.59,4.59,0,0,0,.24-.59,3.27,3.27,0,0,0-5.9-2.76c-.94,1.56-1.82,3-2.66,4.47-4.56,7.71-7.57,12.8-12.24,14-3.41.85-10.13.09-13.39-3.51-2.33-2.58-2.6-6.41-.79-11.42C336,15.06,365.23-1.27,399.14,10.53c19.65,6.83,35.93,24.11,29,43.92-7.89,22.69-36.71,30.73-61.51,30.73h-1.24a45.27,45.27,0,0,1-8-.79l-.47-.1a11.66,11.66,0,0,0-7.84,1.07L330.2,95.14c5.05-9.58,9-19.17,7.18-23.2A3.36,3.36,0,0,0,333,70.18a3.2,3.2,0,0,0-1.69,4.25c.51,2.92-5.89,16.33-12.19,27a3.28,3.28,0,0,0,2.83,5,3.35,3.35,0,0,0,1.51-.36l28.64-14.86a5.09,5.09,0,0,1,3.42-.47l.63.13a52.64,52.64,0,0,0,9.2.91c27.36.29,59.86-8.78,69-35.14C440.68,38.53,431.33,14.77,401.3,4.32Z"/></g></g></svg>
@@ -0,0 +1,28 @@
1
+ import { defineEventHandler, toWebRequest } from 'h3';
2
+ import { appRouter } from '@open-commune/core/trpc';
3
+ import type { AuthSession } from '@open-commune/core';
4
+ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
5
+ import { useCore } from '../../utils/core.ts';
6
+
7
+ // Mounts the core tRPC router under /api/trpc/*. The context is assembled from
8
+ // what the plugin and the session middleware already attached to the event.
9
+ export default defineEventHandler(async (event) => {
10
+ return fetchRequestHandler({
11
+ endpoint: '/api/trpc',
12
+ req: toWebRequest(event),
13
+ router: appRouter,
14
+ createContext: () => ({
15
+ services: useCore().services,
16
+ session: (event.context.session as AuthSession | null | undefined) ?? null,
17
+ requestMeta: {
18
+ ua: event.node.req.headers['user-agent'] ?? null,
19
+ ip:
20
+ (event.node.req.headers['x-forwarded-for'] as string | undefined)
21
+ ?.split(',')[0]
22
+ ?.trim() ??
23
+ event.node.req.socket.remoteAddress ??
24
+ null,
25
+ },
26
+ }),
27
+ });
28
+ });
@@ -0,0 +1,45 @@
1
+ import { createError, defineEventHandler } from 'h3';
2
+ import { useCore } from '../../../utils/core.ts';
3
+
4
+ /**
5
+ * Legacy-compatible plane: the payload current site builds consume —
6
+ * `_theme` (visual identity), `_pages` (page definitions) and `slugs`
7
+ * (static paths plus every published `/collection/slug`).
8
+ */
9
+ export default defineEventHandler(async () => {
10
+ const { organization, collections, media } = useCore().services;
11
+
12
+ const settings = await organization.get();
13
+ if (!settings)
14
+ throw createError({ statusCode: 404, statusMessage: 'organization not initialized' });
15
+
16
+ const deployment = (settings.deployment ?? {}) as {
17
+ theme?: unknown;
18
+ definition?: Array<{ path?: string }>;
19
+ sort?: unknown;
20
+ };
21
+ // The theme lives under deployment.theme; older records may still carry it
22
+ // under settings.theme, hence the fallback below.
23
+ const theme =
24
+ deployment.theme ?? (settings.settings as { theme?: unknown } | null)?.theme ?? undefined;
25
+ const pageSlugs = (deployment.definition ?? [])
26
+ .map((page) => page.path ?? '')
27
+ .filter((path) => path && !path.includes(':'));
28
+
29
+ return {
30
+ name: 'success',
31
+ description: 'Action succeed.',
32
+ data: {
33
+ // The organisation's imported identifier, when it has one.
34
+ _id: (settings.legacyExtra as { legacyId?: string } | null)?.legacyId ?? settings.id,
35
+ name: settings.name,
36
+ ...(deployment.sort !== undefined ? { sort: deployment.sort } : {}),
37
+ // Iso legacy `_parseRecursively` : les chaînes `_media:<id>` de _theme/_pages
38
+ // sont remplacées par le média signé. Clé OMISE quand le thème est absent
39
+ // (an organisation may have no deployment.theme at all).
40
+ ...(theme !== undefined ? { _theme: await media.resolveMediaPlaceholders(theme) } : {}),
41
+ _pages: await media.resolveMediaPlaceholders(deployment.definition ?? []),
42
+ slugs: [...pageSlugs, ...(await collections.publishedSlugs())],
43
+ },
44
+ };
45
+ });
@@ -0,0 +1,12 @@
1
+ import { defineEventHandler } from 'h3';
2
+ import { useCore } from '../../../utils/core.ts';
3
+
4
+ /**
5
+ * Legacy-compatible plane: the current site builds call this exact path.
6
+ * Flat map of published entries keyed by id, media resolved, wrapped in the
7
+ * `{ name, description, data }` envelope those clients expect.
8
+ */
9
+ export default defineEventHandler(async () => {
10
+ const records = await useCore().services.collections.legacyRecordsPayload();
11
+ return { name: 'success', description: 'Action succeed.', data: { records } };
12
+ });
@@ -0,0 +1,19 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { defineEventHandler, getRequestHeader, setResponseHeader } from 'h3';
3
+
4
+ /**
5
+ * First middleware in the chain: correlation id and baseline security headers.
6
+ * Rate limiting belongs to the reverse proxy, not here.
7
+ */
8
+ export default defineEventHandler((event) => {
9
+ const requestId = getRequestHeader(event, 'x-request-id') ?? randomUUID();
10
+ setResponseHeader(event, 'x-request-id', requestId);
11
+
12
+ setResponseHeader(event, 'x-content-type-options', 'nosniff');
13
+ setResponseHeader(event, 'x-frame-options', 'DENY');
14
+ setResponseHeader(event, 'referrer-policy', 'no-referrer');
15
+ // HSTS only behind TLS — never over plain http in dev.
16
+ if (getRequestHeader(event, 'x-forwarded-proto') === 'https') {
17
+ setResponseHeader(event, 'strict-transport-security', 'max-age=31536000; includeSubDomains');
18
+ }
19
+ });
@@ -0,0 +1,31 @@
1
+ import { defineEventHandler, handleCors } from 'h3';
2
+
3
+ /**
4
+ * Any origin is allowed: auth is a Bearer header, never a cookie, so a browser
5
+ * never attaches credentials on its own.
6
+ *
7
+ * The origin is REFLECTED rather than wildcarded — browsers reject
8
+ * `Access-Control-Allow-Origin: *` on credentialed requests, and nuxt-auth
9
+ * sends `credentials: 'include'`.
10
+ */
11
+ export default defineEventHandler((event) => {
12
+ // h3 v1: returns true when it has answered the preflight itself.
13
+ if (
14
+ handleCors(event, {
15
+ origin: () => true,
16
+ credentials: true,
17
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
18
+ allowHeaders: [
19
+ 'Content-Type',
20
+ 'Authorization',
21
+ 'x-trpc-source',
22
+ 'x-trpc-batch-mode',
23
+ 'Accept',
24
+ 'trpc-accept',
25
+ ],
26
+ maxAge: '600',
27
+ })
28
+ ) {
29
+ return;
30
+ }
31
+ });
@@ -0,0 +1,20 @@
1
+ import { defineEventHandler, getRequestHeader } from 'h3';
2
+ import { useCore } from '../utils/core.ts';
3
+
4
+ /**
5
+ * Session resolution. Middlewares are numbered because order matters.
6
+ *
7
+ * Auth travels as a Bearer token, never a cookie, and is verified against the
8
+ * database on every request. Handlers read `event.context.session`.
9
+ */
10
+ function readSessionToken(authorization: string): string | null {
11
+ if (!authorization.startsWith('Bearer ')) return null;
12
+ const token = authorization.slice('Bearer '.length);
13
+ // API tokens (commun_ prefix) belong to the content plane, not to sessions.
14
+ return token.startsWith('commun_') ? null : token;
15
+ }
16
+
17
+ export default defineEventHandler(async (event) => {
18
+ const token = readSessionToken(getRequestHeader(event, 'authorization') ?? '');
19
+ event.context.session = token ? await useCore().services.users.verifySession(token) : null;
20
+ });
@@ -0,0 +1,20 @@
1
+ import { createError, defineEventHandler, getRequestHeader, getRequestURL } from 'h3';
2
+ import { useCore } from '../utils/core.ts';
3
+
4
+ /**
5
+ * API-token guard of the public content plane; runs after `2.session`.
6
+ *
7
+ * Accepts both `Authorization: Bearer <token>` and the bare
8
+ * `Authorization: <token>` form that existing site builds send.
9
+ */
10
+ const GUARDED_PATHS = new Set(['/api/v1/content/records', '/api/v1/content/deployment']);
11
+
12
+ export default defineEventHandler(async (event) => {
13
+ if (!GUARDED_PATHS.has(getRequestURL(event).pathname)) return;
14
+
15
+ const auth = getRequestHeader(event, 'authorization') ?? '';
16
+ const token = auth.startsWith('Bearer ') ? auth.slice('Bearer '.length) : auth;
17
+ if (!token || !(await useCore().services.users.verifyApiToken(token))) {
18
+ throw createError({ statusCode: 401, statusMessage: 'missing or invalid API token' });
19
+ }
20
+ });
@@ -0,0 +1,8 @@
1
+ import { defineNitroPlugin } from 'nitropack/runtime';
2
+ import { useCore } from '../utils/core.ts';
3
+
4
+ /** Instantiate the Core at server START, so migrations and config validation
5
+ * run at boot rather than on the first request. */
6
+ export default defineNitroPlugin(() => {
7
+ useCore();
8
+ });
@@ -0,0 +1,20 @@
1
+ import { createError, defineEventHandler, getRouterParam } from 'h3';
2
+ import { runTask } from 'nitropack/runtime';
3
+
4
+ /**
5
+ * Runs a Nitro task over HTTP, for the E2E suite only: Nitro's own task
6
+ * endpoint exists in the dev preset, and the suite boots the production bundle.
7
+ *
8
+ * DEAD unless COMMUN_TASKS_HTTP=1, which only the E2E harness sets — never the
9
+ * Docker image, the CI smoke test, or a deployed instance.
10
+ */
11
+ export default defineEventHandler(async (event) => {
12
+ if (process.env.COMMUN_TASKS_HTTP !== '1') {
13
+ throw createError({ statusCode: 404 });
14
+ }
15
+ const name = getRouterParam(event, 'name');
16
+ if (!name) {
17
+ throw createError({ statusCode: 400, statusMessage: 'missing task name' });
18
+ }
19
+ return runTask(name);
20
+ });
@@ -0,0 +1,6 @@
1
+ import { defineEventHandler } from 'h3';
2
+ import { useCore } from '../utils/core.ts';
3
+
4
+ // Returns the HealthStatus DTO from @open-commune/core untouched (review: no
5
+ // object rebuilding in the transport layer).
6
+ export default defineEventHandler(() => useCore().services.health.check());