@portaki/module-sections 1.2.1 → 1.2.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.
- package/package.json +9 -7
- package/portaki.catalog.json +124 -0
- package/portaki.module.json +16 -18
- package/src/components/SectionsGuestView.tsx +2 -2
- package/src/index.tsx +1 -17
- package/src/manifest.test.ts +1 -1
- package/src/module.test.tsx +1 -1
- package/src/portaki.gateway.tsx +3 -0
- package/src/portaki.module.tsx +4 -0
- package/src/sections-module-definition.ts +223 -0
- package/vitest.config.ts +1 -1
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portaki/module-sections",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Portaki module — editorial sections (TipTap)",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.tsx",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@portaki/
|
|
10
|
-
"@portaki/sdk": "^0.5.0",
|
|
9
|
+
"@portaki/sdk": "^2.0.4",
|
|
11
10
|
"@tiptap/core": "^3.0.0",
|
|
12
11
|
"@tiptap/html": "^3.0.0",
|
|
13
12
|
"@tiptap/starter-kit": "^3.0.0",
|
|
@@ -15,12 +14,14 @@
|
|
|
15
14
|
"react-dom": "^19.1.0"
|
|
16
15
|
},
|
|
17
16
|
"devDependencies": {
|
|
17
|
+
"@portaki/cli": "^0.1.2",
|
|
18
|
+
"@portaki/sdk-test-support": "^2.0.4",
|
|
19
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
20
|
+
"@testing-library/react": "^16.3.0",
|
|
18
21
|
"@types/react": "^19",
|
|
22
|
+
"jsdom": "^26.0.0",
|
|
19
23
|
"typescript": "^5",
|
|
20
|
-
"vitest": "^3.0.5"
|
|
21
|
-
"@testing-library/react": "^16.3.0",
|
|
22
|
-
"@testing-library/jest-dom": "^6.6.3",
|
|
23
|
-
"jsdom": "^26.0.0"
|
|
24
|
+
"vitest": "^3.0.5"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"access": "public"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
35
|
+
"build": "portaki build --entry src/portaki.gateway.tsx",
|
|
34
36
|
"lint": "echo \"no lint yet\"",
|
|
35
37
|
"test": "vitest run",
|
|
36
38
|
"test:watch": "vitest"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/PortakiApp/portaki-sdk/main/schema/module.v1.json",
|
|
3
|
+
"id": "sections",
|
|
4
|
+
"name": {
|
|
5
|
+
"fr": "Sections éditoriales",
|
|
6
|
+
"en": "Editorial sections"
|
|
7
|
+
},
|
|
8
|
+
"description": {
|
|
9
|
+
"fr": "Blocs de contenu riches (TipTap) pour le carnet d’accueil invité.",
|
|
10
|
+
"en": "Rich content blocks (TipTap) for the guest welcome book."
|
|
11
|
+
},
|
|
12
|
+
"version": "1.2.3",
|
|
13
|
+
"releaseNotesUrl": "https://github.com/PortakiApp/portaki-modules/releases",
|
|
14
|
+
"changelog": [
|
|
15
|
+
{
|
|
16
|
+
"version": "1.2.0",
|
|
17
|
+
"date": "2026-05-18",
|
|
18
|
+
"notes": {
|
|
19
|
+
"fr": "Release catalogue modules 1.2.0.",
|
|
20
|
+
"en": "Module catalog release 1.2.0."
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"version": "1.0.0",
|
|
25
|
+
"date": "2026-05-16",
|
|
26
|
+
"notes": {
|
|
27
|
+
"fr": "Première version : sections TipTap, gateway hôte et invité.",
|
|
28
|
+
"en": "Initial release: TipTap sections, host and guest gateway."
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Portaki",
|
|
34
|
+
"url": "https://portaki.app",
|
|
35
|
+
"type": "official"
|
|
36
|
+
},
|
|
37
|
+
"icon": "file-text",
|
|
38
|
+
"type": "official",
|
|
39
|
+
"tags": [
|
|
40
|
+
"editorial",
|
|
41
|
+
"content",
|
|
42
|
+
"sections"
|
|
43
|
+
],
|
|
44
|
+
"portakiVersionMin": "1.0.0",
|
|
45
|
+
"requiresHostSdk": "2.0.0",
|
|
46
|
+
"database": {
|
|
47
|
+
"schemaVersion": "1.0.1"
|
|
48
|
+
},
|
|
49
|
+
"license": "AGPL-3.0",
|
|
50
|
+
"repository": "https://github.com/PortakiApp/portaki-modules",
|
|
51
|
+
"scopes": [
|
|
52
|
+
"stay:read",
|
|
53
|
+
"property:read",
|
|
54
|
+
"host:property:read",
|
|
55
|
+
"host:property:write"
|
|
56
|
+
],
|
|
57
|
+
"queries": [
|
|
58
|
+
{
|
|
59
|
+
"name": "sections.list",
|
|
60
|
+
"description": {
|
|
61
|
+
"fr": "Liste des sections du logement.",
|
|
62
|
+
"en": "Property editorial sections."
|
|
63
|
+
},
|
|
64
|
+
"scope": "property:read"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"commands": [
|
|
68
|
+
{
|
|
69
|
+
"name": "sections.section.save",
|
|
70
|
+
"description": {
|
|
71
|
+
"fr": "Créer ou mettre à jour une section.",
|
|
72
|
+
"en": "Create or update a section."
|
|
73
|
+
},
|
|
74
|
+
"scope": "host:property:write"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "sections.section.delete",
|
|
78
|
+
"description": {
|
|
79
|
+
"fr": "Supprimer une section.",
|
|
80
|
+
"en": "Delete a section."
|
|
81
|
+
},
|
|
82
|
+
"scope": "host:property:write"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "sections.reorder",
|
|
86
|
+
"description": {
|
|
87
|
+
"fr": "Réordonner les sections.",
|
|
88
|
+
"en": "Reorder sections."
|
|
89
|
+
},
|
|
90
|
+
"scope": "host:property:write"
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"hostSurfaces": [
|
|
94
|
+
{
|
|
95
|
+
"type": "property-workspace-tab",
|
|
96
|
+
"pathSegment": "sections",
|
|
97
|
+
"label": {
|
|
98
|
+
"fr": "Sections",
|
|
99
|
+
"en": "Sections"
|
|
100
|
+
},
|
|
101
|
+
"icon": "file-text"
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"config": {
|
|
105
|
+
"defaults": {},
|
|
106
|
+
"fields": []
|
|
107
|
+
},
|
|
108
|
+
"catalog": {
|
|
109
|
+
"tagline": {
|
|
110
|
+
"fr": "Composez votre carnet section par section.",
|
|
111
|
+
"en": "Build your welcome book section by section."
|
|
112
|
+
},
|
|
113
|
+
"npmPackage": "@portaki/module-sections",
|
|
114
|
+
"npmUrl": "https://www.npmjs.com/package/@portaki/module-sections"
|
|
115
|
+
},
|
|
116
|
+
"runtime": {
|
|
117
|
+
"backend": "wasm",
|
|
118
|
+
"guest": "remote-esm"
|
|
119
|
+
},
|
|
120
|
+
"artifacts": {
|
|
121
|
+
"wasmUrl": "oci://ghcr.io/portakiapp/portaki-module-sections",
|
|
122
|
+
"guestEsmUrl": "https://esm.sh/@portaki/module-sections@1.2.3"
|
|
123
|
+
}
|
|
124
|
+
}
|
package/portaki.module.json
CHANGED
|
@@ -42,52 +42,52 @@
|
|
|
42
42
|
"sections"
|
|
43
43
|
],
|
|
44
44
|
"portakiVersionMin": "1.0.0",
|
|
45
|
-
"requiresHostSdk": "
|
|
45
|
+
"requiresHostSdk": "2.0.0",
|
|
46
46
|
"database": {
|
|
47
|
-
"schemaVersion": "1.0.
|
|
47
|
+
"schemaVersion": "1.0.1"
|
|
48
48
|
},
|
|
49
49
|
"license": "AGPL-3.0",
|
|
50
50
|
"repository": "https://github.com/PortakiApp/portaki-modules",
|
|
51
51
|
"scopes": [
|
|
52
|
-
"stay:read",
|
|
53
|
-
"property:read",
|
|
54
52
|
"host:property:read",
|
|
55
|
-
"host:property:write"
|
|
53
|
+
"host:property:write",
|
|
54
|
+
"property:read",
|
|
55
|
+
"stay:read"
|
|
56
56
|
],
|
|
57
57
|
"queries": [
|
|
58
58
|
{
|
|
59
59
|
"name": "sections.list",
|
|
60
|
+
"scope": "property:read",
|
|
60
61
|
"description": {
|
|
61
62
|
"fr": "Liste des sections du logement.",
|
|
62
63
|
"en": "Property editorial sections."
|
|
63
|
-
}
|
|
64
|
-
"scope": "property:read"
|
|
64
|
+
}
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
67
|
"commands": [
|
|
68
68
|
{
|
|
69
69
|
"name": "sections.section.save",
|
|
70
|
+
"scope": "host:property:write",
|
|
70
71
|
"description": {
|
|
71
72
|
"fr": "Créer ou mettre à jour une section.",
|
|
72
73
|
"en": "Create or update a section."
|
|
73
|
-
}
|
|
74
|
-
"scope": "host:property:write"
|
|
74
|
+
}
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"name": "sections.section.delete",
|
|
78
|
+
"scope": "host:property:write",
|
|
78
79
|
"description": {
|
|
79
80
|
"fr": "Supprimer une section.",
|
|
80
81
|
"en": "Delete a section."
|
|
81
|
-
}
|
|
82
|
-
"scope": "host:property:write"
|
|
82
|
+
}
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"name": "sections.reorder",
|
|
86
|
+
"scope": "host:property:write",
|
|
86
87
|
"description": {
|
|
87
88
|
"fr": "Réordonner les sections.",
|
|
88
89
|
"en": "Reorder sections."
|
|
89
|
-
}
|
|
90
|
-
"scope": "host:property:write"
|
|
90
|
+
}
|
|
91
91
|
}
|
|
92
92
|
],
|
|
93
93
|
"hostSurfaces": [
|
|
@@ -111,16 +111,14 @@
|
|
|
111
111
|
"en": "Build your welcome book section by section."
|
|
112
112
|
},
|
|
113
113
|
"npmPackage": "@portaki/module-sections",
|
|
114
|
-
"npmUrl": "https://www.npmjs.com/package/@portaki/module-sections"
|
|
115
|
-
"javaArtifact": "app.portaki.module:sections-backend"
|
|
114
|
+
"npmUrl": "https://www.npmjs.com/package/@portaki/module-sections"
|
|
116
115
|
},
|
|
117
116
|
"runtime": {
|
|
118
117
|
"backend": "wasm",
|
|
119
118
|
"guest": "remote-esm"
|
|
120
119
|
},
|
|
121
120
|
"artifacts": {
|
|
122
|
-
"wasmUrl": "oci://ghcr.io/portakiapp/portaki-module-sections
|
|
123
|
-
"
|
|
124
|
-
"guestEsmUrl": "https://esm.sh/@portaki/module-sections@1.2.0"
|
|
121
|
+
"wasmUrl": "oci://ghcr.io/portakiapp/portaki-module-sections",
|
|
122
|
+
"guestEsmUrl": "https://esm.sh/@portaki/module-sections@1.2.3"
|
|
125
123
|
}
|
|
126
124
|
}
|
|
@@ -4,8 +4,8 @@ import type { JSONContent } from '@tiptap/core'
|
|
|
4
4
|
import { generateHTML } from '@tiptap/html'
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit'
|
|
6
6
|
import { usePortakiQuery } from '@portaki/sdk'
|
|
7
|
-
import type { LangCode } from '@portaki/
|
|
8
|
-
import { ModuleSection } from '@portaki/
|
|
7
|
+
import type { LangCode } from '@portaki/sdk'
|
|
8
|
+
import { ModuleSection } from '@portaki/sdk'
|
|
9
9
|
|
|
10
10
|
type SectionRow = {
|
|
11
11
|
id: string
|
package/src/index.tsx
CHANGED
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { definePortakiModule } from '@portaki/module-sdk'
|
|
3
|
-
|
|
4
|
-
import { SectionsGuestView } from './components/SectionsGuestView'
|
|
5
|
-
|
|
6
|
-
export default definePortakiModule({
|
|
7
|
-
id: 'sections',
|
|
8
|
-
label: { fr: 'Sections', en: 'Sections' },
|
|
9
|
-
description: {
|
|
10
|
-
fr: 'Contenus éditoriaux du carnet d’accueil.',
|
|
11
|
-
en: 'Welcome book editorial content.',
|
|
12
|
-
},
|
|
13
|
-
version: '1.2.0',
|
|
14
|
-
icon: 'file-text',
|
|
15
|
-
navSlot: 'section',
|
|
16
|
-
render: ({ lang }: ModuleContext) => <SectionsGuestView lang={lang} />,
|
|
17
|
-
})
|
|
1
|
+
export { default } from './portaki.module'
|
package/src/manifest.test.ts
CHANGED
package/src/module.test.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { waitFor } from '@testing-library/react'
|
|
3
|
-
import { assertGuestSurface, renderGuestModule } from '@portaki/
|
|
3
|
+
import { assertGuestSurface, renderGuestModule } from '@portaki/sdk-test-support'
|
|
4
4
|
|
|
5
5
|
import moduleDef from './index'
|
|
6
6
|
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineModule,
|
|
3
|
+
index,
|
|
4
|
+
int,
|
|
5
|
+
jsonb,
|
|
6
|
+
moduleSchema,
|
|
7
|
+
table,
|
|
8
|
+
tenantId,
|
|
9
|
+
tenantPropertyIndex,
|
|
10
|
+
text,
|
|
11
|
+
timestamptz,
|
|
12
|
+
uuid,
|
|
13
|
+
uuidPrimaryKey,
|
|
14
|
+
type ModuleContext,
|
|
15
|
+
type PortakiFullModule,
|
|
16
|
+
} from '@portaki/sdk'
|
|
17
|
+
import type { ReactNode } from 'react'
|
|
18
|
+
|
|
19
|
+
const sectionsSchema = moduleSchema([
|
|
20
|
+
table('items', 't_e_module_sections_item', {
|
|
21
|
+
columns: [
|
|
22
|
+
uuidPrimaryKey(),
|
|
23
|
+
uuid('propertyId'),
|
|
24
|
+
tenantId(),
|
|
25
|
+
int('sortOrder', { defaultSql: '0' }),
|
|
26
|
+
text('titleFr', { defaultSql: "''" }),
|
|
27
|
+
text('titleEn', { defaultSql: "''" }),
|
|
28
|
+
jsonb('contentFr', { nullable: true }),
|
|
29
|
+
jsonb('contentEn', { nullable: true }),
|
|
30
|
+
timestamptz('createdAt', { nullable: false, defaultSql: 'now()' }),
|
|
31
|
+
timestamptz('updatedAt', { nullable: false, defaultSql: 'now()' }),
|
|
32
|
+
],
|
|
33
|
+
indexes: [tenantPropertyIndex()],
|
|
34
|
+
}),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
function stringParam(params: Record<string, unknown>, key: string): string | null {
|
|
38
|
+
const value = params[key]
|
|
39
|
+
if (value == null) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
return String(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function intParam(params: Record<string, unknown>, key: string, defaultValue: number): number {
|
|
46
|
+
const value = params[key]
|
|
47
|
+
if (value == null) {
|
|
48
|
+
return defaultValue
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'number') {
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
return Number.parseInt(String(value), 10)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function defineSectionsModule(render: (ctx: ModuleContext) => ReactNode): PortakiFullModule {
|
|
57
|
+
return defineModule({
|
|
58
|
+
id: 'sections',
|
|
59
|
+
label: { fr: 'Sections', en: 'Sections' },
|
|
60
|
+
description: {
|
|
61
|
+
fr: 'Contenus éditoriaux du carnet d’accueil.',
|
|
62
|
+
en: 'Welcome book editorial content.',
|
|
63
|
+
},
|
|
64
|
+
icon: 'file-text',
|
|
65
|
+
version: '1.2.1',
|
|
66
|
+
schemaVersion: '1.0.1',
|
|
67
|
+
schema: sectionsSchema,
|
|
68
|
+
navSlot: 'section',
|
|
69
|
+
|
|
70
|
+
queries: {
|
|
71
|
+
'sections.list': {
|
|
72
|
+
scope: 'property:read',
|
|
73
|
+
description: {
|
|
74
|
+
fr: 'Liste des sections du logement.',
|
|
75
|
+
en: 'Property editorial sections.',
|
|
76
|
+
},
|
|
77
|
+
async handler(ctx) {
|
|
78
|
+
const rows = await ctx.db
|
|
79
|
+
.from('items')
|
|
80
|
+
.where({ tenantId: ctx.tenantId, propertyId: ctx.propertyId })
|
|
81
|
+
.many()
|
|
82
|
+
rows.sort((a, b) => {
|
|
83
|
+
const orderA = Number(a.sortOrder ?? 0)
|
|
84
|
+
const orderB = Number(b.sortOrder ?? 0)
|
|
85
|
+
if (orderA !== orderB) {
|
|
86
|
+
return orderA - orderB
|
|
87
|
+
}
|
|
88
|
+
const createdA = String(a.createdAt ?? '')
|
|
89
|
+
const createdB = String(b.createdAt ?? '')
|
|
90
|
+
return createdA.localeCompare(createdB)
|
|
91
|
+
})
|
|
92
|
+
const sections = rows.map((row) => {
|
|
93
|
+
const section: Record<string, unknown> = {
|
|
94
|
+
id: row.id,
|
|
95
|
+
sortOrder: row.sortOrder,
|
|
96
|
+
titleFr: row.titleFr,
|
|
97
|
+
titleEn: row.titleEn,
|
|
98
|
+
contentFr: row.contentFr ?? null,
|
|
99
|
+
contentEn: row.contentEn ?? null,
|
|
100
|
+
}
|
|
101
|
+
if (row.updatedAt != null) {
|
|
102
|
+
section.updatedAt = row.updatedAt
|
|
103
|
+
}
|
|
104
|
+
return section
|
|
105
|
+
})
|
|
106
|
+
return { sections }
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
commands: {
|
|
112
|
+
'sections.section.save': {
|
|
113
|
+
scope: 'host:property:write',
|
|
114
|
+
description: {
|
|
115
|
+
fr: 'Créer ou mettre à jour une section.',
|
|
116
|
+
en: 'Create or update a section.',
|
|
117
|
+
},
|
|
118
|
+
async handler(ctx, params) {
|
|
119
|
+
const filters = { tenantId: ctx.tenantId, propertyId: ctx.propertyId }
|
|
120
|
+
const idRaw = stringParam(params, 'id')
|
|
121
|
+
const id =
|
|
122
|
+
idRaw == null || idRaw.trim() === '' ? crypto.randomUUID() : idRaw.trim()
|
|
123
|
+
let titleFr = stringParam(params, 'titleFr')
|
|
124
|
+
let titleEn = stringParam(params, 'titleEn')
|
|
125
|
+
if (titleFr == null || titleFr.trim() === '') {
|
|
126
|
+
throw new Error('title_fr_required')
|
|
127
|
+
}
|
|
128
|
+
titleFr = titleFr.trim()
|
|
129
|
+
if (titleEn == null || titleEn.trim() === '') {
|
|
130
|
+
titleEn = titleFr
|
|
131
|
+
} else {
|
|
132
|
+
titleEn = titleEn.trim()
|
|
133
|
+
}
|
|
134
|
+
const contentFr = params.contentFr ?? null
|
|
135
|
+
const contentEn = params.contentEn ?? null
|
|
136
|
+
const existing = await ctx.db
|
|
137
|
+
.from('items')
|
|
138
|
+
.where({ ...filters, id })
|
|
139
|
+
.one()
|
|
140
|
+
let sortOrder = intParam(params, 'sortOrder', -1)
|
|
141
|
+
if (sortOrder < 0) {
|
|
142
|
+
const all = await ctx.db.from('items').where(filters).many()
|
|
143
|
+
const max = all
|
|
144
|
+
.filter((row) => String(row.id) !== id)
|
|
145
|
+
.reduce((acc, row) => Math.max(acc, Number(row.sortOrder ?? 0)), -1)
|
|
146
|
+
sortOrder = max + 1
|
|
147
|
+
}
|
|
148
|
+
const now = new Date().toISOString()
|
|
149
|
+
const payload = {
|
|
150
|
+
titleFr,
|
|
151
|
+
titleEn,
|
|
152
|
+
contentFr,
|
|
153
|
+
contentEn,
|
|
154
|
+
sortOrder,
|
|
155
|
+
updatedAt: now,
|
|
156
|
+
}
|
|
157
|
+
if (existing != null) {
|
|
158
|
+
await ctx.db.from('items').where({ ...filters, id }).update(payload)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
await ctx.db.from('items').insert({
|
|
162
|
+
id,
|
|
163
|
+
...filters,
|
|
164
|
+
...payload,
|
|
165
|
+
createdAt: now,
|
|
166
|
+
})
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
'sections.section.delete': {
|
|
170
|
+
scope: 'host:property:write',
|
|
171
|
+
description: {
|
|
172
|
+
fr: 'Supprimer une section.',
|
|
173
|
+
en: 'Delete a section.',
|
|
174
|
+
},
|
|
175
|
+
async handler(ctx, params) {
|
|
176
|
+
const idRaw = stringParam(params, 'id')
|
|
177
|
+
if (idRaw == null || idRaw.trim() === '') {
|
|
178
|
+
throw new Error('id_required')
|
|
179
|
+
}
|
|
180
|
+
const deleted = await ctx.db
|
|
181
|
+
.from('items')
|
|
182
|
+
.where({
|
|
183
|
+
id: idRaw.trim(),
|
|
184
|
+
tenantId: ctx.tenantId,
|
|
185
|
+
propertyId: ctx.propertyId,
|
|
186
|
+
})
|
|
187
|
+
.delete()
|
|
188
|
+
if (deleted === 0) {
|
|
189
|
+
throw new Error('section_not_found')
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
'sections.reorder': {
|
|
194
|
+
scope: 'host:property:write',
|
|
195
|
+
description: {
|
|
196
|
+
fr: 'Réordonner les sections.',
|
|
197
|
+
en: 'Reorder sections.',
|
|
198
|
+
},
|
|
199
|
+
async handler(ctx, params) {
|
|
200
|
+
const raw = params.orderedIds
|
|
201
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
202
|
+
throw new Error('ordered_ids_required')
|
|
203
|
+
}
|
|
204
|
+
const filters = { tenantId: ctx.tenantId, propertyId: ctx.propertyId }
|
|
205
|
+
const now = new Date().toISOString()
|
|
206
|
+
let order = 0
|
|
207
|
+
for (const item of raw) {
|
|
208
|
+
if (item == null) {
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
const id = String(item)
|
|
212
|
+
await ctx.db
|
|
213
|
+
.from('items')
|
|
214
|
+
.where({ ...filters, id })
|
|
215
|
+
.update({ sortOrder: order++, updatedAt: now })
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
render,
|
|
222
|
+
})
|
|
223
|
+
}
|
package/vitest.config.ts
CHANGED