@revealui/core 0.5.5 → 0.6.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 +30 -24
- package/dist/api/compression.d.ts +3 -4
- package/dist/api/compression.d.ts.map +1 -1
- package/dist/api/compression.js +1 -2
- package/dist/api/rate-limit.d.ts +11 -12
- package/dist/api/rate-limit.d.ts.map +1 -1
- package/dist/api/rate-limit.js +5 -6
- package/dist/api/response-cache.d.ts +8 -9
- package/dist/api/response-cache.d.ts.map +1 -1
- package/dist/api/response-cache.js +5 -4
- package/dist/api/rest.d.ts +1 -1
- package/dist/api/rest.js +2 -2
- package/dist/cache/query-cache.d.ts +1 -1
- package/dist/cache/query-cache.js +1 -1
- package/dist/caching/index.d.ts +1 -1
- package/dist/caching/index.js +1 -1
- package/dist/client/admin/components/AdminDashboard.d.ts.map +1 -1
- package/dist/client/admin/components/AdminDashboard.js +46 -3
- package/dist/client/admin/components/CollectionList.d.ts +3 -1
- package/dist/client/admin/components/CollectionList.d.ts.map +1 -1
- package/dist/client/admin/components/CollectionList.js +51 -2
- package/dist/client/admin/components/DocumentForm.js +2 -2
- package/dist/client/admin/layout.d.ts.map +1 -1
- package/dist/client/admin/layout.js +1 -3
- package/dist/client/admin/page.js +1 -1
- package/dist/client/admin/utils/apiClient.d.ts +17 -1
- package/dist/client/admin/utils/apiClient.d.ts.map +1 -1
- package/dist/client/admin/utils/apiClient.js +25 -1
- package/dist/client/hooks.d.ts +1 -1
- package/dist/client/hooks.js +1 -1
- package/dist/client/richtext/plugins/PastePlugin.d.ts.map +1 -1
- package/dist/client/richtext/plugins/PastePlugin.js +30 -0
- package/dist/client/ui/index.d.ts +2 -2
- package/dist/client/ui/index.js +2 -2
- package/dist/collections/operations/fieldHooks.d.ts +2 -2
- package/dist/collections/operations/fieldHooks.js +2 -2
- package/dist/collections/operations/update.js +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/runtime.d.ts +3 -3
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -2
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +5 -0
- package/dist/database/safe-parse.d.ts +1 -1
- package/dist/database/safe-parse.js +3 -3
- package/dist/database/universal-postgres.d.ts +1 -1
- package/dist/database/universal-postgres.js +1 -1
- package/dist/error-handling/error-reporter.js +4 -4
- package/dist/features.d.ts +9 -9
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +3 -3
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/index.js +1 -1
- package/dist/generated/types/{cms.d.ts → admin.d.ts} +3 -3
- package/dist/generated/types/admin.d.ts.map +1 -0
- package/dist/generated/types/index.d.ts +2 -2
- package/dist/generated/types/index.d.ts.map +1 -1
- package/dist/generated/types/index.js +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -10
- package/dist/instance/RevealUIInstance.js +2 -2
- package/dist/jobs/queue.d.ts +1 -1
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/license-encryption.d.ts +11 -2
- package/dist/license-encryption.d.ts.map +1 -1
- package/dist/license-encryption.js +79 -24
- package/dist/license.d.ts +68 -5
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +171 -20
- package/dist/monitoring/zombie-detector.js +1 -1
- package/dist/nextjs/index.d.ts +0 -1
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +7 -2
- package/dist/nextjs/withRevealUI.d.ts +29 -1
- package/dist/nextjs/withRevealUI.d.ts.map +1 -1
- package/dist/observability/health-check.js +1 -1
- package/dist/observability/logger.d.ts +0 -4
- package/dist/observability/logger.d.ts.map +1 -1
- package/dist/observability/logger.js +2 -29
- package/dist/plugins/nested-docs.d.ts +1 -1
- package/dist/plugins/nested-docs.d.ts.map +1 -1
- package/dist/plugins/nested-docs.js +1 -1
- package/dist/relationships/analyzer.d.ts +1 -1
- package/dist/relationships/analyzer.js +2 -2
- package/dist/relationships/populate-core.d.ts +1 -1
- package/dist/relationships/populate-core.d.ts.map +1 -1
- package/dist/relationships/populate-core.js +5 -1
- package/dist/relationships/population.js +1 -1
- package/dist/revealui.d.ts +0 -5
- package/dist/revealui.d.ts.map +1 -1
- package/dist/revealui.js +0 -10
- package/dist/richtext/exports/client/rcc.js +1 -1
- package/dist/richtext/exports/server/rsc.d.ts +2 -17
- package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
- package/dist/richtext/exports/server/rsc.js +9 -54
- package/dist/richtext/index.d.ts +1 -1
- package/dist/richtext/index.js +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/server/renderPage.js +1 -1
- package/dist/types/admin.d.ts +8 -0
- package/dist/types/admin.d.ts.map +1 -0
- package/dist/types/admin.js +6 -0
- package/dist/types/config.d.ts +2 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -1
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/extensions.d.ts.map +1 -1
- package/dist/types/generated.d.ts +4 -4
- package/dist/types/generated.d.ts.map +1 -1
- package/dist/types/generated.js +2 -2
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -6
- package/dist/types/jobs.d.ts +1 -1
- package/dist/types/jobs.js +1 -1
- package/dist/types/legacy.d.ts +1 -1
- package/dist/types/legacy.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +1 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/types/query.d.ts +2 -2
- package/dist/types/query.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +2 -2
- package/dist/types/runtime.d.ts.map +1 -1
- package/dist/types/schema.d.ts +1 -1
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/utils/api-wrapper.d.ts +4 -6
- package/dist/utils/api-wrapper.d.ts.map +1 -1
- package/dist/utils/api-wrapper.js +6 -9
- package/dist/utils/error-responses.js +1 -1
- package/dist/utils/field-conversion.js +1 -1
- package/dist/utils/type-guards.d.ts +1 -1
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/package.json +87 -34
- package/dist/generated/types/cms.d.ts.map +0 -1
- package/dist/types/cms.d.ts +0 -8
- package/dist/types/cms.d.ts.map +0 -1
- package/dist/types/cms.js +0 -6
- /package/dist/generated/types/{cms.js → admin.js} +0 -0
package/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# @revealui/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@revealui/core)
|
|
4
|
+
[](https://www.npmjs.com/package/@revealui/core)
|
|
5
|
+
[](https://github.com/RevealUIStudio/revealui/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
The core runtime engine for [RevealUI](https://revealui.com) - collections, admin UI, rich text, security, observability, and plugins.
|
|
8
|
+
|
|
9
|
+
Part of the [RevealUI monorepo](https://github.com/RevealUIStudio/revealui) - open-source agentic business runtime.
|
|
4
10
|
|
|
5
11
|
## Features
|
|
6
12
|
|
|
7
|
-
- **Collections & CRUD**
|
|
8
|
-
- **Admin Dashboard**
|
|
9
|
-
- **Rich Text**
|
|
10
|
-
- **Security**
|
|
11
|
-
- **GDPR Compliance**
|
|
12
|
-
- **Observability**
|
|
13
|
-
- **Plugins**
|
|
14
|
-
- **Feature Gating**
|
|
15
|
-
- **Database**
|
|
16
|
-
- **Storage**
|
|
13
|
+
- **Collections & CRUD** - Define content types with field hooks, access control, and validation
|
|
14
|
+
- **Admin Dashboard** - Ready-to-use React admin UI (collection browser, document editor, global forms)
|
|
15
|
+
- **Rich Text Fields** - Lexical-powered content fields in the admin editor (bold, headings, lists, links, images, blocks)
|
|
16
|
+
- **Security** - CORS, CSP, HSTS, RBAC/ABAC policy engine, encryption (AES-256-GCM), audit logging
|
|
17
|
+
- **GDPR Compliance** - Consent management, data export, deletion, anonymization, breach reporting
|
|
18
|
+
- **Observability** - Structured logging, process health monitoring, alert system, graceful shutdown
|
|
19
|
+
- **Plugins** - Extensible plugin system (form builder, nested docs, redirects)
|
|
20
|
+
- **Feature Gating** - Tier-based licensing (free, pro, max, enterprise/Forge) with JWT license keys
|
|
21
|
+
- **Database** - PostgreSQL adapters (NeonDB + PGlite for testing), connection pooling, SSL/TLS
|
|
22
|
+
- **Storage** - Pluggable storage interface (Vercel Blob adapter included)
|
|
17
23
|
|
|
18
24
|
## Installation
|
|
19
25
|
|
|
@@ -78,15 +84,15 @@ function App() {
|
|
|
78
84
|
}
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
### Rich Text
|
|
87
|
+
### Rich Text Fields
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
Rich text editing is available as a field type in the admin dashboard.
|
|
90
|
+
Define a `richText` field in your collection schema - the admin UI renders
|
|
91
|
+
a Lexical editor automatically. Not a standalone component.
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/>
|
|
93
|
+
```typescript
|
|
94
|
+
// In your collection definition:
|
|
95
|
+
defineField({ name: 'body', type: 'richText' })
|
|
90
96
|
```
|
|
91
97
|
|
|
92
98
|
### Feature Gating
|
|
@@ -150,20 +156,20 @@ pnpm dev
|
|
|
150
156
|
- You're building a content-driven app and need collections, admin UI, and CRUD out of the box
|
|
151
157
|
- You need RBAC/ABAC access control, GDPR compliance, or feature gating by license tier
|
|
152
158
|
- You want a rich text editor (Lexical) integrated with your CMS
|
|
153
|
-
- **Not** for standalone UI components
|
|
154
|
-
- **Not** for raw database queries
|
|
159
|
+
- **Not** for standalone UI components - use `@revealui/presentation`
|
|
160
|
+
- **Not** for raw database queries - use `@revealui/db` directly
|
|
155
161
|
|
|
156
162
|
## JOSHUA Alignment
|
|
157
163
|
|
|
158
|
-
- **Sovereign**: Self-hosted runtime engine
|
|
164
|
+
- **Sovereign**: Self-hosted runtime engine - no vendor dependency for content management, auth, or storage
|
|
159
165
|
- **Unified**: One `buildConfig()` call wires collections, globals, plugins, security, and feature gates into a single configuration
|
|
160
166
|
- **Adaptive**: Plugin system and tier-based feature gating let the platform evolve without breaking existing deployments
|
|
161
167
|
|
|
162
168
|
## Related
|
|
163
169
|
|
|
164
|
-
- [Contracts Package](../contracts/README.md)
|
|
165
|
-
- [DB Package](../db/README.md)
|
|
166
|
-
- [Auth Package](../auth/README.md)
|
|
170
|
+
- [Contracts Package](../contracts/README.md) - Zod schemas and TypeScript types
|
|
171
|
+
- [DB Package](../db/README.md) - Drizzle ORM schema
|
|
172
|
+
- [Auth Package](../auth/README.md) - Authentication system
|
|
167
173
|
- [Architecture Guide](../../docs/ARCHITECTURE.md)
|
|
168
174
|
|
|
169
175
|
## License
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements gzip and brotli compression for API responses
|
|
5
5
|
*/
|
|
6
|
-
import { type NextRequest, NextResponse } from 'next/server';
|
|
7
6
|
interface CompressionOptions {
|
|
8
7
|
threshold?: number;
|
|
9
8
|
level?: number;
|
|
@@ -13,11 +12,11 @@ interface CompressionOptions {
|
|
|
13
12
|
/**
|
|
14
13
|
* Compress API response
|
|
15
14
|
*/
|
|
16
|
-
export declare function compressResponse(request:
|
|
15
|
+
export declare function compressResponse(request: Request, response: Response, options?: CompressionOptions): Promise<Response>;
|
|
17
16
|
/**
|
|
18
17
|
* Create compression middleware
|
|
19
18
|
*/
|
|
20
|
-
export declare function createCompressionMiddleware(options?: CompressionOptions): (request:
|
|
19
|
+
export declare function createCompressionMiddleware(options?: CompressionOptions): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
21
20
|
/**
|
|
22
21
|
* Calculate compression ratio
|
|
23
22
|
*/
|
|
@@ -75,7 +74,7 @@ export declare function decompressBody(body: Uint8Array, encoding: string): Prom
|
|
|
75
74
|
/**
|
|
76
75
|
* Check if compression is supported
|
|
77
76
|
*/
|
|
78
|
-
export declare function isCompressionSupported(request:
|
|
77
|
+
export declare function isCompressionSupported(request: Request): {
|
|
79
78
|
gzip: boolean;
|
|
80
79
|
brotli: boolean;
|
|
81
80
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/api/compression.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/api/compression.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AA4GD;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,QAAQ,CAAC,CAuCnB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,kBAAuB,IAC5D,SAAS,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,QAAQ,CAAC,uBAI9D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAExF;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,GAAG,UAAU,EACjC,cAAc,EAAE,UAAU,EAC1B,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAelB;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CtB,CAAC;AAEX;;GAEG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,OAAO,EACb,QAAQ,GAAE,MAAM,GAAG,IAAa,EAChC,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkB5F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG;IACxD,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB,CAOA"}
|
package/dist/api/compression.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements gzip and brotli compression for API responses
|
|
5
5
|
*/
|
|
6
|
-
import { NextResponse } from 'next/server';
|
|
7
6
|
import { logger } from '../observability/logger.js';
|
|
8
7
|
const DEFAULT_OPTIONS = {
|
|
9
8
|
threshold: 1024, // 1KB minimum
|
|
@@ -108,7 +107,7 @@ export async function compressResponse(request, response, options = {}) {
|
|
|
108
107
|
// Compress body
|
|
109
108
|
const compressed = await compressBody(body, encoding, opts.level || 6);
|
|
110
109
|
// Create new response with compressed body
|
|
111
|
-
const newResponse = new
|
|
110
|
+
const newResponse = new Response(compressed, {
|
|
112
111
|
status: response.status,
|
|
113
112
|
statusText: response.statusText,
|
|
114
113
|
headers: response.headers,
|
package/dist/api/rate-limit.d.ts
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements rate limiting to prevent API abuse
|
|
5
5
|
*/
|
|
6
|
-
import { type NextRequest, NextResponse } from 'next/server';
|
|
7
6
|
interface RateLimitConfig {
|
|
8
7
|
windowMs: number;
|
|
9
8
|
maxRequests: number;
|
|
10
|
-
keyGenerator?: (request:
|
|
11
|
-
skip?: (request:
|
|
12
|
-
handler?: (request:
|
|
9
|
+
keyGenerator?: (request: Request) => string;
|
|
10
|
+
skip?: (request: Request) => boolean;
|
|
11
|
+
handler?: (request: Request) => Response;
|
|
13
12
|
}
|
|
14
13
|
interface RateLimitEntry {
|
|
15
14
|
count: number;
|
|
@@ -18,7 +17,7 @@ interface RateLimitEntry {
|
|
|
18
17
|
/**
|
|
19
18
|
* Check if rate limit exceeded
|
|
20
19
|
*/
|
|
21
|
-
export declare function checkRateLimit(request:
|
|
20
|
+
export declare function checkRateLimit(request: Request, config: RateLimitConfig): {
|
|
22
21
|
allowed: boolean;
|
|
23
22
|
limit: number;
|
|
24
23
|
remaining: number;
|
|
@@ -27,7 +26,7 @@ export declare function checkRateLimit(request: NextRequest, config: RateLimitCo
|
|
|
27
26
|
/**
|
|
28
27
|
* Create rate limit middleware
|
|
29
28
|
*/
|
|
30
|
-
export declare function createRateLimitMiddleware(config: RateLimitConfig): (request:
|
|
29
|
+
export declare function createRateLimitMiddleware(config: RateLimitConfig): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
31
30
|
/**
|
|
32
31
|
* Cleanup expired rate limit entries across all stores
|
|
33
32
|
*/
|
|
@@ -68,7 +67,7 @@ export declare function getRateLimitPresets(): Readonly<Record<string, RateLimit
|
|
|
68
67
|
*/
|
|
69
68
|
export declare function resetRateLimitPresets(): void;
|
|
70
69
|
/**
|
|
71
|
-
* Rate limit presets
|
|
70
|
+
* Rate limit presets - backward-compatible accessor.
|
|
72
71
|
*
|
|
73
72
|
* Reads from the mutable `rateLimitPresets` so overrides from
|
|
74
73
|
* `configureRateLimitPresets()` are reflected automatically.
|
|
@@ -77,21 +76,21 @@ export declare const RATE_LIMIT_PRESETS: Record<string, RateLimitPresetConfig>;
|
|
|
77
76
|
/**
|
|
78
77
|
* Rate limit by user ID
|
|
79
78
|
*/
|
|
80
|
-
export declare function createUserRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request:
|
|
79
|
+
export declare function createUserRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
81
80
|
/**
|
|
82
81
|
* Rate limit by API key
|
|
83
82
|
*/
|
|
84
|
-
export declare function createAPIKeyRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request:
|
|
83
|
+
export declare function createAPIKeyRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
85
84
|
/**
|
|
86
85
|
* Rate limit by endpoint
|
|
87
86
|
*/
|
|
88
|
-
export declare function createEndpointRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request:
|
|
89
|
-
export declare function checkSlidingWindowRateLimit(request:
|
|
87
|
+
export declare function createEndpointRateLimit(config: Omit<RateLimitConfig, 'keyGenerator'>): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
88
|
+
export declare function checkSlidingWindowRateLimit(request: Request, config: RateLimitConfig): {
|
|
90
89
|
allowed: boolean;
|
|
91
90
|
limit: number;
|
|
92
91
|
remaining: number;
|
|
93
92
|
};
|
|
94
|
-
export declare function checkTokenBucketRateLimit(request:
|
|
93
|
+
export declare function checkTokenBucketRateLimit(request: Request, config: RateLimitConfig & {
|
|
95
94
|
refillRate: number;
|
|
96
95
|
}): {
|
|
97
96
|
allowed: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/api/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/api/rate-limit.ts"],"names":[],"mappings":"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"}
|
package/dist/api/rate-limit.js
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements rate limiting to prevent API abuse
|
|
5
5
|
*/
|
|
6
|
-
import { NextResponse } from 'next/server';
|
|
7
6
|
import { logger } from '../observability/logger.js';
|
|
8
|
-
// WARNING: In-memory store
|
|
7
|
+
// WARNING: In-memory store - resets on every serverless cold start or process restart.
|
|
9
8
|
// This module is NOT used by any app in production (API uses apps/api/src/middleware/rate-limit.ts,
|
|
10
|
-
//
|
|
9
|
+
// admin uses @revealui/auth/server which is DB-backed). Exported for framework consumers only.
|
|
11
10
|
// Replace with a database-backed store before using in production.
|
|
12
11
|
const rateLimitStore = new Map();
|
|
13
12
|
/**
|
|
@@ -24,7 +23,7 @@ function defaultKeyGenerator(request) {
|
|
|
24
23
|
if (realIp) {
|
|
25
24
|
return realIp;
|
|
26
25
|
}
|
|
27
|
-
// Fallback to unknown (
|
|
26
|
+
// Fallback to unknown (Request doesn't have ip property)
|
|
28
27
|
return 'unknown';
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
@@ -90,7 +89,7 @@ export function createRateLimitMiddleware(config) {
|
|
|
90
89
|
*/
|
|
91
90
|
function createRateLimitResponse(result) {
|
|
92
91
|
const retryAfter = Math.ceil((result.resetTime - Date.now()) / 1000);
|
|
93
|
-
return
|
|
92
|
+
return Response.json({
|
|
94
93
|
error: 'Too Many Requests',
|
|
95
94
|
message: 'Rate limit exceeded. Please try again later.',
|
|
96
95
|
limit: result.limit,
|
|
@@ -231,7 +230,7 @@ export function resetRateLimitPresets() {
|
|
|
231
230
|
rateLimitPresets = { ...DEFAULT_RATE_LIMIT_PRESETS };
|
|
232
231
|
}
|
|
233
232
|
/**
|
|
234
|
-
* Rate limit presets
|
|
233
|
+
* Rate limit presets - backward-compatible accessor.
|
|
235
234
|
*
|
|
236
235
|
* Reads from the mutable `rateLimitPresets` so overrides from
|
|
237
236
|
* `configureRateLimitPresets()` are reflected automatically.
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements HTTP caching for API responses
|
|
5
5
|
*/
|
|
6
|
-
import { type NextRequest, NextResponse } from 'next/server';
|
|
7
6
|
interface CacheOptions {
|
|
8
7
|
ttl?: number;
|
|
9
8
|
staleWhileRevalidate?: number;
|
|
@@ -15,15 +14,15 @@ interface CacheOptions {
|
|
|
15
14
|
/**
|
|
16
15
|
* Generate cache key from request
|
|
17
16
|
*/
|
|
18
|
-
export declare function generateCacheKey(request:
|
|
17
|
+
export declare function generateCacheKey(request: Request): string;
|
|
19
18
|
/**
|
|
20
19
|
* Get cached response
|
|
21
20
|
*/
|
|
22
|
-
export declare function getCachedResponse(request:
|
|
21
|
+
export declare function getCachedResponse(request: Request): Promise<Response | null>;
|
|
23
22
|
/**
|
|
24
23
|
* Set cached response
|
|
25
24
|
*/
|
|
26
|
-
export declare function setCachedResponse(request:
|
|
25
|
+
export declare function setCachedResponse(request: Request, response: Response, options?: CacheOptions): Promise<void>;
|
|
27
26
|
/**
|
|
28
27
|
* Invalidate cache by key
|
|
29
28
|
*/
|
|
@@ -43,11 +42,11 @@ export declare function clearCache(): void;
|
|
|
43
42
|
/**
|
|
44
43
|
* Set Cache-Control headers
|
|
45
44
|
*/
|
|
46
|
-
export declare function setCacheHeaders(response:
|
|
45
|
+
export declare function setCacheHeaders(response: Response, options: CacheOptions): Response;
|
|
47
46
|
/**
|
|
48
47
|
* Create caching middleware
|
|
49
48
|
*/
|
|
50
|
-
export declare function createCacheMiddleware(options?: CacheOptions): (request:
|
|
49
|
+
export declare function createCacheMiddleware(options?: CacheOptions): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
51
50
|
/**
|
|
52
51
|
* Get cache statistics
|
|
53
52
|
*/
|
|
@@ -69,15 +68,15 @@ export declare function startCacheCleanup(intervalMs?: number): NodeJS.Timeout;
|
|
|
69
68
|
/**
|
|
70
69
|
* Cache response with ETag
|
|
71
70
|
*/
|
|
72
|
-
export declare function withETag(response:
|
|
71
|
+
export declare function withETag(response: Response, content: string): Response;
|
|
73
72
|
/**
|
|
74
73
|
* Check if request has matching ETag
|
|
75
74
|
*/
|
|
76
|
-
export declare function checkETag(request:
|
|
75
|
+
export declare function checkETag(request: Request, etag: string): boolean;
|
|
77
76
|
/**
|
|
78
77
|
* Create 304 Not Modified response
|
|
79
78
|
*/
|
|
80
|
-
export declare function createNotModifiedResponse():
|
|
79
|
+
export declare function createNotModifiedResponse(): Response;
|
|
81
80
|
/**
|
|
82
81
|
* Cache presets for common scenarios
|
|
83
82
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-cache.d.ts","sourceRoot":"","sources":["../../src/api/response-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"response-cache.d.ts","sourceRoot":"","sources":["../../src/api/response-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,UAAU,YAAY;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAiBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWzD;AAyBD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA0BlF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAc9D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAW1D;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,GAAG,QAAQ,CA6BnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,YAAiB,IAChD,SAAS,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,QAAQ,CAAC,uBAqB9D;AAED;;GAEG;AACH,wBAAgB,aAAa;;;;;;EA0B5B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAc1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,GAAE,MAAc,GAAG,MAAM,CAAC,OAAO,CAO5E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAQtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,QAAQ,CAKpD;AAED;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+ChB,CAAC;AAEX;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAsCvC"}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements HTTP caching for API responses
|
|
5
5
|
*/
|
|
6
|
-
import { NextResponse } from 'next/server';
|
|
7
6
|
import { logger } from '../observability/logger.js';
|
|
8
7
|
// In-memory cache store (no external cache dependency needed)
|
|
9
8
|
const cacheStore = new Map();
|
|
@@ -58,7 +57,7 @@ export async function getCachedResponse(request) {
|
|
|
58
57
|
const headers = new Headers(entry.response.headers);
|
|
59
58
|
headers.set('X-Cache', 'HIT');
|
|
60
59
|
headers.set('Age', age.toString());
|
|
61
|
-
return new
|
|
60
|
+
return new Response(entry.response.body, {
|
|
62
61
|
status: entry.response.status,
|
|
63
62
|
statusText: entry.response.statusText,
|
|
64
63
|
headers,
|
|
@@ -100,7 +99,9 @@ export function invalidateCacheKey(key) {
|
|
|
100
99
|
*/
|
|
101
100
|
export function invalidateCachePattern(pattern) {
|
|
102
101
|
let count = 0;
|
|
103
|
-
|
|
102
|
+
// Escape all regex special characters, then convert glob wildcards (*) to .*
|
|
103
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
104
|
+
const regex = new RegExp(`^${escaped.replaceAll('\\*', '.*')}$`);
|
|
104
105
|
for (const key of cacheStore.keys()) {
|
|
105
106
|
if (regex.test(key)) {
|
|
106
107
|
cacheStore.delete(key);
|
|
@@ -248,7 +249,7 @@ export function checkETag(request, etag) {
|
|
|
248
249
|
* Create 304 Not Modified response
|
|
249
250
|
*/
|
|
250
251
|
export function createNotModifiedResponse() {
|
|
251
|
-
return new
|
|
252
|
+
return new Response(null, {
|
|
252
253
|
status: 304,
|
|
253
254
|
statusText: 'Not Modified',
|
|
254
255
|
});
|
package/dist/api/rest.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RevealUI REST API Implementation
|
|
3
3
|
*
|
|
4
|
-
* Based on RevealUI
|
|
4
|
+
* Based on RevealUI admin REST API but adapted for RevealUI.
|
|
5
5
|
*
|
|
6
6
|
* WARNING: This module is server-only.
|
|
7
7
|
* Do NOT import in client-side code or edge runtime.
|
package/dist/api/rest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RevealUI REST API Implementation
|
|
3
3
|
*
|
|
4
|
-
* Based on RevealUI
|
|
4
|
+
* Based on RevealUI admin REST API but adapted for RevealUI.
|
|
5
5
|
*
|
|
6
6
|
* WARNING: This module is server-only.
|
|
7
7
|
* Do NOT import in client-side code or edge runtime.
|
|
@@ -24,7 +24,7 @@ function parseQueryParams(searchParams) {
|
|
|
24
24
|
if (fallbackLocale) {
|
|
25
25
|
options.fallbackLocale = fallbackLocale;
|
|
26
26
|
}
|
|
27
|
-
// Parse overrideAccess
|
|
27
|
+
// Parse overrideAccess - SECURITY: This flag bypasses collection access control.
|
|
28
28
|
// It is stripped from external API requests below to prevent abuse.
|
|
29
29
|
// Only server-side code should set overrideAccess=true (e.g., internal hooks, seed scripts).
|
|
30
30
|
const overrideAccess = searchParams.get('overrideAccess');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Query Result Caching
|
|
3
3
|
*
|
|
4
4
|
* In-memory Map-based cache for database queries. No external dependencies
|
|
5
|
-
* (Redis, Memcached, etc.)
|
|
5
|
+
* (Redis, Memcached, etc.) - RevealUI uses PostgreSQL + ElectricSQL/PGlite
|
|
6
6
|
* for all persistence and sync.
|
|
7
7
|
*/
|
|
8
8
|
interface CacheOptions {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Query Result Caching
|
|
3
3
|
*
|
|
4
4
|
* In-memory Map-based cache for database queries. No external dependencies
|
|
5
|
-
* (Redis, Memcached, etc.)
|
|
5
|
+
* (Redis, Memcached, etc.) - RevealUI uses PostgreSQL + ElectricSQL/PGlite
|
|
6
6
|
* for all persistence and sync.
|
|
7
7
|
*/
|
|
8
8
|
import { logger } from '../observability/logger.js';
|
package/dist/caching/index.d.ts
CHANGED
package/dist/caching/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboard.d.ts","sourceRoot":"","sources":["../../../../src/client/admin/components/AdminDashboard.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,yBAAyB,CAAC;AAUjC,UAAU,mBAAmB;IAC3B,MAAM,EAAE,YAAY,CAAC;CACtB;AA6XD,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"AdminDashboard.d.ts","sourceRoot":"","sources":["../../../../src/client/admin/components/AdminDashboard.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,yBAAyB,CAAC;AAUjC,UAAU,mBAAmB;IAC3B,MAAM,EAAE,YAAY,CAAC;CACtB;AA6XD,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,mBAAmB,2CAkV7D"}
|
|
@@ -72,7 +72,7 @@ function SignOutButton() {
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
catch {
|
|
75
|
-
// Sign out even if the API call fails
|
|
75
|
+
// Sign out even if the API call fails - clear client state regardless
|
|
76
76
|
}
|
|
77
77
|
window.location.href = '/login';
|
|
78
78
|
}, []);
|
|
@@ -82,7 +82,7 @@ function SignOutButton() {
|
|
|
82
82
|
// Dashboard home view
|
|
83
83
|
// =============================================================================
|
|
84
84
|
function DashboardHome({ collections, globals, onCollectionClick, onGlobalClick, }) {
|
|
85
|
-
return (_jsxs("div", { className: "min-h-screen bg-gray-50", children: [_jsx("header", { className: "bg-white shadow-sm border-b", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: _jsxs("div", { className: "flex justify-between items-center py-4", children: [_jsx("div", { className: "flex items-center", children: _jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "RevealUI Admin" }) }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx("span", { className: "text-sm text-gray-500", children: "v0.1.0" }), _jsx(SignOutButton, {})] })] }) }) }), _jsx("main", { className: "max-w-7xl mx-auto py-6 sm:px-6 lg:px-8", children: _jsx("div", { className: "px-4 py-6 sm:px-0", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3", children: [_jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsxs("svg", { className: "h-8 w-8 text-gray-400", "aria-label": "Collections", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", role: "img", children: [_jsx("title", { children: "Collections" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" })] }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "Collections" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: collections.length })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm", children: collections.length > 0 ? (_jsx("ul", { className: "space-y-1 max-h-48 overflow-y-auto", children: collections.map((collection) => (_jsx("li", { className: "text-gray-600 hover:text-gray-900", children: _jsx("button", { type: "button", onClick: () => onCollectionClick(collection), className: "hover:underline cursor-pointer", children: String(collection.slug) }) }, String(collection.slug)))) })) : (_jsx("p", { className: "text-gray-500", children: "No collections configured" })) }) })] }), _jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsxs("svg", { className: "h-8 w-8 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-labelledby": "globals-icon-title", role: "img", children: [_jsx("title", { id: "globals-icon-title", children: "Globals" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4" })] }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "Globals" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: globals.length })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm", children: globals.length > 0 ? (_jsx("ul", { className: "space-y-1 max-h-32 overflow-y-auto", children: globals.map((global) => (_jsx("li", { className: "text-gray-600 hover:text-gray-900", children: _jsx("button", { type: "button", onClick: () => onGlobalClick(global), className: "hover:underline cursor-pointer", children: global.label || String(global.slug) }) }, String(global.slug)))) })) : (_jsx("p", { className: "text-gray-500", children: "No globals configured" })) }) })] }), _jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("div", { className: "h-8 w-8 bg-green-100 rounded-full flex items-center justify-center", children: _jsxs("svg", { className: "h-5 w-5 text-green-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-labelledby": "system-status-icon-title", role: "img", children: [_jsx("title", { id: "system-status-icon-title", children: "System Status: Healthy" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" })] }) }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "System Status" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: "Healthy" })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm text-gray-600", children: "RevealUI
|
|
85
|
+
return (_jsxs("div", { className: "min-h-screen bg-gray-50", children: [_jsx("header", { className: "bg-white shadow-sm border-b", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: _jsxs("div", { className: "flex justify-between items-center py-4", children: [_jsx("div", { className: "flex items-center", children: _jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "RevealUI Admin" }) }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx("span", { className: "text-sm text-gray-500", children: "v0.1.0" }), _jsx(SignOutButton, {})] })] }) }) }), _jsx("main", { className: "max-w-7xl mx-auto py-6 sm:px-6 lg:px-8", children: _jsx("div", { className: "px-4 py-6 sm:px-0", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3", children: [_jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsxs("svg", { className: "h-8 w-8 text-gray-400", "aria-label": "Collections", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", role: "img", children: [_jsx("title", { children: "Collections" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" })] }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "Collections" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: collections.length })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm", children: collections.length > 0 ? (_jsx("ul", { className: "space-y-1 max-h-48 overflow-y-auto", children: collections.map((collection) => (_jsx("li", { className: "text-gray-600 hover:text-gray-900", children: _jsx("button", { type: "button", onClick: () => onCollectionClick(collection), className: "hover:underline cursor-pointer", children: String(collection.slug) }) }, String(collection.slug)))) })) : (_jsx("p", { className: "text-gray-500", children: "No collections configured" })) }) })] }), _jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsxs("svg", { className: "h-8 w-8 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-labelledby": "globals-icon-title", role: "img", children: [_jsx("title", { id: "globals-icon-title", children: "Globals" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4" })] }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "Globals" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: globals.length })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm", children: globals.length > 0 ? (_jsx("ul", { className: "space-y-1 max-h-32 overflow-y-auto", children: globals.map((global) => (_jsx("li", { className: "text-gray-600 hover:text-gray-900", children: _jsx("button", { type: "button", onClick: () => onGlobalClick(global), className: "hover:underline cursor-pointer", children: global.label || String(global.slug) }) }, String(global.slug)))) })) : (_jsx("p", { className: "text-gray-500", children: "No globals configured" })) }) })] }), _jsxs("div", { className: "bg-white overflow-hidden shadow rounded-lg", children: [_jsx("div", { className: "p-5", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("div", { className: "h-8 w-8 bg-green-100 rounded-full flex items-center justify-center", children: _jsxs("svg", { className: "h-5 w-5 text-green-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-labelledby": "system-status-icon-title", role: "img", children: [_jsx("title", { id: "system-status-icon-title", children: "System Status: Healthy" }), _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" })] }) }) }), _jsx("div", { className: "ml-5 w-0 flex-1", children: _jsxs("dl", { children: [_jsx("dt", { className: "text-sm font-medium text-gray-500 truncate", children: "System Status" }), _jsx("dd", { className: "text-lg font-medium text-gray-900", children: "Healthy" })] }) })] }) }), _jsx("div", { className: "bg-gray-50 px-5 py-3", children: _jsx("div", { className: "text-sm text-gray-600", children: "RevealUI admin is running successfully" }) })] })] }) }) })] }));
|
|
86
86
|
}
|
|
87
87
|
// =============================================================================
|
|
88
88
|
// Error handling helpers
|
|
@@ -185,6 +185,49 @@ export function AdminDashboard({ config }) {
|
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
|
+
const handleBulkDelete = async (ids) => {
|
|
189
|
+
if (!state.view.collection)
|
|
190
|
+
return;
|
|
191
|
+
const confirmed = window.confirm(`Are you sure you want to delete ${ids.length} ${String(state.view.collection.slug)}? This action cannot be undone.`);
|
|
192
|
+
if (!confirmed)
|
|
193
|
+
return;
|
|
194
|
+
try {
|
|
195
|
+
dispatch({ type: 'SET_ERROR', error: null });
|
|
196
|
+
await apiClient.batchDelete({
|
|
197
|
+
collection: String(state.view.collection.slug),
|
|
198
|
+
ids,
|
|
199
|
+
});
|
|
200
|
+
if (state.view.collection) {
|
|
201
|
+
await fetchCollection(state.view.collection, state.page);
|
|
202
|
+
}
|
|
203
|
+
dispatch({ type: 'SET_SUCCESS', message: `${ids.length} documents deleted` });
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const msg = extractErrorMessage(err, 'Bulk delete failed. Please try again.');
|
|
207
|
+
logApiError(err, 'Bulk delete failed');
|
|
208
|
+
dispatch({ type: 'SET_ERROR', error: msg });
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const handleBulkPublish = async (ids) => {
|
|
212
|
+
if (!state.view.collection)
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
dispatch({ type: 'SET_ERROR', error: null });
|
|
216
|
+
await apiClient.batchUpdate({
|
|
217
|
+
collection: String(state.view.collection.slug),
|
|
218
|
+
items: ids.map((id) => ({ id, status: 'published' })),
|
|
219
|
+
});
|
|
220
|
+
if (state.view.collection) {
|
|
221
|
+
await fetchCollection(state.view.collection, state.page);
|
|
222
|
+
}
|
|
223
|
+
dispatch({ type: 'SET_SUCCESS', message: `${ids.length} documents published` });
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const msg = extractErrorMessage(err, 'Bulk publish failed. Please try again.');
|
|
227
|
+
logApiError(err, 'Bulk publish failed');
|
|
228
|
+
dispatch({ type: 'SET_ERROR', error: msg });
|
|
229
|
+
}
|
|
230
|
+
};
|
|
188
231
|
const handleDelete = async (document) => {
|
|
189
232
|
if (!(state.view.collection && document.id))
|
|
190
233
|
return;
|
|
@@ -293,7 +336,7 @@ export function AdminDashboard({ config }) {
|
|
|
293
336
|
const collection = state.view.collection;
|
|
294
337
|
if (collection)
|
|
295
338
|
void fetchCollection(collection, nextPage);
|
|
296
|
-
}, deleting: state.deleting })] })] }));
|
|
339
|
+
}, deleting: state.deleting, onBulkDelete: (ids) => void handleBulkDelete(ids), onBulkPublish: (ids) => void handleBulkPublish(ids) })] })] }));
|
|
297
340
|
}
|
|
298
341
|
// ── Document edit/create view ─────────────────────────────────────────
|
|
299
342
|
if (state.view.type === 'edit' && state.view.collection) {
|
|
@@ -10,7 +10,9 @@ interface CollectionListProps {
|
|
|
10
10
|
onDelete: (doc: RevealDocument) => void;
|
|
11
11
|
onPageChange: (page: number) => void;
|
|
12
12
|
deleting?: string | null;
|
|
13
|
+
onBulkDelete?: (ids: string[]) => void;
|
|
14
|
+
onBulkPublish?: (ids: string[]) => void;
|
|
13
15
|
}
|
|
14
|
-
export declare function CollectionList({ collection, documents, totalDocs, page, totalPages, onCreate, onEdit, onDelete, onPageChange, deleting, }: CollectionListProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function CollectionList({ collection, documents, totalDocs, page, totalPages, onCreate, onEdit, onDelete, onPageChange, deleting, onBulkDelete, onBulkPublish, }: CollectionListProps): import("react/jsx-runtime").JSX.Element;
|
|
15
17
|
export {};
|
|
16
18
|
//# sourceMappingURL=CollectionList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionList.d.ts","sourceRoot":"","sources":["../../../../src/client/admin/components/CollectionList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CollectionList.d.ts","sourceRoot":"","sources":["../../../../src/client/admin/components/CollectionList.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,cAAc,EAEf,MAAM,yBAAyB,CAAC;AAoCjC,UAAU,mBAAmB;IAC3B,UAAU,EAAE,sBAAsB,CAAC;IACnC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACzC;AAED,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,SAAS,EACT,SAAS,EACT,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,aAAa,GACd,EAAE,mBAAmB,2CA6QrB"}
|