nuxt-auto-crud 2.1.3 → 2.2.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 +24 -11
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/utils/queries.d.ts +1 -0
- package/dist/runtime/server/utils/queries.js +35 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
## Installation Guide
|
|
15
15
|
|
|
16
|
+
### Option A: Starter Template
|
|
17
|
+
```bash
|
|
18
|
+
npx nuxi init -t gh:clifordpereira/nac-starter my-app
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Option B: Manual Installation
|
|
22
|
+
|
|
16
23
|
```bash
|
|
17
24
|
bun create nuxt@latest my-app
|
|
18
25
|
npx nuxi module add hub
|
|
@@ -21,7 +28,7 @@ bun add -D drizzle-kit@beta
|
|
|
21
28
|
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
#### Configuration
|
|
25
32
|
|
|
26
33
|
Update `nuxt.config.ts`:
|
|
27
34
|
|
|
@@ -38,7 +45,7 @@ export default defineNuxtConfig({
|
|
|
38
45
|
|
|
39
46
|
```
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
#### Schema Definition
|
|
42
49
|
|
|
43
50
|
Define your schema in `server/db/schema.ts`:
|
|
44
51
|
|
|
@@ -57,23 +64,25 @@ export const users = sqliteTable('users', {
|
|
|
57
64
|
```
|
|
58
65
|
|
|
59
66
|
### Generate Migrations and Start Dev Server
|
|
67
|
+
After installing (either option), run the following commands:
|
|
60
68
|
|
|
61
69
|
```bash
|
|
70
|
+
cd my-app
|
|
62
71
|
nuxt db generate
|
|
63
72
|
nuxt dev
|
|
64
73
|
|
|
65
74
|
```
|
|
66
|
-
> If you encounter `Error: Cannot find module 'typescript'`,
|
|
75
|
+
> If you encounter `Error: Cannot find module 'typescript'`, install it using `bun add -D typescript`.
|
|
67
76
|
|
|
68
77
|
---
|
|
69
78
|
|
|
70
|
-
## 🌐 Dynamic RESTful CRUD endpoints
|
|
79
|
+
## 🌐 Exposed Dynamic RESTful CRUD endpoints
|
|
71
80
|
|
|
72
81
|
Nb: Endpoints follow the pattern `/api/_nac/:model`.
|
|
73
82
|
|
|
74
83
|
| Method | Endpoint | Action |
|
|
75
84
|
| --- | --- | --- |
|
|
76
|
-
| **GET** | `/:model` | List records
|
|
85
|
+
| **GET** | `/:model` | List records |
|
|
77
86
|
| **POST** | `/:model` | Create record with Zod validation |
|
|
78
87
|
| **GET** | `/:model/:id` | Fetch single record |
|
|
79
88
|
| **PATCH** | `/:model/:id` | Partial update with validation |
|
|
@@ -83,7 +92,7 @@ Nb: Endpoints follow the pattern `/api/_nac/:model`.
|
|
|
83
92
|
|
|
84
93
|
| Action | HTTP Method | Endpoint | Example Result |
|
|
85
94
|
| --- | --- | --- | --- |
|
|
86
|
-
| **Fetch All** | `GET` | `/api/_nac/users` | List of all users
|
|
95
|
+
| **Fetch All** | `GET` | `/api/_nac/users` | List of all users |
|
|
87
96
|
| **Create** | `POST` | `/api/_nac/users` | New user record added |
|
|
88
97
|
| **Fetch One** | `GET` | `/api/_nac/users/1` | Details of user with `id: 1` |
|
|
89
98
|
| **Update** | `PATCH` | `/api/_nac/users/1` | Partial update to user `1` |
|
|
@@ -154,10 +163,11 @@ Enabling `authentication` in the `autoCrud` config protects all **nac** routes (
|
|
|
154
163
|
|
|
155
164
|
| Key | Default | Description |
|
|
156
165
|
| --- | --- | --- |
|
|
166
|
+
| `statusFiltering` | `false` | Enables/disables automatic filtering of records based on the `status` column. |
|
|
157
167
|
| `realtime` | `false` | Enables/disables real-time capabilities. |
|
|
158
168
|
| `auth.authentication` | `true` | Requires a valid session for all NAC routes. |
|
|
159
169
|
| `auth.authorization` | `true` | Enables role/owner-based access checks. |
|
|
160
|
-
| `auth.ownerKey` | `'
|
|
170
|
+
| `auth.ownerKey` | `'createdBy'` | The column name used to identify the record creator. |
|
|
161
171
|
| `publicResources` | `{}` | Defines tables and specific columns accessible without auth. |
|
|
162
172
|
| `nacEndpointPrefix` | `'/api/_nac'` | The base path for NAC routes. Access via `useRuntimeConfig().public.autoCrud`. |
|
|
163
173
|
| `schemaPath` | `'server/db/schema'` | Location of your Drizzle schema files. |
|
|
@@ -166,11 +176,12 @@ Enabling `authentication` in the `autoCrud` config protects all **nac** routes (
|
|
|
166
176
|
|
|
167
177
|
```typescript
|
|
168
178
|
autoCrud: {
|
|
179
|
+
statusFiltering: false,
|
|
169
180
|
realtime: false,
|
|
170
181
|
auth: {
|
|
171
182
|
authentication: true,
|
|
172
183
|
authorization: true,
|
|
173
|
-
ownerKey: '
|
|
184
|
+
ownerKey: 'createdBy',
|
|
174
185
|
},
|
|
175
186
|
publicResources: {
|
|
176
187
|
users: ['id', 'name', 'email'],
|
|
@@ -191,16 +202,18 @@ autoCrud: {
|
|
|
191
202
|
|
|
192
203
|
### Automatic Status Filtering
|
|
193
204
|
|
|
194
|
-
|
|
205
|
+
If `statusFiltering` is enabled, **nac** applies global visibility constraints. When a status column exists, queries are automatically restricted to `active` records. This logic integrates with the authorization layer, allowing users to see their own records (regardless of status) if they possess the `list_active` permission.
|
|
195
206
|
|
|
196
207
|
### Ownership & Permissions
|
|
197
208
|
|
|
198
|
-
While the implementing app handles the authentication layer, **nac** provides a standardized way to enforce record ownership and granular access.
|
|
209
|
+
While the implementing app handles the authentication & authorization layer, **nac** provides a standardized way to enforce record ownership and granular access.
|
|
199
210
|
|
|
200
211
|
If your middleware populates `event.context.nac` with `resourcePermissions`, **nac** automatically injects the necessary SQL filters.
|
|
201
212
|
|
|
202
213
|
**Example: Restricting users to their own records**
|
|
203
|
-
If the permissions array includes `'list_own'`, **nac** appends a filter where `
|
|
214
|
+
If the permissions array includes `'list_own'`, **nac** appends a filter where `ownerKey` (defaulting to `createdBy`) matches the `userId`.
|
|
215
|
+
|
|
216
|
+
If `list_active` is present, it applies a hybrid OR logic: users can see all active records OR any record they own, regardless of its status.
|
|
204
217
|
|
|
205
218
|
```typescript
|
|
206
219
|
// Example: Setting context in your Auth Middleware
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Table } from 'drizzle-orm';
|
|
2
2
|
import type { QueryContext } from '../../types/index.js';
|
|
3
3
|
import type { TableWithId } from '../types.js';
|
|
4
|
+
export declare function getVisibilityFilters(table: TableWithId, context?: QueryContext): (import("drizzle-orm").SQL<unknown> | undefined)[];
|
|
4
5
|
/**
|
|
5
6
|
* Fetches rows from the database based on the provided table and context.
|
|
6
7
|
* @param table - The table to query.
|
|
@@ -2,30 +2,49 @@ import { useRuntimeConfig } from "#imports";
|
|
|
2
2
|
import { db } from "@nuxthub/db";
|
|
3
3
|
import { eq, desc, and, or, getColumns } from "drizzle-orm";
|
|
4
4
|
import { getSelectableFields } from "./modelMapper.js";
|
|
5
|
-
import { DeletionFailedError, InsertionFailedError, RecordNotFoundError, UpdateFailedError } from "../exceptions.js";
|
|
5
|
+
import { DeletionFailedError, InsertionFailedError, RecordNotFoundError, UnauthorizedAccessError, UpdateFailedError } from "../exceptions.js";
|
|
6
6
|
import { pick } from "#nac/shared/utils/helpers";
|
|
7
|
-
export
|
|
8
|
-
const
|
|
7
|
+
export function getVisibilityFilters(table, context = {}) {
|
|
8
|
+
const isAuthorizationEnabled = useRuntimeConfig().autoCrud.auth?.authorization;
|
|
9
|
+
const isStatusFilteringEnabled = useRuntimeConfig().autoCrud.statusFiltering;
|
|
10
|
+
if (!isAuthorizationEnabled && !isStatusFilteringEnabled) return [];
|
|
11
|
+
const { userId, resourcePermissions = [] } = context;
|
|
12
|
+
if (isAuthorizationEnabled && resourcePermissions?.includes("list_all")) return [];
|
|
9
13
|
const ownerKey = useRuntimeConfig().autoCrud.auth?.ownerKey || "createdBy";
|
|
10
|
-
const filters = [];
|
|
11
14
|
const ownerCol = table[ownerKey];
|
|
12
15
|
const statusCol = table.status;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
} else if (
|
|
22
|
-
filters.push(eq(
|
|
16
|
+
const filters = [];
|
|
17
|
+
if (isAuthorizationEnabled && isStatusFilteringEnabled) {
|
|
18
|
+
if (resourcePermissions?.includes("list_active")) {
|
|
19
|
+
if (statusCol && ownerCol && userId != null) {
|
|
20
|
+
filters.push(or(eq(statusCol, "active"), eq(ownerCol, Number(userId))));
|
|
21
|
+
} else if (statusCol) {
|
|
22
|
+
filters.push(eq(statusCol, "active"));
|
|
23
|
+
}
|
|
24
|
+
} else if (resourcePermissions?.includes("list_own") && ownerCol && userId != null) {
|
|
25
|
+
filters.push(eq(ownerCol, Number(userId)));
|
|
26
|
+
}
|
|
27
|
+
} else if (isStatusFilteringEnabled) {
|
|
28
|
+
if (statusCol) filters.push(eq(statusCol, "active"));
|
|
29
|
+
} else if (isAuthorizationEnabled) {
|
|
30
|
+
if (resourcePermissions?.includes("list_own") && ownerCol && userId != null) {
|
|
31
|
+
filters.push(eq(ownerCol, Number(userId)));
|
|
23
32
|
}
|
|
24
|
-
}
|
|
25
|
-
|
|
33
|
+
}
|
|
34
|
+
return filters;
|
|
35
|
+
}
|
|
36
|
+
function hasAnyListPermissions(context = {}) {
|
|
37
|
+
const { resourcePermissions = [] } = context;
|
|
38
|
+
return resourcePermissions?.includes("list_all") || resourcePermissions?.includes("list_active") || resourcePermissions?.includes("list_own");
|
|
39
|
+
}
|
|
40
|
+
export async function nacGetRows(table, context = {}) {
|
|
41
|
+
const isAuthorizationEnabled = useRuntimeConfig().autoCrud.auth?.authorization;
|
|
42
|
+
if (isAuthorizationEnabled && !context.isPublic && !hasAnyListPermissions(context)) {
|
|
43
|
+
throw new UnauthorizedAccessError();
|
|
26
44
|
}
|
|
27
45
|
const fields = getSelectableFields(table, context);
|
|
28
46
|
let query = db.select(fields).from(table).$dynamic();
|
|
47
|
+
const filters = getVisibilityFilters(table, context);
|
|
29
48
|
if (filters.length > 0) query = query.where(and(...filters));
|
|
30
49
|
return await query.orderBy(desc(table.id)).all();
|
|
31
50
|
}
|