domma-cms 0.66.0 → 0.69.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 (221) hide show
  1. package/CLAUDE.md +64 -1
  2. package/README.md +14 -10
  3. package/admin/css/admin.css +1 -1
  4. package/admin/index.html +1 -0
  5. package/admin/js/api.js +1 -1
  6. package/admin/js/app.js +4 -4
  7. package/admin/js/lib/analytics-shape.js +1 -0
  8. package/admin/js/lib/contacts-arrange.js +1 -0
  9. package/admin/js/lib/help-popover.js +1 -0
  10. package/admin/js/lib/notes-arrange.js +1 -0
  11. package/admin/js/lib/plugin-accent.js +1 -1
  12. package/admin/js/lib/plugin-chrome.js +1 -1
  13. package/admin/js/lib/sidebar-grouping.js +1 -1
  14. package/admin/js/lib/sidebar-grouping.test.js +1 -1
  15. package/admin/js/lib/sidebar-renderer.js +3 -3
  16. package/admin/js/lib/todo-arrange.js +1 -0
  17. package/admin/js/lib/tool-kit.js +1 -0
  18. package/admin/js/templates/analytics.html +138 -0
  19. package/admin/js/templates/contacts.html +156 -0
  20. package/admin/js/templates/notes.html +137 -0
  21. package/admin/js/templates/plugin-marketplace.html +17 -4
  22. package/admin/js/templates/plugins.html +13 -7
  23. package/admin/js/templates/theme.html +38 -0
  24. package/admin/js/templates/todo.html +113 -0
  25. package/admin/js/views/analytics.js +62 -0
  26. package/admin/js/views/contacts.js +174 -0
  27. package/admin/js/views/index.js +1 -1
  28. package/admin/js/views/notes.js +104 -0
  29. package/admin/js/views/plugin-marketplace.js +1 -1
  30. package/admin/js/views/plugins.js +24 -22
  31. package/admin/js/views/theme.js +1 -1
  32. package/admin/js/views/todo.js +96 -0
  33. package/bin/cli.js +4 -10
  34. package/bin/lib/plugin-version.js +28 -0
  35. package/bin/update.js +14 -0
  36. package/config/menus/admin-sidebar.json +1 -1
  37. package/config/server.json +3 -1
  38. package/package.json +3 -7
  39. package/plugins/_lib/admin/mail/reader-view.js +20 -0
  40. package/plugins/_lib/mail/store.js +21 -1
  41. package/plugins/blog/CLAUDE.md +237 -0
  42. package/plugins/blog/admin/css/index.css +178 -1
  43. package/plugins/blog/admin/templates/blog.html +35 -53
  44. package/plugins/blog/admin/templates/categories.html +19 -6
  45. package/plugins/blog/admin/templates/comments.html +26 -9
  46. package/plugins/blog/admin/templates/post-editor.html +36 -13
  47. package/plugins/blog/admin/templates/settings.html +2 -11
  48. package/plugins/blog/admin/views/blog.js +288 -162
  49. package/plugins/blog/admin/views/categories.js +212 -201
  50. package/plugins/blog/admin/views/comments.js +207 -158
  51. package/plugins/blog/admin/views/kit.js +372 -0
  52. package/plugins/blog/admin/views/post-editor.js +269 -21
  53. package/plugins/blog/admin/views/settings-cog.js +56 -0
  54. package/plugins/blog/admin/views/settings.js +16 -86
  55. package/plugins/blog/blocks/blog-card-compact.css +17 -0
  56. package/plugins/blog/blocks/blog-card-compact.html +8 -0
  57. package/plugins/blog/blocks/blog-card-row.css +22 -0
  58. package/plugins/blog/blocks/blog-card-row.html +11 -0
  59. package/plugins/blog/blocks/blog-card.css +35 -0
  60. package/plugins/blog/blocks/blog-card.html +16 -0
  61. package/plugins/blog/blocks/blog-post-classic.css +15 -0
  62. package/plugins/blog/blocks/blog-post-classic.html +28 -0
  63. package/plugins/blog/blocks/blog-post-feature.css +20 -0
  64. package/plugins/blog/blocks/blog-post-feature.html +30 -0
  65. package/plugins/blog/blocks/blog-post-minimal.css +10 -0
  66. package/plugins/blog/blocks/blog-post-minimal.html +23 -0
  67. package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
  68. package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
  69. package/plugins/blog/collections/posts/schema.json +3 -1
  70. package/plugins/blog/config.js +6 -0
  71. package/plugins/blog/lib/layouts.js +304 -0
  72. package/plugins/blog/lib/page.js +158 -0
  73. package/plugins/blog/lib/render.js +119 -0
  74. package/plugins/blog/lib/samples.js +234 -0
  75. package/plugins/blog/plugin.js +225 -15
  76. package/plugins/blog/plugin.json +21 -81
  77. package/plugins/blog/plugin.public.js +115 -210
  78. package/plugins/blog/public/blog.css +177 -0
  79. package/plugins/blog/public/blog.js +586 -0
  80. package/plugins/blog/public/samples/aurora.svg +7 -0
  81. package/plugins/blog/public/samples/dusk.svg +7 -0
  82. package/plugins/blog/public/samples/ember.svg +7 -0
  83. package/plugins/blog/public/samples/harbour.svg +7 -0
  84. package/plugins/blog/public/samples/meadow.svg +7 -0
  85. package/plugins/blog/public/samples/rosewood.svg +7 -0
  86. package/plugins/blog/templates/index.html +9 -8
  87. package/plugins/blog/templates/post.html +4 -17
  88. package/plugins/blog/tests/layouts.test.js +97 -0
  89. package/plugins/blog/tests/public.test.js +71 -0
  90. package/plugins/free-tier.lock.json +74 -0
  91. package/plugins/mail-reader/CLAUDE.md +78 -0
  92. package/plugins/mail-reader/plugin.json +2 -1
  93. package/public/css/theme-switcher.css +1 -0
  94. package/public/js/analytics.js +1 -0
  95. package/public/js/theme-switcher.js +1 -0
  96. package/scripts/build.js +41 -2
  97. package/scripts/run-tests.mjs +92 -0
  98. package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
  99. package/{plugins/contacts/plugin.js → server/routes/api/contacts.js} +71 -43
  100. package/server/routes/api/dashboard.js +8 -2
  101. package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
  102. package/server/routes/api/plugin-marketplace.js +78 -23
  103. package/server/routes/api/plugins.js +148 -3
  104. package/server/routes/api/todo.js +178 -0
  105. package/server/server.js +61 -1
  106. package/server/services/collections.js +13 -3
  107. package/server/services/managerClient.js +72 -136
  108. package/server/services/markdown.js +54 -1
  109. package/server/services/permissionRegistry.js +50 -0
  110. package/server/services/pluginEntitlement.js +171 -0
  111. package/server/services/pluginEnvelope.js +242 -0
  112. package/server/services/pluginInstaller.js +246 -26
  113. package/server/services/pluginScaffold.js +5 -0
  114. package/server/services/plugins.js +117 -7
  115. package/server/services/presetCollections.js +344 -0
  116. package/server/services/promoted-plugins-migration.js +254 -0
  117. package/server/services/publicCors.js +46 -0
  118. package/server/services/renderer.js +83 -3
  119. package/server/services/sidebarBadges.js +37 -0
  120. package/server/services/siteGitignore.js +307 -0
  121. package/server/services/themeSettings.js +39 -0
  122. package/config/theme.json +0 -18
  123. package/plugins/analytics/admin/css/index.css +0 -1
  124. package/plugins/analytics/admin/templates/analytics.html +0 -70
  125. package/plugins/analytics/admin/views/analytics.js +0 -176
  126. package/plugins/analytics/config.js +0 -14
  127. package/plugins/analytics/plugin.json +0 -60
  128. package/plugins/analytics/public/inject-body.html +0 -60
  129. package/plugins/analytics/public/inject-head.html +0 -1
  130. package/plugins/blog/templates/author.html +0 -9
  131. package/plugins/blog/templates/category.html +0 -9
  132. package/plugins/blog/templates/tag.html +0 -9
  133. package/plugins/contacts/admin/templates/contacts.html +0 -254
  134. package/plugins/contacts/admin/views/contacts.js +0 -943
  135. package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
  136. package/plugins/contacts/collections/user-contacts/schema.json +0 -179
  137. package/plugins/contacts/config.js +0 -6
  138. package/plugins/contacts/plugin.json +0 -36
  139. package/plugins/demo-viewer/config.js +0 -4
  140. package/plugins/demo-viewer/plugin.js +0 -36
  141. package/plugins/demo-viewer/plugin.json +0 -9
  142. package/plugins/invoice/admin/css/index.css +0 -1
  143. package/plugins/invoice/admin/templates/editor.html +0 -220
  144. package/plugins/invoice/admin/templates/index.html +0 -173
  145. package/plugins/invoice/admin/templates/issuers.html +0 -2
  146. package/plugins/invoice/admin/templates/receivers.html +0 -2
  147. package/plugins/invoice/admin/views/contacts-source.js +0 -266
  148. package/plugins/invoice/admin/views/editor.js +0 -434
  149. package/plugins/invoice/admin/views/export.js +0 -199
  150. package/plugins/invoice/admin/views/help-content.js +0 -61
  151. package/plugins/invoice/admin/views/index.js +0 -643
  152. package/plugins/invoice/admin/views/issuers.js +0 -30
  153. package/plugins/invoice/admin/views/media.js +0 -172
  154. package/plugins/invoice/admin/views/party-view.js +0 -386
  155. package/plugins/invoice/admin/views/payments.js +0 -127
  156. package/plugins/invoice/admin/views/print.js +0 -130
  157. package/plugins/invoice/admin/views/receivers.js +0 -55
  158. package/plugins/invoice/admin/views/send.js +0 -212
  159. package/plugins/invoice/admin/views/settings.js +0 -594
  160. package/plugins/invoice/admin/views/view-lifecycle.js +0 -33
  161. package/plugins/invoice/collections/invoice-issuers/schema.json +0 -82
  162. package/plugins/invoice/collections/invoice-receivers/schema.json +0 -16
  163. package/plugins/invoice/collections/invoices/schema.json +0 -33
  164. package/plugins/invoice/config.js +0 -37
  165. package/plugins/invoice/pdf.js +0 -164
  166. package/plugins/invoice/plugin.js +0 -1456
  167. package/plugins/invoice/plugin.json +0 -86
  168. package/plugins/invoice/templates/_base.css +0 -1
  169. package/plugins/invoice/templates/classic-nologo.html +0 -100
  170. package/plugins/invoice/templates/classic.html +0 -91
  171. package/plugins/invoice/templates/invoice-print.html +0 -237
  172. package/plugins/invoice/templates/minimal.html +0 -99
  173. package/plugins/invoice/templates/modern-nologo.html +0 -114
  174. package/plugins/invoice/templates/modern.html +0 -113
  175. package/plugins/invoice/templates/templates.json +0 -11
  176. package/plugins/notes/admin/templates/notes.html +0 -83
  177. package/plugins/notes/admin/views/notes.js +0 -281
  178. package/plugins/notes/collections/user-notes/schema.json +0 -54
  179. package/plugins/notes/config.js +0 -6
  180. package/plugins/notes/plugin.json +0 -33
  181. package/plugins/surveys/admin/templates/audience.html +0 -47
  182. package/plugins/surveys/admin/templates/results.html +0 -58
  183. package/plugins/surveys/admin/templates/survey-editor.html +0 -141
  184. package/plugins/surveys/admin/templates/surveys.html +0 -25
  185. package/plugins/surveys/admin/views/audience.js +0 -301
  186. package/plugins/surveys/admin/views/results.js +0 -172
  187. package/plugins/surveys/admin/views/survey-editor.js +0 -211
  188. package/plugins/surveys/admin/views/surveys.js +0 -161
  189. package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
  190. package/plugins/surveys/collections/survey-groups/schema.json +0 -11
  191. package/plugins/surveys/collections/survey-invites/schema.json +0 -16
  192. package/plugins/surveys/collections/surveys/schema.json +0 -23
  193. package/plugins/surveys/config.js +0 -8
  194. package/plugins/surveys/lib/audience.js +0 -37
  195. package/plugins/surveys/lib/campaigns.js +0 -43
  196. package/plugins/surveys/lib/ledger.js +0 -110
  197. package/plugins/surveys/lib/sending.js +0 -106
  198. package/plugins/surveys/lib/stats.js +0 -62
  199. package/plugins/surveys/lib/submit.js +0 -95
  200. package/plugins/surveys/lib/tokens.js +0 -28
  201. package/plugins/surveys/plugin.js +0 -174
  202. package/plugins/surveys/plugin.json +0 -65
  203. package/plugins/surveys/plugin.public.js +0 -149
  204. package/plugins/surveys/public/dist-shared.mjs +0 -1
  205. package/plugins/surveys/public/survey.css +0 -1
  206. package/plugins/surveys/public/survey.mjs +0 -1
  207. package/plugins/surveys/templates/survey-page.html +0 -63
  208. package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
  209. package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
  210. package/plugins/theme-switcher/config.js +0 -10
  211. package/plugins/theme-switcher/plugin.js +0 -26
  212. package/plugins/theme-switcher/plugin.json +0 -74
  213. package/plugins/theme-switcher/public/inject-body.html +0 -153
  214. package/plugins/theme-switcher/public/inject-head.html +0 -260
  215. package/plugins/todo/admin/templates/todo.html +0 -158
  216. package/plugins/todo/admin/views/todo.js +0 -343
  217. package/plugins/todo/collections/todos/schema.json +0 -60
  218. package/plugins/todo/config.js +0 -7
  219. package/plugins/todo/plugin.js +0 -102
  220. package/plugins/todo/plugin.json +0 -33
  221. /package/{plugins/contacts/admin/css/index.css → admin/css/contacts.css} +0 -0
@@ -24,12 +24,47 @@ import {
24
24
  registerTransform
25
25
  } from './hooks.js';
26
26
  import {registerPluginResource, unregisterPluginResourcesByPlugin} from './permissionRegistry.js';
27
+ import {classifyEntitlement, mayLoad} from './pluginEntitlement.js';
27
28
  import {createCollection, getCollection} from './collections.js';
28
29
 
29
30
  const PLUGINS_DIR = path.resolve('plugins');
30
31
 
31
32
  const REQUIRED_MANIFEST_FIELDS = ['name', 'displayName', 'version', 'description', 'author', 'date', 'icon'];
32
33
 
34
+ /**
35
+ * Validate a candidate plugin directory the way discoverPlugins() would.
36
+ *
37
+ * Exported so an UPGRADE can check what it extracted before swapping it over a
38
+ * working installation. Validating after the swap would mean discovering the
39
+ * new version is broken with the old one already gone.
40
+ *
41
+ * @param {string} dir - absolute directory
42
+ * @param {string} slug
43
+ * @returns {Promise<{ok: boolean, reason?: string, manifest?: object}>}
44
+ */
45
+ export async function validatePluginDir(dir, slug) {
46
+ let manifest;
47
+ try {
48
+ manifest = JSON.parse(await fs.readFile(path.join(dir, 'plugin.json'), 'utf8'));
49
+ } catch {
50
+ return {ok: false, reason: 'no readable plugin.json'};
51
+ }
52
+ for (const field of REQUIRED_MANIFEST_FIELDS) {
53
+ if (!manifest[field]) return {ok: false, reason: `missing required field "${field}"`};
54
+ }
55
+ if (manifest.name !== slug) {
56
+ return {ok: false, reason: `manifest name is "${manifest.name}", not "${slug}"`};
57
+ }
58
+ for (const file of ['plugin.js', 'config.js']) {
59
+ try {
60
+ await fs.access(path.join(dir, file));
61
+ } catch {
62
+ return {ok: false, reason: `missing required file ${file}`};
63
+ }
64
+ }
65
+ return {ok: true, manifest};
66
+ }
67
+
33
68
  const failedLoads = [];
34
69
 
35
70
  /**
@@ -60,6 +95,23 @@ export function getPluginLoadFailures() {
60
95
  */
61
96
  const _loadedPlugins = {};
62
97
 
98
+ /**
99
+ * Entitlement state per licensed plugin, as of the last registerPlugins().
100
+ * Reported by GET /api/plugins so the screen can explain an absence rather than
101
+ * simply not showing a plugin the operator knows they installed.
102
+ *
103
+ * @type {Object.<string, string>}
104
+ */
105
+ const _entitlementStates = {};
106
+
107
+ /** Entitlement states that still let a plugin load. Mirrors pluginEntitlement. */
108
+ const ENTITLEMENT_LOADABLE = new Set(['valid', 'grace', 'support-ended', 'not-required']);
109
+
110
+ /** @returns {Object.<string, string>} */
111
+ export function getEntitlementStates() {
112
+ return {..._entitlementStates};
113
+ }
114
+
63
115
  /**
64
116
  * Core plugins - always loaded regardless of plugins.json enabled state.
65
117
  * These are considered first-class CMS features, not optional add-ons: they
@@ -70,12 +122,15 @@ const _loadedPlugins = {};
70
122
  * for these, so a site that had one switched off still gets it on the next
71
123
  * start - which is the point of promoting one.
72
124
  *
73
- * It is also a waiting room rather than a destination: site-search sat here
74
- * until search was moved into the CMS proper (server/services/search.js), and
75
- * a feature nobody may switch off has no business being discovered, listed and
76
- * toggled as though it were an add-on.
125
+ * It is also a waiting room rather than a destination, and as of 0.67.0 it is
126
+ * EMPTY. site-search sat here until search moved into the CMS proper, and
127
+ * analytics until it did the same - a feature nobody may switch off has no
128
+ * business being discovered, listed and toggled as though it were an add-on.
129
+ *
130
+ * Kept rather than deleted because the mechanism is the right one for the next
131
+ * promotion: a plugin lands here while its replacement is built, then leaves.
77
132
  */
78
- const CORE_PLUGINS = new Set(['analytics']);
133
+ const CORE_PLUGINS = new Set([]);
79
134
 
80
135
  /** Is this plugin a built-in feature rather than an optional add-on? */
81
136
  export function isCorePlugin(name) { return CORE_PLUGINS.has(name); }
@@ -300,11 +355,29 @@ export async function registerPlugins(fastify) {
300
355
  const manifests = await discoverPlugins();
301
356
  const states = getPluginStates();
302
357
 
358
+ // Entitlement is settled BEFORE supersession, not after, and the ordering is
359
+ // load-bearing. `email-pro` supersedes `mail-reader`; if an unentitled
360
+ // email-pro still counted as active it would displace mail-reader and then
361
+ // fail to load itself, leaving a customer whose licence lapsed with NEITHER -
362
+ // worse off than someone who never paid at all.
363
+ for (const manifest of manifests) {
364
+ if (manifest.closedSource !== true) continue;
365
+ const verdict = mayLoad(manifest);
366
+ _entitlementStates[manifest.name] = verdict.state;
367
+ if (!verdict.allowed) {
368
+ fastify.log.warn(`[plugins] "${manifest.name}" not loaded: ${verdict.state} (${verdict.reason})`);
369
+ } else if (verdict.state !== 'valid') {
370
+ fastify.log.info(`[plugins] "${manifest.name}": ${verdict.state} (${verdict.reason})`);
371
+ }
372
+ }
373
+ const entitled = (name) => _entitlementStates[name] === undefined
374
+ || ENTITLEMENT_LOADABLE.has(_entitlementStates[name]);
375
+
303
376
  // Which plugins would load on their own account, before anything displaces
304
377
  // one another.
305
378
  const activeNames = new Set(
306
379
  manifests
307
- .filter(m => states[m.name]?.enabled || CORE_PLUGINS.has(m.name))
380
+ .filter(m => (states[m.name]?.enabled || CORE_PLUGINS.has(m.name)) && entitled(m.name))
308
381
  .map(m => m.name)
309
382
  );
310
383
  const superseded = resolveSupersessions(manifests, activeNames, {
@@ -327,6 +400,12 @@ export async function registerPlugins(fastify) {
327
400
  continue;
328
401
  }
329
402
 
403
+ // Already classified above, before supersession was resolved. A refusal
404
+ // is NOT written into plugins.json: a lapse recorded as stored state
405
+ // would mean a renewal could not simply restore it, the same reasoning
406
+ // resolveSupersessions() uses for a superseded plugin.
407
+ if (!entitled(manifest.name)) continue;
408
+
330
409
  const entryPath = path.join(PLUGINS_DIR, manifest.name, 'plugin.js');
331
410
  try {
332
411
  const { default: plugin } = await import(entryPath);
@@ -801,6 +880,34 @@ export function pluginLicence(manifest) {
801
880
  return 'Included with Domma CMS';
802
881
  }
803
882
 
883
+ /**
884
+ * The expiry shown beside the licence on a plugin's admin banner, or null.
885
+ *
886
+ * Only a licensed plugin has one, and what "expires" means depends on how it
887
+ * was bought (see pluginEntitlement.js): a rental STOPS at its end date, a
888
+ * perpetual licence never does and only its support lapses. The banner says
889
+ * which, because "Expires" on something bought outright would frighten a
890
+ * customer about a thing that is not going to happen.
891
+ *
892
+ * An owner wildcard, or a perpetual licence with no support date, has nothing
893
+ * to show and returns null rather than a chip with no date in it.
894
+ *
895
+ * @param {object} manifest
896
+ * @param {object|null} [given] - injectable for testing; read from disk when omitted
897
+ * @returns {{kind: 'rental'|'support', date: string, state: string}|null}
898
+ */
899
+ export function pluginExpiry(manifest, given) {
900
+ if (manifest?.closedSource !== true) return null;
901
+ const {state, entitlement} = classifyEntitlement(manifest.name, given);
902
+ if (!entitlement || state === 'unlicensed') return null;
903
+ if (entitlement.model === 'rental') {
904
+ return entitlement.expiresAt ? {kind: 'rental', date: entitlement.expiresAt, state} : null;
905
+ }
906
+ return entitlement.supportExpiresAt
907
+ ? {kind: 'support', date: entitlement.supportExpiresAt, state}
908
+ : null;
909
+ }
910
+
804
911
  export async function getAdminPluginConfig() {
805
912
  const manifests = await discoverPlugins();
806
913
  const states = getPluginStates();
@@ -830,6 +937,8 @@ export async function getAdminPluginConfig() {
830
937
  // browser fetching the same URL and serving the previous build of
831
938
  // everything behind it. That presents as a feature that worked
832
939
  // yesterday and does not today, on one machine.
940
+ // Once per plugin, not per view: it verifies a signature.
941
+ const expiry = pluginExpiry(manifest);
833
942
  const sharedStamp = Math.max(
834
943
  await newestAdminMtime(path.join(PLUGINS_DIR, '_lib', 'admin')),
835
944
  await newestAdminMtime(path.join(PLUGINS_DIR, manifest.name, 'admin'))
@@ -851,7 +960,8 @@ export async function getAdminPluginConfig() {
851
960
  author: manifest.author || '',
852
961
  icon: manifest.icon || 'package',
853
962
  core: CORE_PLUGINS.has(manifest.name),
854
- licence: pluginLicence(manifest)
963
+ licence: pluginLicence(manifest),
964
+ expiry
855
965
  };
856
966
  }
857
967
 
@@ -115,6 +115,350 @@ const PRESETS = [
115
115
  delete: {enabled: false, access: 'admin'}
116
116
  }
117
117
  }
118
+ ,
119
+ // ---- Tools promoted out of plugins/ (0.67.0) --------------------------
120
+ // The slugs are what setupPlugin() created from `schema.slug`, NOT the
121
+ // directories the schemas used to live in (`user-notes`, `todos`). Keeping
122
+ // them exactly preserves every existing entry; changing one would seed an
123
+ // empty collection beside a site's real one.
124
+ //
125
+ // seedAll() rewrites schema.json on every boot, so `storage` is declared
126
+ // exactly as the plugins declared it - a site that had these in Mongo must
127
+ // not silently switch to files and lose sight of its rows. Sites with no
128
+ // connections.json fall back to the file adapter as they always did.
129
+ {
130
+ "slug": "notes-notes",
131
+ "title": "Notes",
132
+ "description": "Personal notes, one row per note, scoped to its author.",
133
+ "preset": true,
134
+ "fields": [
135
+ {
136
+ "name": "title",
137
+ "label": "Title",
138
+ "type": "text",
139
+ "required": false
140
+ },
141
+ {
142
+ "name": "content",
143
+ "label": "Content",
144
+ "type": "textarea",
145
+ "required": false
146
+ },
147
+ {
148
+ "name": "categories",
149
+ "label": "Categories",
150
+ "type": "text",
151
+ "required": false
152
+ },
153
+ {
154
+ "name": "userId",
155
+ "label": "User ID",
156
+ "type": "text",
157
+ "required": false
158
+ }
159
+ ],
160
+ "api": {
161
+ "create": {
162
+ "enabled": true,
163
+ "access": "admin"
164
+ },
165
+ "read": {
166
+ "enabled": true,
167
+ "access": "admin"
168
+ },
169
+ "update": {
170
+ "enabled": false,
171
+ "access": "admin"
172
+ },
173
+ "delete": {
174
+ "enabled": false,
175
+ "access": "admin"
176
+ }
177
+ },
178
+ "storage": {
179
+ "adapter": "mongodb",
180
+ "connection": "default"
181
+ }
182
+ },
183
+ {
184
+ "slug": "todo-items",
185
+ "title": "Todo Items",
186
+ "description": "Personal tasks, one row per task, scoped to its owner.",
187
+ "preset": true,
188
+ "fields": [
189
+ {
190
+ "name": "text",
191
+ "label": "Text",
192
+ "type": "text",
193
+ "required": true
194
+ },
195
+ {
196
+ "name": "status",
197
+ "label": "Status",
198
+ "type": "text",
199
+ "required": false
200
+ },
201
+ {
202
+ "name": "priority",
203
+ "label": "Priority",
204
+ "type": "text",
205
+ "required": false
206
+ },
207
+ {
208
+ "name": "dueAt",
209
+ "label": "Due At",
210
+ "type": "text",
211
+ "required": false
212
+ },
213
+ {
214
+ "name": "userId",
215
+ "label": "User ID",
216
+ "type": "text",
217
+ "required": false
218
+ }
219
+ ],
220
+ "api": {
221
+ "create": {
222
+ "enabled": true,
223
+ "access": "admin"
224
+ },
225
+ "read": {
226
+ "enabled": true,
227
+ "access": "admin"
228
+ },
229
+ "update": {
230
+ "enabled": false,
231
+ "access": "admin"
232
+ },
233
+ "delete": {
234
+ "enabled": false,
235
+ "access": "admin"
236
+ }
237
+ },
238
+ "storage": {
239
+ "adapter": "mongodb",
240
+ "connection": "default"
241
+ }
242
+ },
243
+ // ---- Contacts, promoted out of plugins/ (0.67.0) ----------------------
244
+ // Slugs are contacts-contacts and contacts-groups - NOT the directories the
245
+ // schemas lived in (user-contacts, user-contact-groups). Taken verbatim from
246
+ // those files so none of the 25 contact fields is lost in transcription, and
247
+ // `storage` stays `file` exactly as it was.
248
+ {
249
+ "slug": "contacts-contacts",
250
+ "title": "Contacts",
251
+ "description": "Contacts managed by the Contacts plugin.",
252
+ "fields": [
253
+ {
254
+ "name": "name",
255
+ "label": "Name",
256
+ "type": "text",
257
+ "required": true
258
+ },
259
+ {
260
+ "name": "email",
261
+ "label": "Email",
262
+ "type": "text",
263
+ "required": false
264
+ },
265
+ {
266
+ "name": "phone",
267
+ "label": "Phone",
268
+ "type": "text",
269
+ "required": false
270
+ },
271
+ {
272
+ "name": "groups",
273
+ "label": "Groups",
274
+ "type": "text",
275
+ "required": false
276
+ },
277
+ {
278
+ "name": "notes",
279
+ "label": "Notes",
280
+ "type": "textarea",
281
+ "required": false
282
+ },
283
+ {
284
+ "name": "favourite",
285
+ "label": "Favourite",
286
+ "type": "text",
287
+ "required": false
288
+ },
289
+ {
290
+ "name": "company",
291
+ "label": "Company",
292
+ "type": "text",
293
+ "required": false
294
+ },
295
+ {
296
+ "name": "jobTitle",
297
+ "label": "Job Title",
298
+ "type": "text",
299
+ "required": false
300
+ },
301
+ {
302
+ "name": "website",
303
+ "label": "Website",
304
+ "type": "text",
305
+ "required": false
306
+ },
307
+ {
308
+ "name": "vatNumber",
309
+ "label": "VAT Number",
310
+ "type": "text",
311
+ "required": false
312
+ },
313
+ {
314
+ "name": "companyNumber",
315
+ "label": "Company Number",
316
+ "type": "text",
317
+ "required": false
318
+ },
319
+ {
320
+ "name": "reference",
321
+ "label": "Their Reference",
322
+ "type": "text",
323
+ "required": false
324
+ },
325
+ {
326
+ "name": "billingEmail",
327
+ "label": "Billing Email",
328
+ "type": "text",
329
+ "required": false
330
+ },
331
+ {
332
+ "name": "paymentTerms",
333
+ "label": "Payment Terms (days)",
334
+ "type": "number",
335
+ "required": false
336
+ },
337
+ {
338
+ "name": "currency",
339
+ "label": "Preferred Currency",
340
+ "type": "text",
341
+ "required": false
342
+ },
343
+ {
344
+ "name": "addresses",
345
+ "label": "Addresses",
346
+ "type": "json",
347
+ "required": false
348
+ },
349
+ {
350
+ "name": "emails",
351
+ "label": "Additional Emails",
352
+ "type": "json",
353
+ "required": false
354
+ },
355
+ {
356
+ "name": "phones",
357
+ "label": "Additional Phones",
358
+ "type": "json",
359
+ "required": false
360
+ },
361
+ {
362
+ "name": "accountName",
363
+ "label": "Account Name",
364
+ "type": "text",
365
+ "required": false
366
+ },
367
+ {
368
+ "name": "bankName",
369
+ "label": "Bank",
370
+ "type": "text",
371
+ "required": false
372
+ },
373
+ {
374
+ "name": "sortCode",
375
+ "label": "Sort Code",
376
+ "type": "text",
377
+ "required": false
378
+ },
379
+ {
380
+ "name": "accountNumber",
381
+ "label": "Account Number",
382
+ "type": "text",
383
+ "required": false
384
+ },
385
+ {
386
+ "name": "iban",
387
+ "label": "IBAN",
388
+ "type": "text",
389
+ "required": false
390
+ },
391
+ {
392
+ "name": "bic",
393
+ "label": "BIC / SWIFT",
394
+ "type": "text",
395
+ "required": false
396
+ },
397
+ {
398
+ "name": "userId",
399
+ "label": "User ID",
400
+ "type": "text",
401
+ "required": false
402
+ }
403
+ ],
404
+ "api": {
405
+ "create": {
406
+ "enabled": true,
407
+ "access": "admin"
408
+ },
409
+ "read": {
410
+ "enabled": true,
411
+ "access": "admin"
412
+ },
413
+ "update": {
414
+ "enabled": false,
415
+ "access": "admin"
416
+ },
417
+ "delete": {
418
+ "enabled": false,
419
+ "access": "admin"
420
+ }
421
+ },
422
+ "storage": {
423
+ "adapter": "file"
424
+ },
425
+ "preset": true
426
+ },
427
+ {
428
+ "slug": "contacts-groups",
429
+ "title": "Contact Groups",
430
+ "description": "Groups for the Contacts plugin.",
431
+ "fields": [
432
+ {
433
+ "name": "name",
434
+ "label": "Group Name",
435
+ "type": "text",
436
+ "required": true
437
+ }
438
+ ],
439
+ "api": {
440
+ "create": {
441
+ "enabled": true,
442
+ "access": "admin"
443
+ },
444
+ "read": {
445
+ "enabled": true,
446
+ "access": "admin"
447
+ },
448
+ "update": {
449
+ "enabled": false,
450
+ "access": "admin"
451
+ },
452
+ "delete": {
453
+ "enabled": false,
454
+ "access": "admin"
455
+ }
456
+ },
457
+ "storage": {
458
+ "adapter": "file"
459
+ },
460
+ "preset": true
461
+ }
118
462
  ];
119
463
 
120
464
  /** Slugs exported for use in adapterRegistry and the delete guard. */