@aphexcms/cms-core 0.1.12 → 0.1.14
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/LICENSE +21 -0
- package/dist/api/documents.d.ts +1 -0
- package/dist/api/documents.d.ts.map +1 -1
- package/dist/api/documents.js +9 -1
- package/dist/api/types.d.ts +24 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/auth-hooks.d.ts.map +1 -1
- package/dist/auth/auth-hooks.js +18 -4
- package/dist/cli/generate-types.js +218 -0
- package/dist/cli/index.js +86 -0
- package/dist/components/AdminApp.svelte +15 -12
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentEditor.svelte +60 -14
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ImageField.svelte +22 -13
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +2 -3
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/asset.d.ts +22 -0
- package/dist/db/interfaces/asset.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +25 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/field-validation/utils.d.ts +19 -1
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +33 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +80 -12
- package/dist/lib/auth/provider.js +1 -0
- package/dist/lib/db/index.js +4 -0
- package/dist/lib/db/interfaces/asset.js +1 -0
- package/dist/lib/db/interfaces/document.js +1 -0
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/organization.js +1 -0
- package/dist/lib/db/interfaces/schema.js +1 -0
- package/dist/lib/db/interfaces/user.js +1 -0
- package/dist/lib/email/index.js +4 -0
- package/dist/lib/email/interfaces/email.js +1 -0
- package/dist/lib/field-validation/rule.js +221 -0
- package/dist/lib/field-validation/utils.js +99 -0
- package/dist/lib/storage/interfaces/index.js +2 -0
- package/dist/lib/storage/interfaces/storage.js +1 -0
- package/dist/lib/types/asset.js +2 -0
- package/dist/lib/types/auth.js +41 -0
- package/dist/lib/types/config.js +1 -0
- package/dist/lib/types/document.js +1 -0
- package/dist/lib/types/filters.js +5 -0
- package/dist/lib/types/index.js +9 -0
- package/dist/lib/types/organization.js +3 -0
- package/dist/lib/types/schemas.js +1 -0
- package/dist/lib/types/sidebar.js +1 -0
- package/dist/lib/types/user.js +1 -0
- package/dist/local-api/auth-helpers.d.ts +65 -0
- package/dist/local-api/auth-helpers.d.ts.map +1 -0
- package/dist/local-api/auth-helpers.js +102 -0
- package/dist/local-api/collection-api.d.ts +138 -0
- package/dist/local-api/collection-api.d.ts.map +1 -0
- package/dist/local-api/collection-api.js +276 -0
- package/dist/local-api/index.d.ts +108 -0
- package/dist/local-api/index.d.ts.map +1 -0
- package/dist/local-api/index.js +157 -0
- package/dist/local-api/permissions.d.ts +45 -0
- package/dist/local-api/permissions.d.ts.map +1 -0
- package/dist/local-api/permissions.js +117 -0
- package/dist/local-api/types.d.ts +65 -0
- package/dist/local-api/types.d.ts.map +1 -0
- package/dist/local-api/types.js +4 -0
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +84 -63
- package/dist/routes/documents-publish.d.ts.map +1 -1
- package/dist/routes/documents-publish.js +57 -72
- package/dist/routes/documents-query.d.ts +24 -0
- package/dist/routes/documents-query.d.ts.map +1 -0
- package/dist/routes/documents-query.js +95 -0
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +80 -75
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/types/config.d.ts +18 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/document.d.ts +1 -0
- package/dist/types/document.d.ts.map +1 -1
- package/dist/types/filters.d.ts +173 -0
- package/dist/types/filters.d.ts.map +1 -0
- package/dist/types/filters.js +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/package.json +101 -95
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export class Rule {
|
|
2
|
+
_required = false;
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
_rules = [];
|
|
5
|
+
_level = 'error';
|
|
6
|
+
_message;
|
|
7
|
+
static FIELD_REF = Symbol('fieldReference');
|
|
8
|
+
static valueOfField(path) {
|
|
9
|
+
return {
|
|
10
|
+
__fieldReference: true,
|
|
11
|
+
path
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
valueOfField(path) {
|
|
15
|
+
return Rule.valueOfField(path);
|
|
16
|
+
}
|
|
17
|
+
required() {
|
|
18
|
+
const newRule = this.clone();
|
|
19
|
+
newRule._required = true;
|
|
20
|
+
return newRule;
|
|
21
|
+
}
|
|
22
|
+
optional() {
|
|
23
|
+
const newRule = this.clone();
|
|
24
|
+
newRule._required = false;
|
|
25
|
+
return newRule;
|
|
26
|
+
}
|
|
27
|
+
min(len) {
|
|
28
|
+
const newRule = this.clone();
|
|
29
|
+
newRule._rules.push({ type: 'min', constraint: len });
|
|
30
|
+
return newRule;
|
|
31
|
+
}
|
|
32
|
+
max(len) {
|
|
33
|
+
const newRule = this.clone();
|
|
34
|
+
newRule._rules.push({ type: 'max', constraint: len });
|
|
35
|
+
return newRule;
|
|
36
|
+
}
|
|
37
|
+
length(len) {
|
|
38
|
+
const newRule = this.clone();
|
|
39
|
+
newRule._rules.push({ type: 'length', constraint: len });
|
|
40
|
+
return newRule;
|
|
41
|
+
}
|
|
42
|
+
email() {
|
|
43
|
+
const newRule = this.clone();
|
|
44
|
+
newRule._rules.push({ type: 'email' });
|
|
45
|
+
return newRule;
|
|
46
|
+
}
|
|
47
|
+
uri(options) {
|
|
48
|
+
const newRule = this.clone();
|
|
49
|
+
newRule._rules.push({ type: 'uri', constraint: options });
|
|
50
|
+
return newRule;
|
|
51
|
+
}
|
|
52
|
+
regex(pattern, name) {
|
|
53
|
+
const newRule = this.clone();
|
|
54
|
+
newRule._rules.push({ type: 'regex', constraint: { pattern, name } });
|
|
55
|
+
return newRule;
|
|
56
|
+
}
|
|
57
|
+
positive() {
|
|
58
|
+
const newRule = this.clone();
|
|
59
|
+
newRule._rules.push({ type: 'positive' });
|
|
60
|
+
return newRule;
|
|
61
|
+
}
|
|
62
|
+
negative() {
|
|
63
|
+
const newRule = this.clone();
|
|
64
|
+
newRule._rules.push({ type: 'negative' });
|
|
65
|
+
return newRule;
|
|
66
|
+
}
|
|
67
|
+
integer() {
|
|
68
|
+
const newRule = this.clone();
|
|
69
|
+
newRule._rules.push({ type: 'integer' });
|
|
70
|
+
return newRule;
|
|
71
|
+
}
|
|
72
|
+
greaterThan(num) {
|
|
73
|
+
const newRule = this.clone();
|
|
74
|
+
newRule._rules.push({ type: 'greaterThan', constraint: num });
|
|
75
|
+
return newRule;
|
|
76
|
+
}
|
|
77
|
+
lessThan(num) {
|
|
78
|
+
const newRule = this.clone();
|
|
79
|
+
newRule._rules.push({ type: 'lessThan', constraint: num });
|
|
80
|
+
return newRule;
|
|
81
|
+
}
|
|
82
|
+
custom(fn) {
|
|
83
|
+
const newRule = this.clone();
|
|
84
|
+
newRule._rules.push({ type: 'custom', constraint: fn });
|
|
85
|
+
return newRule;
|
|
86
|
+
}
|
|
87
|
+
error(message) {
|
|
88
|
+
const newRule = this.clone();
|
|
89
|
+
newRule._level = 'error';
|
|
90
|
+
newRule._message = message;
|
|
91
|
+
return newRule;
|
|
92
|
+
}
|
|
93
|
+
warning(message) {
|
|
94
|
+
const newRule = this.clone();
|
|
95
|
+
newRule._level = 'warning';
|
|
96
|
+
newRule._message = message;
|
|
97
|
+
return newRule;
|
|
98
|
+
}
|
|
99
|
+
info(message) {
|
|
100
|
+
const newRule = this.clone();
|
|
101
|
+
newRule._level = 'info';
|
|
102
|
+
newRule._message = message;
|
|
103
|
+
return newRule;
|
|
104
|
+
}
|
|
105
|
+
clone() {
|
|
106
|
+
const newRule = new Rule();
|
|
107
|
+
newRule._required = this._required;
|
|
108
|
+
newRule._rules = [...this._rules];
|
|
109
|
+
newRule._level = this._level;
|
|
110
|
+
newRule._message = this._message;
|
|
111
|
+
return newRule;
|
|
112
|
+
}
|
|
113
|
+
async validate(value, context = {}) {
|
|
114
|
+
const markers = [];
|
|
115
|
+
// Check required
|
|
116
|
+
if (this._required && (value === undefined || value === null || value === '')) {
|
|
117
|
+
markers.push({
|
|
118
|
+
level: this._level,
|
|
119
|
+
message: this._message || 'Required',
|
|
120
|
+
path: context.path
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// If value is empty and not required, skip other validations
|
|
124
|
+
if (!this._required && (value === undefined || value === null || value === '')) {
|
|
125
|
+
return markers;
|
|
126
|
+
}
|
|
127
|
+
// Run other validations
|
|
128
|
+
for (const rule of this._rules) {
|
|
129
|
+
try {
|
|
130
|
+
const result = await this.validateRule(rule, value, context);
|
|
131
|
+
if (result) {
|
|
132
|
+
markers.push({
|
|
133
|
+
level: this._level,
|
|
134
|
+
message: this._message || result,
|
|
135
|
+
path: context.path
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
markers.push({
|
|
141
|
+
level: 'error',
|
|
142
|
+
message: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
143
|
+
path: context.path
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return markers;
|
|
148
|
+
}
|
|
149
|
+
async validateRule(rule, value, context) {
|
|
150
|
+
switch (rule.type) {
|
|
151
|
+
case 'min':
|
|
152
|
+
if (typeof value === 'string' && value.length < rule.constraint) {
|
|
153
|
+
return `Must be at least ${rule.constraint} characters`;
|
|
154
|
+
}
|
|
155
|
+
if (typeof value === 'number' && value < rule.constraint) {
|
|
156
|
+
return `Must be at least ${rule.constraint}`;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'max':
|
|
160
|
+
if (typeof value === 'string' && value.length > rule.constraint) {
|
|
161
|
+
return `Must be at most ${rule.constraint} characters`;
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === 'number' && value > rule.constraint) {
|
|
164
|
+
return `Must be at most ${rule.constraint}`;
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case 'email':
|
|
168
|
+
if (typeof value === 'string' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
169
|
+
return 'Must be a valid email address';
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case 'uri':
|
|
173
|
+
if (typeof value === 'string') {
|
|
174
|
+
try {
|
|
175
|
+
new URL(value);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return 'Must be a valid URL';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
case 'regex':
|
|
183
|
+
if (typeof value === 'string' && !rule.constraint.pattern.test(value)) {
|
|
184
|
+
return `Must match pattern${rule.constraint.name ? ` (${rule.constraint.name})` : ''}`;
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case 'positive':
|
|
188
|
+
if (typeof value === 'number' && value <= 0) {
|
|
189
|
+
return 'Must be positive';
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
case 'negative':
|
|
193
|
+
if (typeof value === 'number' && value >= 0) {
|
|
194
|
+
return 'Must be negative';
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
case 'integer':
|
|
198
|
+
if (typeof value === 'number' && !Number.isInteger(value)) {
|
|
199
|
+
return 'Must be an integer';
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case 'custom': {
|
|
203
|
+
const customResult = await rule.constraint(value, context);
|
|
204
|
+
if (customResult === false) {
|
|
205
|
+
return 'Validation failed';
|
|
206
|
+
}
|
|
207
|
+
if (typeof customResult === 'string') {
|
|
208
|
+
return customResult;
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(customResult) && customResult.length > 0) {
|
|
211
|
+
return customResult[0].message;
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
isRequired() {
|
|
219
|
+
return this._required;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Rule } from './rule.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check if a field is required based on its validation rules
|
|
4
|
+
*/
|
|
5
|
+
export function isFieldRequired(field) {
|
|
6
|
+
if (!field.validation)
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
const validationFn = Array.isArray(field.validation) ? field.validation[0] : field.validation;
|
|
10
|
+
if (!validationFn)
|
|
11
|
+
return false;
|
|
12
|
+
const rule = validationFn(new Rule());
|
|
13
|
+
return rule.isRequired();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validate a field value against its validation rules
|
|
21
|
+
*/
|
|
22
|
+
export async function validateField(field, value, context = {}) {
|
|
23
|
+
if (!field.validation) {
|
|
24
|
+
return { isValid: true, errors: [] };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const validationFunctions = Array.isArray(field.validation)
|
|
28
|
+
? field.validation
|
|
29
|
+
: [field.validation];
|
|
30
|
+
const allErrors = [];
|
|
31
|
+
for (const validationFn of validationFunctions) {
|
|
32
|
+
const rule = validationFn(new Rule());
|
|
33
|
+
if (!(rule instanceof Rule)) {
|
|
34
|
+
console.error(`Validation function for field "${field.name}" did not return a Rule object. Make sure you are chaining validation methods and returning the result.`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const markers = await rule.validate(value, {
|
|
38
|
+
path: [field.name],
|
|
39
|
+
...context
|
|
40
|
+
});
|
|
41
|
+
allErrors.push(...markers.map((marker) => ({
|
|
42
|
+
level: marker.level,
|
|
43
|
+
message: marker.message
|
|
44
|
+
})));
|
|
45
|
+
}
|
|
46
|
+
const isValid = allErrors.filter((e) => e.level === 'error').length === 0;
|
|
47
|
+
return { isValid, errors: allErrors };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error('Validation error:', error);
|
|
51
|
+
return {
|
|
52
|
+
isValid: false,
|
|
53
|
+
errors: [{ level: 'error', message: 'Validation failed' }]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get validation CSS classes for input styling
|
|
59
|
+
*/
|
|
60
|
+
export function getValidationClasses(hasErrors) {
|
|
61
|
+
if (hasErrors) {
|
|
62
|
+
return 'border-destructive border-2';
|
|
63
|
+
}
|
|
64
|
+
// No green styling for success - only show red for errors
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validate an entire document's data against a schema
|
|
69
|
+
* This function validates all fields in a schema against the provided data
|
|
70
|
+
* and returns any validation errors found.
|
|
71
|
+
*
|
|
72
|
+
* @param schema - The schema type containing field definitions
|
|
73
|
+
* @param data - The document data to validate
|
|
74
|
+
* @param context - Optional context to pass to field validators
|
|
75
|
+
* @returns Validation result with isValid flag and array of field errors
|
|
76
|
+
*/
|
|
77
|
+
export async function validateDocumentData(schema, data, context = {}) {
|
|
78
|
+
const validationErrors = [];
|
|
79
|
+
// Validate each field in the schema
|
|
80
|
+
for (const field of schema.fields) {
|
|
81
|
+
const value = data[field.name];
|
|
82
|
+
const result = await validateField(field, value, { ...context, ...data });
|
|
83
|
+
if (!result.isValid) {
|
|
84
|
+
const errorMessages = result.errors
|
|
85
|
+
.filter((e) => e.level === 'error')
|
|
86
|
+
.map((e) => e.message);
|
|
87
|
+
if (errorMessages.length > 0) {
|
|
88
|
+
validationErrors.push({
|
|
89
|
+
field: field.name,
|
|
90
|
+
errors: errorMessages
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
isValid: validationErrors.length === 0,
|
|
97
|
+
errors: validationErrors
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Access control utilities
|
|
2
|
+
/**
|
|
3
|
+
* Check if a user has write permissions based on their organization role
|
|
4
|
+
* Viewers have read-only access
|
|
5
|
+
*/
|
|
6
|
+
export function canWrite(auth) {
|
|
7
|
+
if (auth.type === 'api_key') {
|
|
8
|
+
return auth.permissions.includes('write');
|
|
9
|
+
}
|
|
10
|
+
// Viewers are read-only
|
|
11
|
+
return auth.organizationRole !== 'viewer';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if a user can manage organization members
|
|
15
|
+
* Only owners and admins can manage members
|
|
16
|
+
*/
|
|
17
|
+
export function canManageMembers(auth) {
|
|
18
|
+
if (auth.type === 'api_key') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return auth.organizationRole === 'owner' || auth.organizationRole === 'admin';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a user can manage API keys
|
|
25
|
+
* Only owners and admins can manage API keys
|
|
26
|
+
*/
|
|
27
|
+
export function canManageApiKeys(auth) {
|
|
28
|
+
if (auth.type === 'api_key') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return auth.organizationRole === 'owner' || auth.organizationRole === 'admin';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if a user is a viewer (read-only access)
|
|
35
|
+
*/
|
|
36
|
+
export function isViewer(auth) {
|
|
37
|
+
if (auth.type === 'api_key') {
|
|
38
|
+
return !auth.permissions.includes('write');
|
|
39
|
+
}
|
|
40
|
+
return auth.organizationRole === 'viewer';
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './asset.js';
|
|
2
|
+
export * from './auth.js';
|
|
3
|
+
export * from './document.js';
|
|
4
|
+
export * from './schemas.js';
|
|
5
|
+
export * from './config.js';
|
|
6
|
+
export * from './user.js';
|
|
7
|
+
export * from './sidebar.js';
|
|
8
|
+
export * from './organization.js';
|
|
9
|
+
export * from './filters.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Auth } from '../types/auth.js';
|
|
2
|
+
import type { LocalAPIContext } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Convert SvelteKit locals.auth to LocalAPIContext
|
|
5
|
+
*
|
|
6
|
+
* This helper bridges the gap between the authentication system
|
|
7
|
+
* (handled in the app layer and enriched by handleAuthHook) and
|
|
8
|
+
* the LocalAPI's context-based approach.
|
|
9
|
+
*
|
|
10
|
+
* Preserves the full Auth object in context.auth so custom permission
|
|
11
|
+
* logic can access any custom fields added via module augmentation.
|
|
12
|
+
*
|
|
13
|
+
* @param auth - Auth object from locals.auth (already validated by handleAuthHook)
|
|
14
|
+
* @returns LocalAPIContext for use with LocalAPI operations
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // In a SvelteKit route handler
|
|
19
|
+
* import { authToContext } from '@aphexcms/cms-core/local-api';
|
|
20
|
+
*
|
|
21
|
+
* export const GET: RequestHandler = async ({ locals }) => {
|
|
22
|
+
* const api = locals.aphexCMS.localAPI;
|
|
23
|
+
* const context = authToContext(locals.auth);
|
|
24
|
+
*
|
|
25
|
+
* const pages = await api.collections.page.find(context, {
|
|
26
|
+
* where: { status: { equals: 'published' } }
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* return json({ data: pages });
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function authToContext(auth: Auth | null | undefined): LocalAPIContext;
|
|
34
|
+
/**
|
|
35
|
+
* Check if auth exists and convert to context
|
|
36
|
+
* Alias for authToContext - more semantic in some contexts
|
|
37
|
+
*
|
|
38
|
+
* @param auth - Auth object from locals.auth
|
|
39
|
+
* @returns LocalAPIContext
|
|
40
|
+
* @throws Error if auth is missing or invalid
|
|
41
|
+
*/
|
|
42
|
+
export declare function requireAuth(auth: Auth | null | undefined): LocalAPIContext;
|
|
43
|
+
/**
|
|
44
|
+
* Create a system context for operations that bypass normal permissions
|
|
45
|
+
* Use this for seed scripts, cron jobs, migrations, and other system-level operations
|
|
46
|
+
*
|
|
47
|
+
* @param organizationId - Organization ID for the operation
|
|
48
|
+
* @returns LocalAPIContext with overrideAccess: true
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // In a seed script
|
|
53
|
+
* import { systemContext } from '@aphexcms/cms-core/local-api';
|
|
54
|
+
*
|
|
55
|
+
* const api = getLocalAPI();
|
|
56
|
+
* const context = systemContext('org_123');
|
|
57
|
+
*
|
|
58
|
+
* await api.collections.page.create(context, {
|
|
59
|
+
* data: { title: 'Home', slug: 'home' },
|
|
60
|
+
* publish: true
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function systemContext(organizationId: string): LocalAPIContext;
|
|
65
|
+
//# sourceMappingURL=auth-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/auth-helpers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,CAiC5E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,CAE1E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,eAAe,CAKrE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// local-api/auth-helpers.ts
|
|
2
|
+
//
|
|
3
|
+
// Helper utilities to convert between Auth types and LocalAPIContext
|
|
4
|
+
/**
|
|
5
|
+
* Convert SvelteKit locals.auth to LocalAPIContext
|
|
6
|
+
*
|
|
7
|
+
* This helper bridges the gap between the authentication system
|
|
8
|
+
* (handled in the app layer and enriched by handleAuthHook) and
|
|
9
|
+
* the LocalAPI's context-based approach.
|
|
10
|
+
*
|
|
11
|
+
* Preserves the full Auth object in context.auth so custom permission
|
|
12
|
+
* logic can access any custom fields added via module augmentation.
|
|
13
|
+
*
|
|
14
|
+
* @param auth - Auth object from locals.auth (already validated by handleAuthHook)
|
|
15
|
+
* @returns LocalAPIContext for use with LocalAPI operations
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // In a SvelteKit route handler
|
|
20
|
+
* import { authToContext } from '@aphexcms/cms-core/local-api';
|
|
21
|
+
*
|
|
22
|
+
* export const GET: RequestHandler = async ({ locals }) => {
|
|
23
|
+
* const api = locals.aphexCMS.localAPI;
|
|
24
|
+
* const context = authToContext(locals.auth);
|
|
25
|
+
*
|
|
26
|
+
* const pages = await api.collections.page.find(context, {
|
|
27
|
+
* where: { status: { equals: 'published' } }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* return json({ data: pages });
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function authToContext(auth) {
|
|
35
|
+
if (!auth) {
|
|
36
|
+
throw new Error('Authentication required');
|
|
37
|
+
}
|
|
38
|
+
// For session auth, extract user and organization info
|
|
39
|
+
if (auth.type === 'session') {
|
|
40
|
+
return {
|
|
41
|
+
organizationId: auth.organizationId,
|
|
42
|
+
user: auth.user,
|
|
43
|
+
auth: auth // Preserve full auth for custom permission logic
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// For API key auth, create a synthetic user for permission checking
|
|
47
|
+
// API keys don't have traditional user accounts, but we need user info
|
|
48
|
+
// for the permission system to work correctly
|
|
49
|
+
if (auth.type === 'api_key') {
|
|
50
|
+
return {
|
|
51
|
+
organizationId: auth.organizationId,
|
|
52
|
+
user: {
|
|
53
|
+
id: `apikey:${auth.keyId}`,
|
|
54
|
+
email: `apikey-${auth.name}@system`,
|
|
55
|
+
name: auth.name,
|
|
56
|
+
// Map API key permissions to user roles for permission checking
|
|
57
|
+
role: auth.permissions.includes('write') ? 'editor' : 'viewer'
|
|
58
|
+
},
|
|
59
|
+
auth: auth // Preserve full auth for custom permission logic
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// This should never happen with proper typing, but just in case
|
|
63
|
+
throw new Error('Unknown auth type');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if auth exists and convert to context
|
|
67
|
+
* Alias for authToContext - more semantic in some contexts
|
|
68
|
+
*
|
|
69
|
+
* @param auth - Auth object from locals.auth
|
|
70
|
+
* @returns LocalAPIContext
|
|
71
|
+
* @throws Error if auth is missing or invalid
|
|
72
|
+
*/
|
|
73
|
+
export function requireAuth(auth) {
|
|
74
|
+
return authToContext(auth);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a system context for operations that bypass normal permissions
|
|
78
|
+
* Use this for seed scripts, cron jobs, migrations, and other system-level operations
|
|
79
|
+
*
|
|
80
|
+
* @param organizationId - Organization ID for the operation
|
|
81
|
+
* @returns LocalAPIContext with overrideAccess: true
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // In a seed script
|
|
86
|
+
* import { systemContext } from '@aphexcms/cms-core/local-api';
|
|
87
|
+
*
|
|
88
|
+
* const api = getLocalAPI();
|
|
89
|
+
* const context = systemContext('org_123');
|
|
90
|
+
*
|
|
91
|
+
* await api.collections.page.create(context, {
|
|
92
|
+
* data: { title: 'Home', slug: 'home' },
|
|
93
|
+
* publish: true
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function systemContext(organizationId) {
|
|
98
|
+
return {
|
|
99
|
+
organizationId,
|
|
100
|
+
overrideAccess: true
|
|
101
|
+
};
|
|
102
|
+
}
|