@open-commune/web 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/components/forms/inputs/input-editor.vue +4 -5
- package/nuxt.config.ts +27 -8
- package/package.json +2 -2
- package/pages/index/index.vue +0 -5
- package/server/api/v1/content/deployment.get.ts +0 -45
- package/server/api/v1/content/records.get.ts +0 -12
- package/server/middleware/3.api-token.ts +0 -20
package/README.md
CHANGED
|
@@ -36,6 +36,19 @@ Run it with Bun: the database layer uses `bun:sqlite`, so `bun --bun nuxt dev`,
|
|
|
36
36
|
|
|
37
37
|
Environment is validated at startup by `@open-commune/core`; see `.env.example` in the repository for the full list.
|
|
38
38
|
|
|
39
|
+
One variable matters when you deploy a build rather than run it in place. Nitro
|
|
40
|
+
inlines the core into its chunks, but the Drizzle migrations are files: the
|
|
41
|
+
build drops them next to the server output, and the deployment points at them.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
COMMUN_MIGRATIONS_DIR=/path/to/.output/drizzle
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Leave it unset in development — the layer resolves the folder from the
|
|
48
|
+
installed `@open-commune/core`. Unset in a deployment where `node_modules` is
|
|
49
|
+
gone, migrations are silently skipped and the first query fails on a missing
|
|
50
|
+
table.
|
|
51
|
+
|
|
39
52
|
## Licence
|
|
40
53
|
|
|
41
54
|
AGPL-3.0-or-later.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<label v-if="label" :for="name" class="block text-2xs font-light tracking-wider text-gray-500 uppercase">
|
|
4
4
|
{{ label }}
|
|
5
5
|
</label>
|
|
6
|
-
<!-- Fixed toolbar
|
|
6
|
+
<!-- Fixed toolbar: discoverability for
|
|
7
7
|
authors who never hover a handle or type "/". Gestures stay available. -->
|
|
8
8
|
<UEditorToolbar
|
|
9
9
|
v-if="uEditor?.editor"
|
|
@@ -149,9 +149,8 @@
|
|
|
149
149
|
// contract (shared cache, bounded resolution). Editing surface is
|
|
150
150
|
// Notion-like: selection bubble, slash commands, per-block drag handle.
|
|
151
151
|
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// the string adapter dies with models/_commun.
|
|
152
|
+
// Value contract: accepts the ProseMirror document as an object or as
|
|
153
|
+
// stringified JSON, and emits IN THE SAME SHAPE it received.
|
|
155
154
|
import { ref, watch } from 'vue'
|
|
156
155
|
import { mapEditorItems } from '@nuxt/ui/utils/editor'
|
|
157
156
|
import { communExtensions, communStarterKit, sanitizeDoc, EMBED_SERVICES } from '~/editor'
|
|
@@ -304,7 +303,7 @@ const slashItems = [
|
|
|
304
303
|
],
|
|
305
304
|
]
|
|
306
305
|
|
|
307
|
-
// Fixed toolbar:
|
|
306
|
+
// Fixed toolbar: block catalog, link, marks, alignment.
|
|
308
307
|
const toolbarItems = [
|
|
309
308
|
[{ slot: 'blocks' }],
|
|
310
309
|
[
|
package/nuxt.config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { globSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { cp } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
3
5
|
import { fileURLToPath } from 'node:url';
|
|
4
6
|
|
|
5
7
|
// Under Bun's isolated store, the ESM build of @vue/devtools-api only exists
|
|
@@ -32,13 +34,21 @@ const devtoolsApiEsm = findDevtoolsApiEsm();
|
|
|
32
34
|
// found.
|
|
33
35
|
const layerDir = dirname(fileURLToPath(import.meta.url));
|
|
34
36
|
|
|
35
|
-
// Le worker de dev empaquette les sources du core
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
37
|
+
// Le worker de dev empaquette les sources du core : le dossier de migrations
|
|
38
|
+
// Drizzle n'y est plus resolvable relativement, on l'epingle ici. Par
|
|
39
|
+
// resolution de module et NON par chemin relatif — « ../../packages/core »
|
|
40
|
+
// n'est vrai que dans le monorepo, jamais chez qui installe la couche.
|
|
41
|
+
function coreDrizzleDir(): string | undefined {
|
|
42
|
+
try {
|
|
43
|
+
const pkg = createRequire(import.meta.url).resolve('@open-commune/core/package.json');
|
|
44
|
+
return join(dirname(pkg), 'drizzle');
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const drizzleDir = coreDrizzleDir();
|
|
51
|
+
if (drizzleDir) process.env.COMMUN_MIGRATIONS_DIR ??= drizzleDir;
|
|
42
52
|
|
|
43
53
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
44
54
|
export default defineNuxtConfig({
|
|
@@ -75,6 +85,15 @@ export default defineNuxtConfig({
|
|
|
75
85
|
scheduledTasks: {
|
|
76
86
|
'30 0 * * *': ['deploy'],
|
|
77
87
|
},
|
|
88
|
+
hooks: {
|
|
89
|
+
// Nitro inline le core dans ses chunks : le dossier de migrations, lui,
|
|
90
|
+
// ne suit pas. On le depose a cote du build pour qu'il voyage avec, et
|
|
91
|
+
// le deploiement pointe COMMUN_MIGRATIONS_DIR dessus (l'image le fait).
|
|
92
|
+
async compiled(nitro) {
|
|
93
|
+
if (!drizzleDir) return;
|
|
94
|
+
await cp(drizzleDir, join(nitro.options.output.dir, 'drizzle'), { recursive: true });
|
|
95
|
+
},
|
|
96
|
+
},
|
|
78
97
|
},
|
|
79
98
|
css: [join(layerDir, 'assets/css/main.css')],
|
|
80
99
|
// Scan nested composables (composables/core/domains/*).
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@iconify/vue": "^5.0.1",
|
|
18
18
|
"@nuxt/kit": "^4.5.1",
|
|
19
19
|
"@nuxt/ui": "^4.3.0",
|
|
20
|
-
"@open-commune/core": "^0.
|
|
20
|
+
"@open-commune/core": "^0.2.0",
|
|
21
21
|
"@open-commune/editor": "^0.1.4",
|
|
22
22
|
"@pinia-orm/nuxt": "^2.0.0",
|
|
23
23
|
"@pinia/nuxt": "^1.0.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"description": "The Open Commune application: the admin interface and the server, on a single origin.",
|
|
42
42
|
"license": "AGPL-3.0-or-later",
|
|
43
|
-
"version": "0.
|
|
43
|
+
"version": "0.2.0",
|
|
44
44
|
"type": "module",
|
|
45
45
|
"main": "./nuxt.config.ts",
|
|
46
46
|
"files": [
|
package/pages/index/index.vue
CHANGED
|
@@ -23,11 +23,6 @@
|
|
|
23
23
|
/>
|
|
24
24
|
<button-primary type="submit" label="S'identifier" :loading="isLoading" class="w-full" />
|
|
25
25
|
</form>
|
|
26
|
-
<!-- Le lien de réinitialisation était commenté depuis le legacy, qui
|
|
27
|
-
n'envoyait AUCUN email : le seul recours était le formulaire de
|
|
28
|
-
support. L'événement `passwordResetRequested` étant désormais relayé
|
|
29
|
-
vers un fournisseur d'emails, le parcours autonome reprend sa place —
|
|
30
|
-
et Fillout redevient ce qu'il doit être, un second recours. -->
|
|
31
26
|
<div class="flex mt-8 flex-col items-center justify-center">
|
|
32
27
|
<button-tertiary
|
|
33
28
|
label="Vous avez oublié votre mot de passe ?"
|
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
});
|