@appweaver/cli 1.3.0 → 1.4.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/generate/generate-schema.js +122 -25
- package/generate/generate-types.js +4 -1
- package/package.json +1 -1
- package/skill/GUIDELINES.md +18 -4
- package/skill/SKILL.md +753 -707
- package/skill/references/cli.md +4 -0
- package/skill/references/client.md +609 -565
- package/skill/references/configuration.md +6 -6
- package/skill/references/resources.md +1424 -1284
- package/skill/references/security.md +29 -5
- package/skill/references/storage.md +38 -4
- package/utils/loader-util.js +3 -0
|
@@ -321,11 +321,35 @@ export default createAuthService({
|
|
|
321
321
|
});
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
-
**User avatar** — the provider's
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
324
|
+
**User avatar** — `registrationData` and `registrationFiles` receive the provider's picture URL as
|
|
325
|
+
`additionalData.avatarUrl` and the downloaded image as `additionalData.avatarFile`
|
|
326
|
+
(`{ name, mimeType, size, data: Buffer }`). `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED=false` (JSON:
|
|
327
|
+
`security.oauth2.fetchAvatarEnabled`) skips the download, leaving `avatarFile` `undefined`. The download is best-effort:
|
|
328
|
+
failures are logged and registration proceeds without the file.
|
|
329
|
+
|
|
330
|
+
**`registrationFiles` callback** — an optional callback on `createAuthService` that attaches the avatar (or any other
|
|
331
|
+
file) to a newly registered user. A file must be linked to an existing resource, so it cannot be part of the
|
|
332
|
+
registration payload and is stored right after the user record is created. Return a map of the model's **file fields**
|
|
333
|
+
to the files to store; nullish values are skipped, so nothing is stored unless the callback asks for it:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
// src/resources/user/service.ts
|
|
337
|
+
export default createAuthService({
|
|
338
|
+
modelName: 'User',
|
|
339
|
+
registrationData: (source, email, password, additionalData) => ({
|
|
340
|
+
email,
|
|
341
|
+
password,
|
|
342
|
+
firstName: additionalData?.firstName ?? ''
|
|
343
|
+
}),
|
|
344
|
+
registrationFiles: (source, additionalData) => ({
|
|
345
|
+
avatar: additionalData?.avatarFile
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The file is validated against the `files.avatar` config of the model (media type, size limit, name pattern, image
|
|
351
|
+
processing). Storing it is the best effort: a rejected file is logged and never fails the registration. Outside
|
|
352
|
+
registration, use [`FileService.saveBuffer()`](./storage.md#saving-an-in-memory-file).
|
|
329
353
|
|
|
330
354
|
### Client-side OAuth2 integration example
|
|
331
355
|
|
|
@@ -220,26 +220,54 @@ When saving a file via `FileService`, you must provide the multipart data, the r
|
|
|
220
220
|
`ResourceClient`.
|
|
221
221
|
|
|
222
222
|
```ts
|
|
223
|
-
import { inject, injectService
|
|
223
|
+
import { inject, injectService } from '@appweaver/core';
|
|
224
224
|
import { FileService } from '@appweaver/core/storage';
|
|
225
225
|
|
|
226
226
|
export class PostService {
|
|
227
227
|
private readonly _fileService = inject(FileService);
|
|
228
228
|
private readonly _postService = injectService('Post');
|
|
229
|
-
private readonly _postClient = injectModel('Post');
|
|
230
229
|
|
|
231
230
|
async uploadImage(postId: number, data: MultipartFile) {
|
|
232
231
|
const post = await this._postService.find(postId);
|
|
233
232
|
|
|
234
233
|
// saveFile stores the file in Storage AND creates a File record in the DB
|
|
235
|
-
// linked to the
|
|
236
|
-
const file = await this._fileService.saveFile(
|
|
234
|
+
// linked to the file field named by the multipart field of the 'post' resource.
|
|
235
|
+
const file = await this._fileService.saveFile(
|
|
236
|
+
data,
|
|
237
|
+
post,
|
|
238
|
+
this._postService.client
|
|
239
|
+
);
|
|
237
240
|
|
|
238
241
|
return file;
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
244
|
```
|
|
242
245
|
|
|
246
|
+
### Saving an in-memory file
|
|
247
|
+
|
|
248
|
+
`saveBuffer()` stores a file that did not arrive as a multipart upload — a downloaded avatar, a generated report, a
|
|
249
|
+
thumbnail. It behaves exactly like `saveFile()` (media type check, size limit, name pattern, image processing,
|
|
250
|
+
checksum, `File` record), except the content comes from a buffer and the target file field is named explicitly:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import { inject, injectService } from '@appweaver/core';
|
|
254
|
+
import { FileService } from '@appweaver/core/storage';
|
|
255
|
+
|
|
256
|
+
const users = injectService('User');
|
|
257
|
+
const user = await users.find(userId);
|
|
258
|
+
|
|
259
|
+
const file = await inject(FileService).saveBuffer(
|
|
260
|
+
'avatar', // the file field of the User model
|
|
261
|
+
{ name: 'avatar.png', mimeType: 'image/png', data: buffer },
|
|
262
|
+
user,
|
|
263
|
+
users.client
|
|
264
|
+
);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The optional `size` (defaults to the buffer length) is what the size limit is checked against, and `encoding` defaults
|
|
268
|
+
to `7bit`. The owning resource must already exist — to attach files while a user is registering, use the
|
|
269
|
+
[`registrationFiles`](./security.md) callback of `createAuthService`.
|
|
270
|
+
|
|
243
271
|
### File integrity (checksum)
|
|
244
272
|
|
|
245
273
|
When a file is uploaded through `FileService.saveFile()` (or the resource file upload routes), a **SHA-256 checksum**
|
|
@@ -326,3 +354,9 @@ You can also call `deleteResourceFiles` manually if needed:
|
|
|
326
354
|
```ts
|
|
327
355
|
await fileService.deleteResourceFiles('Post', postId);
|
|
328
356
|
```
|
|
357
|
+
|
|
358
|
+
### Owning resource reference
|
|
359
|
+
|
|
360
|
+
The `File` model records its owner through the `resourceName`, `resourceField` and `resourceId` columns. `resourceId`
|
|
361
|
+
is a text column holding the owning record ID, so files attach to models with either an integer or a string primary
|
|
362
|
+
key, and `deleteResourceFiles` accepts an ID of either type.
|
package/utils/loader-util.js
CHANGED
|
@@ -128,5 +128,8 @@ async function loadModels(modelPattern) {
|
|
|
128
128
|
if (Object.keys(models).length === 0) {
|
|
129
129
|
console.log('No resource models exports found matching pattern:', modelPattern);
|
|
130
130
|
}
|
|
131
|
+
// Resolve the schemas referencing the primary key of another model, now that
|
|
132
|
+
// every model is registered
|
|
133
|
+
(0, common_1.relinkResourceModels)(models);
|
|
131
134
|
return models;
|
|
132
135
|
}
|