@reverso/api 0.1.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 +47 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/auth.d.ts +72 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +173 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/plugins/database.d.ts +19 -0
- package/dist/plugins/database.d.ts.map +1 -0
- package/dist/plugins/database.js +23 -0
- package/dist/plugins/database.js.map +1 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +5 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +258 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/content.d.ts +8 -0
- package/dist/routes/content.d.ts.map +1 -0
- package/dist/routes/content.js +339 -0
- package/dist/routes/content.js.map +1 -0
- package/dist/routes/forms.d.ts +8 -0
- package/dist/routes/forms.d.ts.map +1 -0
- package/dist/routes/forms.js +953 -0
- package/dist/routes/forms.js.map +1 -0
- package/dist/routes/index.d.ts +19 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +31 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/media.d.ts +8 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +400 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/pages.d.ts +8 -0
- package/dist/routes/pages.d.ts.map +1 -0
- package/dist/routes/pages.js +220 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/routes/redirects.d.ts +8 -0
- package/dist/routes/redirects.d.ts.map +1 -0
- package/dist/routes/redirects.js +462 -0
- package/dist/routes/redirects.js.map +1 -0
- package/dist/routes/schema.d.ts +8 -0
- package/dist/routes/schema.d.ts.map +1 -0
- package/dist/routes/schema.js +151 -0
- package/dist/routes/schema.js.map +1 -0
- package/dist/routes/sitemap.d.ts +8 -0
- package/dist/routes/sitemap.d.ts.map +1 -0
- package/dist/routes/sitemap.js +144 -0
- package/dist/routes/sitemap.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +218 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/security.d.ts +32 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +154 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/validation.d.ts +402 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +308 -0
- package/dist/validation.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication routes.
|
|
3
|
+
*/
|
|
4
|
+
import bcrypt from 'bcrypt';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import { getUserByEmail, getAccountByUserId, createUser, createAccount, createSession, deleteSessionByToken, getSessionWithUser, isLockedOut, recordFailedLoginAttempt, clearLoginAttempts, } from '@reverso/db';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
const SALT_ROUNDS = 12;
|
|
9
|
+
const SESSION_DURATION_DAYS = 30;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a secure session token.
|
|
12
|
+
*/
|
|
13
|
+
function generateSessionToken() {
|
|
14
|
+
return crypto.randomBytes(32).toString('hex');
|
|
15
|
+
}
|
|
16
|
+
// Validation schemas
|
|
17
|
+
const loginSchema = z.object({
|
|
18
|
+
email: z.string().email('Invalid email format'),
|
|
19
|
+
password: z.string().min(1, 'Password is required'),
|
|
20
|
+
});
|
|
21
|
+
const registerSchema = z.object({
|
|
22
|
+
email: z.string().email('Invalid email format'),
|
|
23
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
24
|
+
name: z.string().min(1, 'Name is required'),
|
|
25
|
+
});
|
|
26
|
+
export default async function authRoutes(fastify) {
|
|
27
|
+
/**
|
|
28
|
+
* POST /auth/login - Login with email and password.
|
|
29
|
+
*/
|
|
30
|
+
fastify.post('/auth/login', async (request, reply) => {
|
|
31
|
+
const db = request.db;
|
|
32
|
+
const ip = request.ip;
|
|
33
|
+
// Check lockout (persistent in database)
|
|
34
|
+
const lockoutKey = `login:${ip}`;
|
|
35
|
+
const lockoutStatus = await isLockedOut(db, lockoutKey);
|
|
36
|
+
if (lockoutStatus.locked) {
|
|
37
|
+
return reply.status(429).send({
|
|
38
|
+
success: false,
|
|
39
|
+
error: 'Too many failed attempts',
|
|
40
|
+
message: `Account temporarily locked. Try again in ${Math.ceil(lockoutStatus.remainingSeconds / 60)} minutes.`,
|
|
41
|
+
retryAfter: lockoutStatus.remainingSeconds,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Validate input
|
|
45
|
+
const parseResult = loginSchema.safeParse(request.body);
|
|
46
|
+
if (!parseResult.success) {
|
|
47
|
+
return reply.status(400).send({
|
|
48
|
+
success: false,
|
|
49
|
+
error: 'Validation error',
|
|
50
|
+
details: parseResult.error.issues,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const { email, password } = parseResult.data;
|
|
54
|
+
// Find user
|
|
55
|
+
const user = await getUserByEmail(db, email);
|
|
56
|
+
if (!user) {
|
|
57
|
+
await recordFailedLoginAttempt(db, lockoutKey);
|
|
58
|
+
return reply.status(401).send({
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'Invalid credentials',
|
|
61
|
+
message: 'Email or password is incorrect',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Get account with password
|
|
65
|
+
const account = await getAccountByUserId(db, user.id, 'credential');
|
|
66
|
+
if (!account || !account.password) {
|
|
67
|
+
await recordFailedLoginAttempt(db, lockoutKey);
|
|
68
|
+
return reply.status(401).send({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'Invalid credentials',
|
|
71
|
+
message: 'Email or password is incorrect',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Verify password
|
|
75
|
+
const passwordValid = await bcrypt.compare(password, account.password);
|
|
76
|
+
if (!passwordValid) {
|
|
77
|
+
await recordFailedLoginAttempt(db, lockoutKey);
|
|
78
|
+
return reply.status(401).send({
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'Invalid credentials',
|
|
81
|
+
message: 'Email or password is incorrect',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Clear failed attempts on successful login
|
|
85
|
+
await clearLoginAttempts(db, lockoutKey);
|
|
86
|
+
// Create session
|
|
87
|
+
const token = generateSessionToken();
|
|
88
|
+
const expiresAt = new Date(Date.now() + SESSION_DURATION_DAYS * 24 * 60 * 60 * 1000);
|
|
89
|
+
const session = await createSession(db, {
|
|
90
|
+
id: crypto.randomUUID(),
|
|
91
|
+
userId: user.id,
|
|
92
|
+
token,
|
|
93
|
+
expiresAt,
|
|
94
|
+
ipAddress: ip,
|
|
95
|
+
userAgent: request.headers['user-agent'] ?? null,
|
|
96
|
+
createdAt: new Date(),
|
|
97
|
+
updatedAt: new Date(),
|
|
98
|
+
});
|
|
99
|
+
// Set cookie
|
|
100
|
+
reply.setCookie('reverso_session', token, {
|
|
101
|
+
httpOnly: true,
|
|
102
|
+
secure: process.env.NODE_ENV === 'production',
|
|
103
|
+
sameSite: 'lax',
|
|
104
|
+
path: '/',
|
|
105
|
+
expires: expiresAt,
|
|
106
|
+
});
|
|
107
|
+
return reply.send({
|
|
108
|
+
success: true,
|
|
109
|
+
user: {
|
|
110
|
+
id: user.id,
|
|
111
|
+
email: user.email,
|
|
112
|
+
name: user.name,
|
|
113
|
+
role: user.role,
|
|
114
|
+
image: user.image,
|
|
115
|
+
},
|
|
116
|
+
session: {
|
|
117
|
+
token,
|
|
118
|
+
expiresAt: session.expiresAt.toISOString(),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* POST /auth/logout - Logout and invalidate session.
|
|
124
|
+
*/
|
|
125
|
+
fastify.post('/auth/logout', async (request, reply) => {
|
|
126
|
+
const db = request.db;
|
|
127
|
+
// Get token from cookie or header
|
|
128
|
+
const token = request.cookies.reverso_session ||
|
|
129
|
+
request.headers.authorization?.replace('Bearer ', '');
|
|
130
|
+
if (token) {
|
|
131
|
+
await deleteSessionByToken(db, token);
|
|
132
|
+
}
|
|
133
|
+
// Clear cookie
|
|
134
|
+
reply.clearCookie('reverso_session', { path: '/' });
|
|
135
|
+
return reply.send({
|
|
136
|
+
success: true,
|
|
137
|
+
message: 'Logged out successfully',
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* GET /auth/me - Get current authenticated user.
|
|
142
|
+
*/
|
|
143
|
+
fastify.get('/auth/me', async (request, reply) => {
|
|
144
|
+
const db = request.db;
|
|
145
|
+
// Get token from cookie or header
|
|
146
|
+
const token = request.cookies.reverso_session ||
|
|
147
|
+
request.headers.authorization?.replace('Bearer ', '');
|
|
148
|
+
if (!token) {
|
|
149
|
+
return reply.status(401).send({
|
|
150
|
+
success: false,
|
|
151
|
+
error: 'Not authenticated',
|
|
152
|
+
message: 'No session token provided',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const result = await getSessionWithUser(db, token);
|
|
156
|
+
if (!result) {
|
|
157
|
+
reply.clearCookie('reverso_session', { path: '/' });
|
|
158
|
+
return reply.status(401).send({
|
|
159
|
+
success: false,
|
|
160
|
+
error: 'Invalid session',
|
|
161
|
+
message: 'Session expired or invalid',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return reply.send({
|
|
165
|
+
success: true,
|
|
166
|
+
user: {
|
|
167
|
+
id: result.user.id,
|
|
168
|
+
email: result.user.email,
|
|
169
|
+
name: result.user.name,
|
|
170
|
+
role: result.user.role,
|
|
171
|
+
image: result.user.image,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
/**
|
|
176
|
+
* POST /auth/register - Register a new user (admin only or first user).
|
|
177
|
+
*/
|
|
178
|
+
fastify.post('/auth/register', async (request, reply) => {
|
|
179
|
+
const db = request.db;
|
|
180
|
+
// Validate input
|
|
181
|
+
const parseResult = registerSchema.safeParse(request.body);
|
|
182
|
+
if (!parseResult.success) {
|
|
183
|
+
return reply.status(400).send({
|
|
184
|
+
success: false,
|
|
185
|
+
error: 'Validation error',
|
|
186
|
+
details: parseResult.error.issues,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const { email, password, name } = parseResult.data;
|
|
190
|
+
// Check if user already exists
|
|
191
|
+
const existingUser = await getUserByEmail(db, email);
|
|
192
|
+
if (existingUser) {
|
|
193
|
+
return reply.status(409).send({
|
|
194
|
+
success: false,
|
|
195
|
+
error: 'User exists',
|
|
196
|
+
message: 'A user with this email already exists',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// Hash password
|
|
200
|
+
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
|
|
201
|
+
// Create user
|
|
202
|
+
const userId = crypto.randomUUID();
|
|
203
|
+
const now = new Date();
|
|
204
|
+
const user = await createUser(db, {
|
|
205
|
+
id: userId,
|
|
206
|
+
email,
|
|
207
|
+
name,
|
|
208
|
+
role: 'admin', // First user is admin
|
|
209
|
+
createdAt: now,
|
|
210
|
+
updatedAt: now,
|
|
211
|
+
});
|
|
212
|
+
// Create credential account
|
|
213
|
+
await createAccount(db, {
|
|
214
|
+
id: crypto.randomUUID(),
|
|
215
|
+
userId: user.id,
|
|
216
|
+
accountId: user.id,
|
|
217
|
+
providerId: 'credential',
|
|
218
|
+
password: hashedPassword,
|
|
219
|
+
createdAt: now,
|
|
220
|
+
updatedAt: now,
|
|
221
|
+
});
|
|
222
|
+
// Auto-login: create session
|
|
223
|
+
const token = generateSessionToken();
|
|
224
|
+
const expiresAt = new Date(Date.now() + SESSION_DURATION_DAYS * 24 * 60 * 60 * 1000);
|
|
225
|
+
await createSession(db, {
|
|
226
|
+
id: crypto.randomUUID(),
|
|
227
|
+
userId: user.id,
|
|
228
|
+
token,
|
|
229
|
+
expiresAt,
|
|
230
|
+
ipAddress: request.ip,
|
|
231
|
+
userAgent: request.headers['user-agent'] ?? null,
|
|
232
|
+
createdAt: now,
|
|
233
|
+
updatedAt: now,
|
|
234
|
+
});
|
|
235
|
+
// Set cookie
|
|
236
|
+
reply.setCookie('reverso_session', token, {
|
|
237
|
+
httpOnly: true,
|
|
238
|
+
secure: process.env.NODE_ENV === 'production',
|
|
239
|
+
sameSite: 'lax',
|
|
240
|
+
path: '/',
|
|
241
|
+
expires: expiresAt,
|
|
242
|
+
});
|
|
243
|
+
return reply.status(201).send({
|
|
244
|
+
success: true,
|
|
245
|
+
user: {
|
|
246
|
+
id: user.id,
|
|
247
|
+
email: user.email,
|
|
248
|
+
name: user.name,
|
|
249
|
+
role: user.role,
|
|
250
|
+
},
|
|
251
|
+
session: {
|
|
252
|
+
token,
|
|
253
|
+
expiresAt: expiresAt.toISOString(),
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/routes/content.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAUnE,QAAA,MAAM,aAAa,EAAE,kBAgXpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content routes.
|
|
3
|
+
* Handles content CRUD operations.
|
|
4
|
+
*/
|
|
5
|
+
import { bulkUpdateContent, getContentByPath, getContentByPathPrefix, getFieldByPath, parseContentValue, publishContent, unpublishContent, upsertContent, } from '@reverso/db';
|
|
6
|
+
import { bulkContentUpdateSchema, contentUpdateSchema, localeQuerySchema, pathParamSchema, slugParamSchema, } from '../validation.js';
|
|
7
|
+
const contentRoutes = async (fastify) => {
|
|
8
|
+
/**
|
|
9
|
+
* GET /content/:path
|
|
10
|
+
* Get content by field path.
|
|
11
|
+
* Requires: viewer, editor, or admin role
|
|
12
|
+
*/
|
|
13
|
+
fastify.get('/content/:path', {
|
|
14
|
+
preHandler: fastify.requireAuth(['viewer', 'editor', 'admin']),
|
|
15
|
+
}, async (request, reply) => {
|
|
16
|
+
try {
|
|
17
|
+
const paramResult = pathParamSchema.safeParse(request.params);
|
|
18
|
+
if (!paramResult.success) {
|
|
19
|
+
return reply.status(400).send({
|
|
20
|
+
success: false,
|
|
21
|
+
error: 'Validation error',
|
|
22
|
+
message: 'Invalid path format. Use dot notation (e.g., home.hero.title)',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const queryResult = localeQuerySchema.safeParse(request.query);
|
|
26
|
+
if (!queryResult.success) {
|
|
27
|
+
return reply.status(400).send({
|
|
28
|
+
success: false,
|
|
29
|
+
error: 'Validation error',
|
|
30
|
+
message: 'Invalid locale format',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const { path } = paramResult.data;
|
|
34
|
+
const { locale } = queryResult.data;
|
|
35
|
+
const db = request.db;
|
|
36
|
+
const content = await getContentByPath(db, path, locale);
|
|
37
|
+
if (!content) {
|
|
38
|
+
// Check if field exists
|
|
39
|
+
const field = await getFieldByPath(db, path);
|
|
40
|
+
if (!field) {
|
|
41
|
+
return reply.status(404).send({
|
|
42
|
+
success: false,
|
|
43
|
+
error: 'Not found',
|
|
44
|
+
message: `Field "${path}" not found`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Field exists but no content yet
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
data: {
|
|
51
|
+
path,
|
|
52
|
+
locale,
|
|
53
|
+
value: null,
|
|
54
|
+
published: false,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
data: {
|
|
61
|
+
id: content.id,
|
|
62
|
+
path,
|
|
63
|
+
locale: content.locale,
|
|
64
|
+
value: parseContentValue(content),
|
|
65
|
+
published: content.published,
|
|
66
|
+
publishedAt: content.publishedAt,
|
|
67
|
+
updatedAt: content.updatedAt,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
fastify.log.error(error, 'Failed to get content');
|
|
73
|
+
return reply.status(500).send({
|
|
74
|
+
success: false,
|
|
75
|
+
error: 'Internal error',
|
|
76
|
+
message: 'Failed to get content',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* PUT /content/:path
|
|
82
|
+
* Update content by field path.
|
|
83
|
+
* Requires: editor or admin role
|
|
84
|
+
*/
|
|
85
|
+
fastify.put('/content/:path', {
|
|
86
|
+
preHandler: fastify.requireAuth(['editor', 'admin']),
|
|
87
|
+
}, async (request, reply) => {
|
|
88
|
+
try {
|
|
89
|
+
const paramResult = pathParamSchema.safeParse(request.params);
|
|
90
|
+
if (!paramResult.success) {
|
|
91
|
+
return reply.status(400).send({
|
|
92
|
+
success: false,
|
|
93
|
+
error: 'Validation error',
|
|
94
|
+
message: 'Invalid path format. Use dot notation (e.g., home.hero.title)',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const bodyResult = contentUpdateSchema.safeParse(request.body);
|
|
98
|
+
if (!bodyResult.success) {
|
|
99
|
+
return reply.status(400).send({
|
|
100
|
+
success: false,
|
|
101
|
+
error: 'Validation error',
|
|
102
|
+
message: bodyResult.error.issues[0]?.message ?? 'Invalid request body',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const { path } = paramResult.data;
|
|
106
|
+
const { value, locale, publish } = bodyResult.data;
|
|
107
|
+
const db = request.db;
|
|
108
|
+
const field = await getFieldByPath(db, path);
|
|
109
|
+
if (!field) {
|
|
110
|
+
return reply.status(404).send({
|
|
111
|
+
success: false,
|
|
112
|
+
error: 'Not found',
|
|
113
|
+
message: `Field "${path}" not found`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Get user ID from auth if available
|
|
117
|
+
const changedBy = request.user?.id;
|
|
118
|
+
const content = await upsertContent(db, {
|
|
119
|
+
fieldId: field.id,
|
|
120
|
+
locale,
|
|
121
|
+
value: value,
|
|
122
|
+
published: publish,
|
|
123
|
+
changedBy,
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
data: {
|
|
128
|
+
id: content.id,
|
|
129
|
+
path,
|
|
130
|
+
locale: content.locale,
|
|
131
|
+
value: parseContentValue(content),
|
|
132
|
+
published: content.published,
|
|
133
|
+
updatedAt: content.updatedAt,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
fastify.log.error(error, 'Failed to update content');
|
|
139
|
+
return reply.status(500).send({
|
|
140
|
+
success: false,
|
|
141
|
+
error: 'Internal error',
|
|
142
|
+
message: 'Failed to update content',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* POST /content/bulk
|
|
148
|
+
* Bulk update content.
|
|
149
|
+
* Requires: editor or admin role
|
|
150
|
+
*/
|
|
151
|
+
fastify.post('/content/bulk', {
|
|
152
|
+
preHandler: fastify.requireAuth(['editor', 'admin']),
|
|
153
|
+
}, async (request, reply) => {
|
|
154
|
+
try {
|
|
155
|
+
const bodyResult = bulkContentUpdateSchema.safeParse(request.body);
|
|
156
|
+
if (!bodyResult.success) {
|
|
157
|
+
return reply.status(400).send({
|
|
158
|
+
success: false,
|
|
159
|
+
error: 'Validation error',
|
|
160
|
+
message: bodyResult.error.issues[0]?.message ?? 'Invalid request body',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const { updates } = bodyResult.data;
|
|
164
|
+
const db = request.db;
|
|
165
|
+
const changedBy = request.user?.id;
|
|
166
|
+
const results = await bulkUpdateContent(db, updates.map((u) => ({
|
|
167
|
+
path: u.path,
|
|
168
|
+
value: u.value,
|
|
169
|
+
locale: u.locale,
|
|
170
|
+
changedBy,
|
|
171
|
+
})));
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
data: {
|
|
175
|
+
updated: results.length,
|
|
176
|
+
items: results.map((c) => ({
|
|
177
|
+
id: c.id,
|
|
178
|
+
locale: c.locale,
|
|
179
|
+
updatedAt: c.updatedAt,
|
|
180
|
+
})),
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
fastify.log.error(error, 'Failed to bulk update content');
|
|
186
|
+
return reply.status(500).send({
|
|
187
|
+
success: false,
|
|
188
|
+
error: 'Internal error',
|
|
189
|
+
message: 'Failed to bulk update content',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* GET /content/page/:slug
|
|
195
|
+
* Get all content for a page.
|
|
196
|
+
* Requires: viewer, editor, or admin role
|
|
197
|
+
*/
|
|
198
|
+
fastify.get('/content/page/:slug', {
|
|
199
|
+
preHandler: fastify.requireAuth(['viewer', 'editor', 'admin']),
|
|
200
|
+
}, async (request, reply) => {
|
|
201
|
+
try {
|
|
202
|
+
const paramResult = slugParamSchema.safeParse(request.params);
|
|
203
|
+
if (!paramResult.success) {
|
|
204
|
+
return reply.status(400).send({
|
|
205
|
+
success: false,
|
|
206
|
+
error: 'Validation error',
|
|
207
|
+
message: 'Invalid slug format',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const queryResult = localeQuerySchema.safeParse(request.query);
|
|
211
|
+
if (!queryResult.success) {
|
|
212
|
+
return reply.status(400).send({
|
|
213
|
+
success: false,
|
|
214
|
+
error: 'Validation error',
|
|
215
|
+
message: 'Invalid locale format',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const { slug } = paramResult.data;
|
|
219
|
+
const { locale } = queryResult.data;
|
|
220
|
+
const db = request.db;
|
|
221
|
+
const results = await getContentByPathPrefix(db, `${slug}.`, locale);
|
|
222
|
+
const contentMap = {};
|
|
223
|
+
for (const { path, content } of results) {
|
|
224
|
+
contentMap[path] = parseContentValue(content);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
data: {
|
|
229
|
+
page: slug,
|
|
230
|
+
locale,
|
|
231
|
+
content: contentMap,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
fastify.log.error(error, 'Failed to get page content');
|
|
237
|
+
return reply.status(500).send({
|
|
238
|
+
success: false,
|
|
239
|
+
error: 'Internal error',
|
|
240
|
+
message: 'Failed to get page content',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
/**
|
|
245
|
+
* POST /content/:path/publish
|
|
246
|
+
* Publish content.
|
|
247
|
+
* Requires: editor or admin role
|
|
248
|
+
*/
|
|
249
|
+
fastify.post('/content/:path/publish', {
|
|
250
|
+
preHandler: fastify.requireAuth(['editor', 'admin']),
|
|
251
|
+
}, async (request, reply) => {
|
|
252
|
+
try {
|
|
253
|
+
const paramResult = pathParamSchema.safeParse(request.params);
|
|
254
|
+
if (!paramResult.success) {
|
|
255
|
+
return reply.status(400).send({
|
|
256
|
+
success: false,
|
|
257
|
+
error: 'Validation error',
|
|
258
|
+
message: 'Invalid path format',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const { path } = paramResult.data;
|
|
262
|
+
const db = request.db;
|
|
263
|
+
const content = await getContentByPath(db, path);
|
|
264
|
+
if (!content) {
|
|
265
|
+
return reply.status(404).send({
|
|
266
|
+
success: false,
|
|
267
|
+
error: 'Not found',
|
|
268
|
+
message: `Content for "${path}" not found`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const published = await publishContent(db, content.id);
|
|
272
|
+
return {
|
|
273
|
+
success: true,
|
|
274
|
+
data: {
|
|
275
|
+
id: published?.id,
|
|
276
|
+
path,
|
|
277
|
+
published: published?.published,
|
|
278
|
+
publishedAt: published?.publishedAt,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
fastify.log.error(error, 'Failed to publish content');
|
|
284
|
+
return reply.status(500).send({
|
|
285
|
+
success: false,
|
|
286
|
+
error: 'Internal error',
|
|
287
|
+
message: 'Failed to publish content',
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
/**
|
|
292
|
+
* POST /content/:path/unpublish
|
|
293
|
+
* Unpublish content.
|
|
294
|
+
* Requires: editor or admin role
|
|
295
|
+
*/
|
|
296
|
+
fastify.post('/content/:path/unpublish', {
|
|
297
|
+
preHandler: fastify.requireAuth(['editor', 'admin']),
|
|
298
|
+
}, async (request, reply) => {
|
|
299
|
+
try {
|
|
300
|
+
const paramResult = pathParamSchema.safeParse(request.params);
|
|
301
|
+
if (!paramResult.success) {
|
|
302
|
+
return reply.status(400).send({
|
|
303
|
+
success: false,
|
|
304
|
+
error: 'Validation error',
|
|
305
|
+
message: 'Invalid path format',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const { path } = paramResult.data;
|
|
309
|
+
const db = request.db;
|
|
310
|
+
const content = await getContentByPath(db, path);
|
|
311
|
+
if (!content) {
|
|
312
|
+
return reply.status(404).send({
|
|
313
|
+
success: false,
|
|
314
|
+
error: 'Not found',
|
|
315
|
+
message: `Content for "${path}" not found`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const unpublished = await unpublishContent(db, content.id);
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
321
|
+
data: {
|
|
322
|
+
id: unpublished?.id,
|
|
323
|
+
path,
|
|
324
|
+
published: unpublished?.published,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
fastify.log.error(error, 'Failed to unpublish content');
|
|
330
|
+
return reply.status(500).send({
|
|
331
|
+
success: false,
|
|
332
|
+
error: 'Internal error',
|
|
333
|
+
message: 'Failed to unpublish content',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
export default contentRoutes;
|
|
339
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/routes/content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forms routes.
|
|
3
|
+
* Handles forms, form fields, and form submissions CRUD operations.
|
|
4
|
+
*/
|
|
5
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
6
|
+
declare const formsRoutes: FastifyPluginAsync;
|
|
7
|
+
export default formsRoutes;
|
|
8
|
+
//# sourceMappingURL=forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["../../src/routes/forms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiCH,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAiBnE,QAAA,MAAM,WAAW,EAAE,kBAihClB,CAAC;AAYF,eAAe,WAAW,CAAC"}
|