@appweaver/create-weaver-app 1.0.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/LICENSE +1 -0
- package/README.md +7 -0
- package/create-weaver-app.d.ts +2 -0
- package/create-weaver-app.js +266 -0
- package/package.json +37 -0
- package/skill/GUIDELINES.md +298 -0
- package/skill/SKILL.md +593 -0
- package/skill/references/cache.md +207 -0
- package/skill/references/cli.md +213 -0
- package/skill/references/client.md +507 -0
- package/skill/references/configuration.md +402 -0
- package/skill/references/database.md +134 -0
- package/skill/references/dependency-injection.md +214 -0
- package/skill/references/events.md +152 -0
- package/skill/references/mailer.md +235 -0
- package/skill/references/queue.md +196 -0
- package/skill/references/resources.md +961 -0
- package/skill/references/scheduler.md +184 -0
- package/skill/references/security.md +694 -0
- package/skill/references/storage.md +251 -0
- package/templates/default/.dockerignore +5 -0
- package/templates/default/.env.tpl +1 -0
- package/templates/default/.prettierignore +3 -0
- package/templates/default/.prettierrc +7 -0
- package/templates/default/Dockerfile +56 -0
- package/templates/default/Dockerfile.bun +56 -0
- package/templates/default/README.md.tpl +7 -0
- package/templates/default/appweaver.dev.json.tpl +9 -0
- package/templates/default/appweaver.json.bun.tpl +17 -0
- package/templates/default/appweaver.json.tpl +16 -0
- package/templates/default/appweaver.test.json.tpl +30 -0
- package/templates/default/bunfig.toml.bun +8 -0
- package/templates/default/database/client.ts.tpl +7 -0
- package/templates/default/database/schema.prisma +13 -0
- package/templates/default/database/seeders/001-create-admin-user.ts.tpl +39 -0
- package/templates/default/eslint.config.mjs +55 -0
- package/templates/default/eslint.config.mjs.bun +53 -0
- package/templates/default/jest.config.json.node +23 -0
- package/templates/default/package.json.bun.tpl +39 -0
- package/templates/default/package.json.tpl +44 -0
- package/templates/default/prisma.config.ts.tpl +14 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/robots.txt +2 -0
- package/templates/default/src/features/index.ts.tpl +0 -0
- package/templates/default/src/main.ts.tpl +7 -0
- package/templates/default/src/resources/user/model.ts.tpl +28 -0
- package/templates/default/src/resources/user/policy.ts.tpl +3 -0
- package/templates/default/src/resources/user/routes.ts.tpl +3 -0
- package/templates/default/src/resources/user/service.ts.tpl +16 -0
- package/templates/default/src/types/generated.ts.tpl +1 -0
- package/templates/default/src/types/index.ts.tpl +1 -0
- package/templates/default/start.sh +26 -0
- package/templates/default/start.sh.bun +26 -0
- package/templates/default/swc.config.json.node +13 -0
- package/templates/default/test/e2e/jest.e2e-config.json.node +22 -0
- package/templates/default/test/e2e/main.test.ts.tpl +24 -0
- package/templates/default/test/e2e/support/each.ts.tpl +13 -0
- package/templates/default/test/e2e/support/preload.ts.bun +13 -0
- package/templates/default/test/e2e/support/setup.ts.tpl +13 -0
- package/templates/default/test/e2e/support/teardown.ts.tpl +13 -0
- package/templates/default/test/unit/sample.test.ts.tpl +5 -0
- package/templates/default/tsconfig.build.json +10 -0
- package/templates/default/tsconfig.json +27 -0
- package/templates/default/tsconfig.json.bun +28 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
|
|
3
|
+
The storage module handles file persistence: storing, streaming, and deleting binary content. The default
|
|
4
|
+
implementation (`FilesystemStorage`) writes files to a local directory using a configurable name pattern. The module
|
|
5
|
+
supports range-based streaming for efficient large-file delivery (e.g., video, audio).
|
|
6
|
+
|
|
7
|
+
## Injecting Storage
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { inject } from '@appweaver/core';
|
|
11
|
+
import { Storage } from '@appweaver/common';
|
|
12
|
+
|
|
13
|
+
const storage = inject(Storage);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
#### `storage.store(fileName, data)`
|
|
19
|
+
|
|
20
|
+
Persists a readable stream under the given file name. Returns the stored file's path/ID (derived from
|
|
21
|
+
`STORAGE_NAME_PATTERN`), or `null` on failure.
|
|
22
|
+
|
|
23
|
+
| Parameter | Type | Description |
|
|
24
|
+
|------------|------------|--------------------------------------------|
|
|
25
|
+
| `fileName` | `string` | Original file name (used for name pattern) |
|
|
26
|
+
| `data` | `Readable` | Node.js readable stream with the content |
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createReadStream } from 'node:fs';
|
|
30
|
+
|
|
31
|
+
const fileId = await storage.store('avatar.png', createReadStream('/tmp/upload.png'));
|
|
32
|
+
// fileId e.g. "avatar-a3f8bc12.png"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**From a multipart upload (Fastify example):**
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const upload = await req.file();
|
|
39
|
+
const fileId = await storage.store(upload.filename, upload.file);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
#### `storage.stream(fileName, start, end?)`
|
|
45
|
+
|
|
46
|
+
Returns a `ContentStream` for the stored file, or `null` if the file does not exist. Supports range requests.
|
|
47
|
+
|
|
48
|
+
| Parameter | Type | Description |
|
|
49
|
+
|------------|----------|--------------------------------------------|
|
|
50
|
+
| `fileName` | `string` | File ID returned by `store` |
|
|
51
|
+
| `start` | `number` | Byte offset to start reading from |
|
|
52
|
+
| `end` | `number` | Byte offset to stop reading at (inclusive) |
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
type ContentStream = {
|
|
56
|
+
stream: Readable;
|
|
57
|
+
size: number; // total file size in bytes
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Full file download:**
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const result = await storage.stream('avatar-a3f8bc12.png', 0);
|
|
65
|
+
if (!result) throw new Error('File not found');
|
|
66
|
+
|
|
67
|
+
reply.header('Content-Length', result.size);
|
|
68
|
+
reply.send(result.stream);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Range request (e.g., video seeking):**
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const { start, end } = parseRangeHeader(req.headers.range, fileSize);
|
|
75
|
+
const result = await storage.stream(fileId, start, end);
|
|
76
|
+
|
|
77
|
+
reply
|
|
78
|
+
.status(206)
|
|
79
|
+
.header('Content-Range', `bytes ${start}-${end}/${fileSize}`)
|
|
80
|
+
.send(result.stream);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
#### `storage.exists(fileName)`
|
|
86
|
+
|
|
87
|
+
Returns `true` if the file exists in storage.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
if (await storage.exists('avatar-a3f8bc12.png')) {
|
|
91
|
+
// safe to stream
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
#### `storage.delete(fileName)`
|
|
98
|
+
|
|
99
|
+
Deletes the stored file. Returns `true` if the file existed and was removed.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await storage.delete('avatar-a3f8bc12.png');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
#### `storage.checkHealth()`
|
|
108
|
+
|
|
109
|
+
Verifies that the storage directory is readable and writable. Returns a `HealthCheckResult`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## File name pattern
|
|
114
|
+
|
|
115
|
+
Stored files are renamed according to `STORAGE_NAME_PATTERN`. The default pattern is `{name}-{hash}.{extension}`, which
|
|
116
|
+
produces collision-resistant names while preserving the original extension.
|
|
117
|
+
|
|
118
|
+
Available placeholders:
|
|
119
|
+
|
|
120
|
+
| Placeholder | Resolved to |
|
|
121
|
+
|---------------|-----------------------------|
|
|
122
|
+
| `{name}` | Base name without extension |
|
|
123
|
+
| `{hash}` | Short content hash |
|
|
124
|
+
| `{extension}` | Original file extension |
|
|
125
|
+
|
|
126
|
+
Example: storing `photo.jpg` produces `photo-c7d3a1f2.jpg`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
| Key | Type | Default | Description |
|
|
133
|
+
|------------------------------|----------|------------------------------------------------|-------------------------------------------------------|
|
|
134
|
+
| `STORAGE_PATH` | `string` | `'./storage'` | Root directory where files are stored |
|
|
135
|
+
| `STORAGE_NAME_PATTERN` | `string` | `'{name}-{hash}.{extension}'` | Template for deriving the stored file name |
|
|
136
|
+
| `STORAGE_CACHE_TTL` | `int` | `86400000` | Cache TTL for storage responses in milliseconds (24h) |
|
|
137
|
+
| `STORAGE_FILES_ROUTE_PREFIX` | `string` | `/files` | URL prefix for file access routes. |
|
|
138
|
+
| `STORAGE_PROVIDER` | `string` | `'@appweaver/core/storage/filesystem-storage'` | Path to the Storage implementation |
|
|
139
|
+
|
|
140
|
+
**`appweaver.json` example:**
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"STORAGE_PATH": "./uploads",
|
|
145
|
+
"STORAGE_NAME_PATTERN": "{name}-{hash}.{extension}"
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Real-world example
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { inject } from '@appweaver/core';
|
|
155
|
+
import { Storage } from '@appweaver/common';
|
|
156
|
+
|
|
157
|
+
export class AvatarService {
|
|
158
|
+
private readonly _storage = inject(Storage);
|
|
159
|
+
|
|
160
|
+
async upload(userId: number, stream: Readable, originalName: string): Promise<string> {
|
|
161
|
+
const fileId = await this._storage.store(originalName, stream);
|
|
162
|
+
if (!fileId) throw new Error('Upload failed');
|
|
163
|
+
|
|
164
|
+
await saveAvatarRecord(userId, fileId);
|
|
165
|
+
return fileId;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async download(fileId: string): Promise<{ stream: Readable; size: number }> {
|
|
169
|
+
const result = await this._storage.stream(fileId, 0);
|
|
170
|
+
if (!result) throw new Error('File not found');
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async remove(fileId: string): Promise<void> {
|
|
175
|
+
await this._storage.delete(fileId);
|
|
176
|
+
await deleteAvatarRecord(fileId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## FileService usage
|
|
184
|
+
|
|
185
|
+
The `FileService` provides higher-level file management specifically for **Resource Models**. Unlike the raw `Storage`
|
|
186
|
+
module, it automatically manages the database records (`File` entries) and enforces security policies defined on your
|
|
187
|
+
models.
|
|
188
|
+
|
|
189
|
+
### Injecting FileService
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { inject } from '@appweaver/core';
|
|
193
|
+
import { FileService } from '@appweaver/core/storage';
|
|
194
|
+
|
|
195
|
+
const fileService = inject(FileService);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Saving a file
|
|
199
|
+
|
|
200
|
+
When saving a file via `FileService`, you must provide the multipart data, the resource instance it belongs to, and its
|
|
201
|
+
`ResourceClient`.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { inject, injectService, injectModel } from '@appweaver/core';
|
|
205
|
+
import { FileService } from '@appweaver/core/storage';
|
|
206
|
+
|
|
207
|
+
export class PostService {
|
|
208
|
+
private readonly _fileService = inject(FileService);
|
|
209
|
+
private readonly _postService = injectService('Post');
|
|
210
|
+
private readonly _postClient = injectModel('Post');
|
|
211
|
+
|
|
212
|
+
async uploadImage(postId: number, data: MultipartFile) {
|
|
213
|
+
const post = await this._postService.find(postId);
|
|
214
|
+
|
|
215
|
+
// saveFile stores the file in Storage AND creates a File record in the DB
|
|
216
|
+
// linked to the 'image' field of the 'post' resource.
|
|
217
|
+
const file = await this._fileService.saveFile(data, post, this._postClient);
|
|
218
|
+
|
|
219
|
+
return file;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Streaming a file
|
|
225
|
+
|
|
226
|
+
`FileService.stream()` handles authorization checks (public, private, or protected) and range-based requests
|
|
227
|
+
automatically.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
// Streams a file while checking if the current user has access
|
|
231
|
+
const fileStream = await fileService.stream('photo-c7d3a1f2.jpg', req.headers.range);
|
|
232
|
+
|
|
233
|
+
reply
|
|
234
|
+
.status(fileStream.start > 0 ? 206 : 200)
|
|
235
|
+
.header('Content-Type', fileStream.mimeType)
|
|
236
|
+
.header('Content-Disposition', `attachment; filename="${fileStream.fileName}"`)
|
|
237
|
+
.send(fileStream.content.stream);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Deleting a file
|
|
241
|
+
|
|
242
|
+
Deleting a file via `FileService` removes it from both the storage backend and the database.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
await fileService.deleteFile(
|
|
246
|
+
'photo-c7d3a1f2.jpg',
|
|
247
|
+
'image', // resource field name
|
|
248
|
+
post, // resource instance
|
|
249
|
+
postClient // ResourceClient
|
|
250
|
+
);
|
|
251
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
NODE_ENV=dev
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## 1. Install dependencies and build the application
|
|
2
|
+
FROM node:24.13.0-alpine AS build
|
|
3
|
+
|
|
4
|
+
# Install tools for building libraries on other platforms
|
|
5
|
+
RUN apk add build-base python3 py3-pip make gcc
|
|
6
|
+
|
|
7
|
+
# Create app directory
|
|
8
|
+
WORKDIR /usr/app
|
|
9
|
+
|
|
10
|
+
# Copy project files
|
|
11
|
+
COPY ../.. .
|
|
12
|
+
|
|
13
|
+
# Install NPM dependencies
|
|
14
|
+
RUN npm ci
|
|
15
|
+
|
|
16
|
+
# Build project
|
|
17
|
+
RUN npm run build
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## 2. Setup server environment, copy build output and start the application script
|
|
21
|
+
FROM node:24.13.0-alpine AS deploy
|
|
22
|
+
|
|
23
|
+
# Install tools for building libraries on other platforms
|
|
24
|
+
RUN apk add build-base python3 py3-pip make gcc
|
|
25
|
+
|
|
26
|
+
# Create app directory
|
|
27
|
+
WORKDIR /usr/app
|
|
28
|
+
|
|
29
|
+
# Copy NPM package
|
|
30
|
+
COPY --from=build /usr/app/package*.json /usr/app/
|
|
31
|
+
|
|
32
|
+
# Install NPM dependencies for production
|
|
33
|
+
RUN npm ci --omit=dev
|
|
34
|
+
|
|
35
|
+
# Copy build files
|
|
36
|
+
COPY --from=build /usr/app/dist /usr/app/dist
|
|
37
|
+
|
|
38
|
+
# Copy Prisma config, database migrations, and schema file
|
|
39
|
+
COPY --from=build /usr/app/prisma.config.ts /usr/app/prisma.config.ts
|
|
40
|
+
COPY --from=build /usr/app/database/migrations /usr/app/database/migrations
|
|
41
|
+
COPY --from=build /usr/app/database/schema.prisma /usr/app/database/schema.prisma
|
|
42
|
+
|
|
43
|
+
# Copy config file/s
|
|
44
|
+
COPY --from=build /usr/app/appweaver*.json /usr/app/
|
|
45
|
+
|
|
46
|
+
# Copy start script
|
|
47
|
+
COPY --from=build /usr/app/start.sh /usr/app/start.sh
|
|
48
|
+
|
|
49
|
+
# Make start script executable
|
|
50
|
+
RUN chmod +x /usr/app/start.sh
|
|
51
|
+
|
|
52
|
+
# Create storage and logs directories
|
|
53
|
+
RUN mkdir storage logs
|
|
54
|
+
|
|
55
|
+
# Start container
|
|
56
|
+
ENTRYPOINT ["/usr/app/start.sh"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## 1. Install dependencies and build the application
|
|
2
|
+
FROM oven/bun:1.3.11-alpine AS build
|
|
3
|
+
|
|
4
|
+
# Install tools for building libraries on other platforms
|
|
5
|
+
RUN apk add build-base python3 py3-pip make gcc
|
|
6
|
+
|
|
7
|
+
# Create app directory
|
|
8
|
+
WORKDIR /usr/app
|
|
9
|
+
|
|
10
|
+
# Copy project files
|
|
11
|
+
COPY ../.. .
|
|
12
|
+
|
|
13
|
+
# Install NPM dependencies
|
|
14
|
+
RUN bun install --frozen-lockfile
|
|
15
|
+
|
|
16
|
+
# Build project
|
|
17
|
+
RUN bun run build
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## 2. Setup server environment, copy build output and start the application script
|
|
21
|
+
FROM oven/bun:1.3.11-alpine AS deploy
|
|
22
|
+
|
|
23
|
+
# Install tools for building libraries on other platforms
|
|
24
|
+
RUN apk add build-base python3 py3-pip make gcc
|
|
25
|
+
|
|
26
|
+
# Create app directory
|
|
27
|
+
WORKDIR /usr/app
|
|
28
|
+
|
|
29
|
+
# Copy NPM package
|
|
30
|
+
COPY --from=build /usr/app/package*.json /usr/app/
|
|
31
|
+
|
|
32
|
+
# Install NPM dependencies for production
|
|
33
|
+
RUN bun install --frozen-lockfile --production
|
|
34
|
+
|
|
35
|
+
# Copy build files
|
|
36
|
+
COPY --from=build /usr/app/dist /usr/app/dist
|
|
37
|
+
|
|
38
|
+
# Copy Prisma config, database migrations, and schema file
|
|
39
|
+
COPY --from=build /usr/app/prisma.config.ts /usr/app/prisma.config.ts
|
|
40
|
+
COPY --from=build /usr/app/database/migrations /usr/app/database/migrations
|
|
41
|
+
COPY --from=build /usr/app/database/schema.prisma /usr/app/database/schema.prisma
|
|
42
|
+
|
|
43
|
+
# Copy config file/s
|
|
44
|
+
COPY --from=build /usr/app/appweaver*.json /usr/app/
|
|
45
|
+
|
|
46
|
+
# Copy start script
|
|
47
|
+
COPY --from=build /usr/app/start.sh /usr/app/start.sh
|
|
48
|
+
|
|
49
|
+
# Make start script executable
|
|
50
|
+
RUN chmod +x /usr/app/start.sh
|
|
51
|
+
|
|
52
|
+
# Create storage and logs directories
|
|
53
|
+
RUN mkdir storage logs
|
|
54
|
+
|
|
55
|
+
# Start container
|
|
56
|
+
ENTRYPOINT ["/usr/app/start.sh"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@appweaver/common/config/schema.json",
|
|
3
|
+
"config": {
|
|
4
|
+
"app": {
|
|
5
|
+
"name": "{{NAME}}",
|
|
6
|
+
"description": "{{DESCRIPTION}}",
|
|
7
|
+
"runtime": "bun"
|
|
8
|
+
},
|
|
9
|
+
"server": {
|
|
10
|
+
"host": "{{HOST}}",
|
|
11
|
+
"port": {{PORT}}
|
|
12
|
+
},
|
|
13
|
+
"database": {
|
|
14
|
+
"url": "{{DATABASE_URL}}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@appweaver/common/config/schema.json",
|
|
3
|
+
"config": {
|
|
4
|
+
"app": {
|
|
5
|
+
"name": "{{NAME}}",
|
|
6
|
+
"description": "{{DESCRIPTION}}"
|
|
7
|
+
},
|
|
8
|
+
"server": {
|
|
9
|
+
"host": "{{HOST}}",
|
|
10
|
+
"port": {{PORT}}
|
|
11
|
+
},
|
|
12
|
+
"database": {
|
|
13
|
+
"url": "{{DATABASE_URL}}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@appweaver/common/config/schema.json",
|
|
3
|
+
"config": {
|
|
4
|
+
"log": {
|
|
5
|
+
"level": "error",
|
|
6
|
+
"pretty": true
|
|
7
|
+
},
|
|
8
|
+
"database": {
|
|
9
|
+
"url": "{{DATABASE_TEST_URL}}",
|
|
10
|
+
"schemaPath": "./temp/database/schema.prisma",
|
|
11
|
+
"migrationsDirPath": "./temp/database/migrations",
|
|
12
|
+
"clientOutputDirPath": "./temp/database/client"
|
|
13
|
+
},
|
|
14
|
+
"storage": {
|
|
15
|
+
"path": "./temp/storage"
|
|
16
|
+
},
|
|
17
|
+
"redis": {
|
|
18
|
+
"provider": "@appweaver/core/memory/in-memory"
|
|
19
|
+
},
|
|
20
|
+
"queue": {
|
|
21
|
+
"provider": "@appweaver/core/queue/memory-queue"
|
|
22
|
+
},
|
|
23
|
+
"mailer": {
|
|
24
|
+
"provider": "@appweaver/core/mailer/json-mailer"
|
|
25
|
+
},
|
|
26
|
+
"scheduler": {
|
|
27
|
+
"autoStartJob": false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
// Generated by Appweaver. Please do not edit this file manually.
|
|
5
|
+
|
|
6
|
+
datasource db {
|
|
7
|
+
provider = "sqlite"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
generator client {
|
|
11
|
+
provider = "prisma-client"
|
|
12
|
+
output = "./client"
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hashPassword } from '@appweaver/core';
|
|
2
|
+
import { config, randomString } from '@appweaver/common';
|
|
3
|
+
import { db } from '@db/client';
|
|
4
|
+
|
|
5
|
+
export async function createAdminUser(): Promise<void> {
|
|
6
|
+
let password = config.SYSTEM_ADMIN_INITIAL_PASSWORD;
|
|
7
|
+
|
|
8
|
+
if (!password) {
|
|
9
|
+
password = randomString(16, { extra: false });
|
|
10
|
+
console.log(`Generated admin password: ${password}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const passwordHash = await hashPassword(password);
|
|
14
|
+
|
|
15
|
+
await db.user.create({
|
|
16
|
+
data: {
|
|
17
|
+
firstName: 'Admin',
|
|
18
|
+
lastName: 'Admin',
|
|
19
|
+
email: config.SYSTEM_ADMIN_INITIAL_EMAIL,
|
|
20
|
+
passwordHash,
|
|
21
|
+
roles: {
|
|
22
|
+
connectOrCreate: [
|
|
23
|
+
{
|
|
24
|
+
where: { name: 'Admin' },
|
|
25
|
+
create: {
|
|
26
|
+
name: 'Admin',
|
|
27
|
+
permissions: {
|
|
28
|
+
connectOrCreate: [
|
|
29
|
+
{ where: { name: '*.read' }, create: { name: '*.read' } },
|
|
30
|
+
{ where: { name: '*.write' }, create: { name: '*.write' } }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
3
|
+
import eslintPluginJest from 'eslint-plugin-jest';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import tsEslint from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
export default tsEslint.config(
|
|
8
|
+
{
|
|
9
|
+
ignores: [
|
|
10
|
+
'node_modules',
|
|
11
|
+
'dist',
|
|
12
|
+
'database/client',
|
|
13
|
+
'**/*.js',
|
|
14
|
+
'**/*.d.ts'
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
eslint.configs.recommended,
|
|
18
|
+
...tsEslint.configs.recommendedTypeChecked.map((config) => ({ ...config })),
|
|
19
|
+
eslintPluginPrettierRecommended,
|
|
20
|
+
eslintPluginJest.configs['flat/recommended'],
|
|
21
|
+
{
|
|
22
|
+
languageOptions: {
|
|
23
|
+
globals: {
|
|
24
|
+
...globals.node,
|
|
25
|
+
...globals.jest
|
|
26
|
+
},
|
|
27
|
+
ecmaVersion: 5,
|
|
28
|
+
sourceType: 'module',
|
|
29
|
+
parserOptions: {
|
|
30
|
+
projectService: true,
|
|
31
|
+
tsconfigRootDir: import.meta.dirname
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
rules: {
|
|
37
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
38
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
39
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
40
|
+
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
41
|
+
'@typescript-eslint/require-await': 'off',
|
|
42
|
+
'@typescript-eslint/unbound-method': 'off',
|
|
43
|
+
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
44
|
+
'@typescript-eslint/no-misused-promises': 'off',
|
|
45
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
46
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
47
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
48
|
+
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
49
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
50
|
+
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
51
|
+
'@typescript-eslint/no-unsafe-call': 'off',
|
|
52
|
+
'@typescript-eslint/no-unsafe-return': 'off'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
3
|
+
import globals from 'globals';
|
|
4
|
+
import tsEslint from 'typescript-eslint';
|
|
5
|
+
|
|
6
|
+
export default tsEslint.config(
|
|
7
|
+
{
|
|
8
|
+
ignores: [
|
|
9
|
+
'node_modules',
|
|
10
|
+
'dist',
|
|
11
|
+
'database/client',
|
|
12
|
+
'**/*.js',
|
|
13
|
+
'**/*.d.ts'
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
eslint.configs.recommended,
|
|
17
|
+
...tsEslint.configs.recommendedTypeChecked.map((config) => ({ ...config })),
|
|
18
|
+
eslintPluginPrettierRecommended,
|
|
19
|
+
{
|
|
20
|
+
languageOptions: {
|
|
21
|
+
globals: {
|
|
22
|
+
...globals.node,
|
|
23
|
+
...globals.bunBuiltin
|
|
24
|
+
},
|
|
25
|
+
ecmaVersion: 5,
|
|
26
|
+
sourceType: 'module',
|
|
27
|
+
parserOptions: {
|
|
28
|
+
projectService: true,
|
|
29
|
+
tsconfigRootDir: import.meta.dirname
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rules: {
|
|
35
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
36
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
37
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
38
|
+
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
39
|
+
'@typescript-eslint/require-await': 'off',
|
|
40
|
+
'@typescript-eslint/unbound-method': 'off',
|
|
41
|
+
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
42
|
+
'@typescript-eslint/no-misused-promises': 'off',
|
|
43
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
44
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
45
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
46
|
+
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
47
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
48
|
+
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
49
|
+
'@typescript-eslint/no-unsafe-call': 'off',
|
|
50
|
+
'@typescript-eslint/no-unsafe-return': 'off'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"moduleFileExtensions": ["js", "json", "ts"],
|
|
3
|
+
"rootDir": ".",
|
|
4
|
+
"testEnvironment": "node",
|
|
5
|
+
"testMatch": ["**/?(*.)+(spec|test).ts"],
|
|
6
|
+
"testPathIgnorePatterns": ["/test/e2e/"],
|
|
7
|
+
"transform": {
|
|
8
|
+
"^.+\\.(t|j)s$": ["@swc/jest"]
|
|
9
|
+
},
|
|
10
|
+
"reporters": [
|
|
11
|
+
"default",
|
|
12
|
+
[
|
|
13
|
+
"jest-junit",
|
|
14
|
+
{
|
|
15
|
+
"outputDirectory": "./reports",
|
|
16
|
+
"outputName": "junit.xml"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
],
|
|
20
|
+
"coverageReporters": ["cobertura", "text"],
|
|
21
|
+
"collectCoverageFrom": ["./src/**/*.(t|j)s"],
|
|
22
|
+
"coverageDirectory": "./reports"
|
|
23
|
+
}
|