@yunsoft/yuncms-api 0.1.9 → 0.1.11

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.
@@ -0,0 +1,80 @@
1
+ import express from 'express';
2
+
3
+ function authError() {
4
+ const error = new Error('Yapay Zeka requires an authenticated YunCMS account');
5
+ error.code = 'UNAUTHORIZED';
6
+ return error;
7
+ }
8
+
9
+ function requireAuthenticated(req) {
10
+ if (req?.authMethod === 'public' || !req?.accountability?.user) throw authError();
11
+ }
12
+
13
+ function requireAdministrator(req) {
14
+ requireAuthenticated(req);
15
+ if (req.accountability?.admin === true || req.accountability?.system === true) return;
16
+ const error = new Error('Yapay Zeka settings require administrator access');
17
+ error.code = 'FORBIDDEN';
18
+ throw error;
19
+ }
20
+
21
+ export function aiRequestAccess(body = {}) {
22
+ const allowWrites = body?.allow_writes === true;
23
+ return {
24
+ allowWrites,
25
+ allowDeletes: allowWrites && body?.allow_deletes === true,
26
+ };
27
+ }
28
+
29
+ export function createAiRouter({ assistant, settingsStore } = {}) {
30
+ if (!assistant) throw new Error('AI assistant service is required');
31
+ if (!settingsStore) throw new Error('AI settings store is required');
32
+ const router = express.Router();
33
+
34
+ router.get('/status', async (req, res, next) => {
35
+ try {
36
+ requireAuthenticated(req);
37
+ return res.json({ data: await assistant.status(), request_id: req.id });
38
+ } catch (error) {
39
+ return next(error);
40
+ }
41
+ });
42
+
43
+ router.get('/settings', async (req, res, next) => {
44
+ try {
45
+ requireAdministrator(req);
46
+ return res.json({ data: await settingsStore.readAdmin(), request_id: req.id });
47
+ } catch (error) {
48
+ return next(error);
49
+ }
50
+ });
51
+
52
+ router.patch('/settings', async (req, res, next) => {
53
+ try {
54
+ requireAdministrator(req);
55
+ const data = await settingsStore.update(req.body ?? {});
56
+ return res.json({ data, request_id: req.id });
57
+ } catch (error) {
58
+ return next(error);
59
+ }
60
+ });
61
+
62
+ router.post('/chat', async (req, res, next) => {
63
+ try {
64
+ requireAuthenticated(req);
65
+ const access = aiRequestAccess(req.body);
66
+ const result = await assistant.chat(req, {
67
+ messages: req.body?.messages,
68
+ locale: req.body?.locale,
69
+ ...access,
70
+ });
71
+ return res.json({ data: result, request_id: req.id });
72
+ } catch (error) {
73
+ return next(error);
74
+ }
75
+ });
76
+
77
+ return router;
78
+ }
79
+
80
+ export { requireAdministrator, requireAuthenticated };
@@ -0,0 +1,59 @@
1
+ import express from 'express';
2
+
3
+ import { serviceOptionsFromRequest } from '../service-options.js';
4
+
5
+ function navigationService(req) {
6
+ const Service = req.context.services.NavigationGroupsService;
7
+ return new Service(serviceOptionsFromRequest(req));
8
+ }
9
+
10
+ function requireStudioUser(req) {
11
+ if (req?.authMethod !== 'public' && req?.accountability?.user) return;
12
+ const error = new Error('Studio navigation requires an authenticated YunCMS account');
13
+ error.code = 'UNAUTHORIZED';
14
+ throw error;
15
+ }
16
+
17
+ export function createStudioNavigationRouter() {
18
+ const router = express.Router();
19
+
20
+ router.get('/groups', async (req, res, next) => {
21
+ try {
22
+ requireStudioUser(req);
23
+ return res.json({ data: await navigationService(req).readMany(), request_id: req.id });
24
+ } catch (error) {
25
+ return next(error);
26
+ }
27
+ });
28
+
29
+ router.post('/groups', async (req, res, next) => {
30
+ try {
31
+ requireStudioUser(req);
32
+ return res.status(201).json({ data: await navigationService(req).createOne(req.body ?? {}), request_id: req.id });
33
+ } catch (error) {
34
+ return next(error);
35
+ }
36
+ });
37
+
38
+ router.patch('/groups/:id', async (req, res, next) => {
39
+ try {
40
+ requireStudioUser(req);
41
+ return res.json({ data: await navigationService(req).updateOne(req.params.id, req.body ?? {}), request_id: req.id });
42
+ } catch (error) {
43
+ return next(error);
44
+ }
45
+ });
46
+
47
+ router.delete('/groups/:id', async (req, res, next) => {
48
+ try {
49
+ requireStudioUser(req);
50
+ return res.json({ data: await navigationService(req).deleteOne(req.params.id), request_id: req.id });
51
+ } catch (error) {
52
+ return next(error);
53
+ }
54
+ });
55
+
56
+ return router;
57
+ }
58
+
59
+ export { requireStudioUser };
package/src/server.js CHANGED
@@ -20,11 +20,16 @@ import {
20
20
  SmtpMailer,
21
21
  } from '@yunsoft/yuncms-core';
22
22
  import { createApp } from './app.js';
23
+ import { AiAssistantService } from './ai/service.js';
24
+ import { loadOrCreateAiSettingsKey } from './ai/secret-key.js';
25
+ import { AiSettingsStore } from './ai/settings-store.js';
23
26
  import { INTERNAL_AUDIT_EVENTS } from './audit-events.js';
24
27
  import { loadExternalAuthConfig } from './external-auth/config.js';
25
28
  import { ExternalAuthProviderRegistry } from './external-auth/providers.js';
26
29
  import { loadExtensionRuntime } from './extensions/runtime.js';
27
30
  import { createMcpRouter } from './mcp.js';
31
+ import { McpSettingsStore } from './mcp/settings-store.js';
32
+ import { createAiRouter } from './routes/ai.js';
28
33
 
29
34
  loadEnvFileIfPresent();
30
35
  await assertMaintenanceStartupAllowed({ cwd: process.cwd(), env: process.env });
@@ -103,6 +108,10 @@ function registerInternalAudit({ emitter, services }) {
103
108
 
104
109
  async function start() {
105
110
  await assertDatabaseCompatible(pool);
111
+ const aiKey = await loadOrCreateAiSettingsKey();
112
+ const aiSettingsStore = new AiSettingsStore({ database: pool, key: aiKey.key });
113
+ if (aiKey.created) logger.info('YunCMS AI settings encryption key created');
114
+
106
115
  if (redisClient) {
107
116
  try {
108
117
  await redisClient.connect();
@@ -124,7 +133,10 @@ async function start() {
124
133
  database: pool,
125
134
  logger,
126
135
  });
127
- const mcpRouter = createMcpRouter({ config, logger });
136
+ const aiAssistant = new AiAssistantService({ settingsStore: aiSettingsStore, logger });
137
+ const aiRouter = createAiRouter({ assistant: aiAssistant, settingsStore: aiSettingsStore });
138
+ const mcpSettingsStore = new McpSettingsStore({ database: pool });
139
+ const mcpRouter = createMcpRouter({ settingsStore: mcpSettingsStore, logger });
128
140
  mailer?.setEmitter(emitter);
129
141
  registerInternalAudit({ emitter, services });
130
142
 
@@ -152,6 +164,7 @@ async function start() {
152
164
  redisClient,
153
165
  externalAuthRegistry,
154
166
  endpointExtensions: extensionRuntime.endpointExtensions,
167
+ aiRouter,
155
168
  mcpRouter,
156
169
  });
157
170