@yunsoft/yuncms-core 0.1.17 → 0.1.19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunsoft/yuncms-core",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Core services, schema, authentication, permissions and storage primitives for YunCMS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/bootstrap.js CHANGED
@@ -19,6 +19,7 @@ import { navigationGroupCollapseMigration } from './migrations/0016-navigation-g
19
19
  import { mcpSettingsMigration } from './migrations/0017-mcp-settings.js';
20
20
  import { publicRegistrationSettingsMigration } from './migrations/0018-public-registration-settings.js';
21
21
  import { publicRegistrationEmailVerificationMigration } from './migrations/0019-public-registration-email-verification.js';
22
+ import { studioLocalesMigration } from './migrations/0020-studio-locales.js';
22
23
  import { readSchemaVersion } from './schema-version.js';
23
24
  import { ensurePublicRole } from './setup.js';
24
25
 
@@ -42,6 +43,7 @@ export const CORE_MIGRATIONS = Object.freeze([
42
43
  mcpSettingsMigration,
43
44
  publicRegistrationSettingsMigration,
44
45
  publicRegistrationEmailVerificationMigration,
46
+ studioLocalesMigration,
45
47
  ]);
46
48
 
47
49
  export const REQUIRED_CORE_MIGRATION_IDS = Object.freeze(
@@ -0,0 +1,9 @@
1
+ export const studioLocalesMigration = {
2
+ id: '0020-studio-locales',
3
+ statements: [
4
+ `ALTER TABLE yuncms_studio_settings
5
+ DROP CHECK chk_yuncms_studio_settings_locale,
6
+ ADD CONSTRAINT chk_yuncms_studio_settings_locale
7
+ CHECK (default_locale IN ('en', 'tr', 'es', 'de', 'fr', 'pt-BR', 'ja', 'zh-CN'))`,
8
+ ],
9
+ };
@@ -95,6 +95,38 @@ function mergeSystemEntries(entries, schema, accountability, operation, now = ne
95
95
  return [...entries, ...systemMutationEntries(schema, accountability, operation, now)];
96
96
  }
97
97
 
98
+ function databaseMutationValue(field, value, schema) {
99
+ if (value == null) return value;
100
+ const type = schema.fields[field]?.type;
101
+
102
+ if (type === 'json') {
103
+ if (typeof value === 'string') {
104
+ try {
105
+ JSON.parse(value);
106
+ return value;
107
+ } catch {
108
+ return JSON.stringify(value);
109
+ }
110
+ }
111
+ return JSON.stringify(value);
112
+ }
113
+
114
+ if (
115
+ (type === 'datetime' || type === 'timestamp')
116
+ && typeof value === 'string'
117
+ && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(value)
118
+ ) {
119
+ const date = new Date(value);
120
+ if (!Number.isNaN(date.getTime())) return date;
121
+ }
122
+
123
+ return value;
124
+ }
125
+
126
+ function databaseMutationParams(entries, schema) {
127
+ return entries.map(([field, value]) => databaseMutationValue(field, value, schema));
128
+ }
129
+
98
130
  export class ItemsService extends BaseService {
99
131
  constructor(collection, options = {}) {
100
132
  super(options);
@@ -372,7 +404,7 @@ export class ItemsService extends BaseService {
372
404
  const table = quoteIdentifier(this.collection, 'collection name');
373
405
  await this.database.query(
374
406
  `INSERT INTO ${table} (${fields.map((field) => quoteIdentifier(field, 'field name')).join(', ')})\n VALUES (${fields.map(() => '?').join(', ')})`,
375
- fields.map((field) => values[field]),
407
+ databaseMutationParams(fields.map((field) => [field, values[field]]), schema),
376
408
  );
377
409
 
378
410
  const record = await this.returnCreatedOrUpdated(id, schema);
@@ -408,7 +440,10 @@ export class ItemsService extends BaseService {
408
440
  const fields = Object.keys(entry.values);
409
441
  await connection.query(
410
442
  `INSERT INTO ${table} (${fields.map((field) => quoteIdentifier(field, 'field name')).join(', ')})\n VALUES (${fields.map(() => '?').join(', ')})`,
411
- fields.map((field) => entry.values[field]),
443
+ databaseMutationParams(
444
+ fields.map((field) => [field, entry.values[field]]),
445
+ schema,
446
+ ),
412
447
  );
413
448
  }
414
449
  });
@@ -448,7 +483,7 @@ export class ItemsService extends BaseService {
448
483
  const setSql = entries.map(([field]) => `${quoteIdentifier(field, 'field name')} = ?`).join(', ');
449
484
  const [result] = await this.database.query(
450
485
  `UPDATE ${table} SET ${setSql}${filter.sql}`,
451
- [...entries.map(([, value]) => value), ...filter.params],
486
+ [...databaseMutationParams(entries, schema), ...filter.params],
452
487
  );
453
488
  if (result.affectedRows === 0) return null;
454
489
  const record = await this.returnCreatedOrUpdated(id, schema);
@@ -490,7 +525,7 @@ export class ItemsService extends BaseService {
490
525
  const setSql = entries.map(([field]) => `${quoteIdentifier(field, 'field name')} = ?`).join(', ');
491
526
  const [result] = await this.database.query(
492
527
  `UPDATE ${table} SET ${setSql}${filter.sql}`,
493
- [...entries.map(([, value]) => value), ...filter.params],
528
+ [...databaseMutationParams(entries, schema), ...filter.params],
494
529
  );
495
530
  await this.actionMutation('items.update', { filter: filterInput, changes: effectiveChanges, affected: result.affectedRows }, { operation: 'update', bulk: true });
496
531
  return result.affectedRows;
@@ -1,7 +1,7 @@
1
+ import { canonicalStudioLocale, STUDIO_LOCALE_CODES } from '../studio-locales.js';
1
2
  import { BaseService } from './base-service.js';
2
3
 
3
4
  const THEMES = new Set(['system', 'light', 'dark']);
4
- const LOCALES = new Set(['en', 'tr']);
5
5
  const ACCENT_PATTERN = /^#[0-9a-f]{6}$/i;
6
6
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
7
  const YUNSOFT_LOGOS = new Set([
@@ -63,8 +63,10 @@ function normalizeTheme(value) {
63
63
  }
64
64
 
65
65
  function normalizeLocale(value) {
66
- const normalized = String(value ?? '').trim().toLowerCase();
67
- if (!LOCALES.has(normalized)) throw invalid('Default locale must be en or tr');
66
+ const normalized = canonicalStudioLocale(value);
67
+ if (!normalized) {
68
+ throw invalid(`Default locale must be one of: ${STUDIO_LOCALE_CODES.join(', ')}`);
69
+ }
68
70
  return normalized;
69
71
  }
70
72
 
@@ -293,4 +295,4 @@ export const STUDIO_SETTING_DEFAULTS = Object.freeze({
293
295
  public_registration_enabled: false,
294
296
  public_registration_role: null,
295
297
  public_registration_require_email_verification: false,
296
- });
298
+ });
@@ -0,0 +1,9 @@
1
+ export const STUDIO_LOCALE_CODES = Object.freeze(['en', 'tr', 'es', 'de', 'fr', 'pt-BR', 'ja', 'zh-CN']);
2
+
3
+ const STUDIO_LOCALE_LOOKUP = new Map(
4
+ STUDIO_LOCALE_CODES.map((locale) => [locale.toLowerCase(), locale]),
5
+ );
6
+
7
+ export function canonicalStudioLocale(value) {
8
+ return STUDIO_LOCALE_LOOKUP.get(String(value ?? '').trim().toLowerCase()) ?? null;
9
+ }