@appweaver/create-weaver-app 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.
@@ -321,11 +321,35 @@ export default createAuthService({
321
321
  });
322
322
  ```
323
323
 
324
- **User avatar** — the provider's avatar/picture URL is passed to `registrationData` as `additionalData.avatarUrl`. When
325
- `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED=true` (JSON: `security.oauth2.fetchAvatarEnabled`), the avatar image is also
326
- downloaded during registration and passed as `additionalData.avatarFile`
327
- (`{ name, mimeType, size, data: Buffer }`), so it can be mapped to a model field or stored via the file service. The
328
- download is best-effort: failures are logged and registration proceeds without the file.
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, injectModel } from '@appweaver/core';
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 'image' field of the 'post' resource.
236
- const file = await this._fileService.saveFile(data, post, this._postClient);
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.
@@ -12,5 +12,6 @@ export default createAuthService<UserCreate>({
12
12
  twoFactorAuth: 'None',
13
13
  roles: [{ id: 1 }]
14
14
  };
15
- }
15
+ },
16
+ registrationFiles: (_, data) => ({ avatar: data?.avatarFile })
16
17
  });