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 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
- ### Configuration
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
- ### Schema Definition
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'`, run `bun add -D 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 (supports filtering & pagination) |
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 (paginated) |
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` | `'ownerId'` | The column name used to identify the record creator. |
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: 'ownerId',
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
- To align with standard application behavior, **nac** automatically filters records if a `status` column exists. By default, it will only return **active** records, reducing boilerplate for soft-state management.
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 `ownerCol === userId`.
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-auto-crud",
3
3
  "configKey": "autoCrud",
4
- "version": "2.1.3",
4
+ "version": "2.2.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -8,7 +8,7 @@ const module$1 = defineNuxtModule({
8
8
  },
9
9
  defaults: {
10
10
  // Private config
11
- statusFiltering: true,
11
+ statusFiltering: false,
12
12
  realtime: false,
13
13
  auth: {
14
14
  authentication: false,
@@ -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 async function nacGetRows(table, context = {}) {
8
- const { userId, resourcePermissions } = context;
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
- if (resourcePermissions?.includes("list")) {
14
- if (statusCol && ownerCol) {
15
- filters.push(
16
- or(
17
- eq(statusCol, "active"),
18
- eq(ownerCol, Number(userId))
19
- )
20
- );
21
- } else if (statusCol) {
22
- filters.push(eq(statusCol, "active"));
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
- } else if (resourcePermissions?.includes("list_own") && userId && ownerCol) {
25
- filters.push(eq(ownerCol, Number(userId)));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auto-crud",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "Dynamic RESTful CRUD APIs for Nuxt without code generation, fully schema-driven.",
5
5
  "author": "Cliford Pereira",
6
6
  "license": "MIT",