flarecms 0.1.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 +73 -0
- package/dist/auth/index.js +40 -0
- package/dist/cli/commands.js +389 -0
- package/dist/cli/index.js +403 -0
- package/dist/cli/mcp.js +209 -0
- package/dist/db/index.js +164 -0
- package/dist/index.js +17626 -0
- package/package.json +105 -0
- package/scripts/fix-api-paths.mjs +32 -0
- package/scripts/fix-imports.mjs +38 -0
- package/scripts/prefix-css.mjs +45 -0
- package/src/api/lib/cache.ts +45 -0
- package/src/api/lib/response.ts +40 -0
- package/src/api/middlewares/auth.ts +186 -0
- package/src/api/middlewares/cors.ts +10 -0
- package/src/api/middlewares/rbac.ts +85 -0
- package/src/api/routes/auth.ts +377 -0
- package/src/api/routes/collections.ts +205 -0
- package/src/api/routes/content.ts +175 -0
- package/src/api/routes/device.ts +160 -0
- package/src/api/routes/magic.ts +150 -0
- package/src/api/routes/mcp.ts +273 -0
- package/src/api/routes/oauth.ts +160 -0
- package/src/api/routes/settings.ts +43 -0
- package/src/api/routes/setup.ts +307 -0
- package/src/api/routes/tokens.ts +80 -0
- package/src/api/schemas/auth.ts +15 -0
- package/src/api/schemas/index.ts +51 -0
- package/src/api/schemas/tokens.ts +24 -0
- package/src/auth/index.ts +28 -0
- package/src/cli/commands.ts +217 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/mcp.ts +210 -0
- package/src/cli/tests/cli.test.ts +40 -0
- package/src/cli/tests/create.test.ts +87 -0
- package/src/client/FlareAdminRouter.tsx +47 -0
- package/src/client/app.tsx +175 -0
- package/src/client/components/app-sidebar.tsx +227 -0
- package/src/client/components/collection-modal.tsx +215 -0
- package/src/client/components/content-list.tsx +247 -0
- package/src/client/components/dynamic-form.tsx +190 -0
- package/src/client/components/field-modal.tsx +221 -0
- package/src/client/components/settings/api-token-section.tsx +400 -0
- package/src/client/components/settings/general-section.tsx +224 -0
- package/src/client/components/settings/security-section.tsx +154 -0
- package/src/client/components/settings/seo-section.tsx +200 -0
- package/src/client/components/settings/signup-section.tsx +257 -0
- package/src/client/components/ui/accordion.tsx +78 -0
- package/src/client/components/ui/avatar.tsx +107 -0
- package/src/client/components/ui/badge.tsx +52 -0
- package/src/client/components/ui/button.tsx +60 -0
- package/src/client/components/ui/card.tsx +103 -0
- package/src/client/components/ui/checkbox.tsx +27 -0
- package/src/client/components/ui/collapsible.tsx +19 -0
- package/src/client/components/ui/dialog.tsx +162 -0
- package/src/client/components/ui/icon-picker.tsx +485 -0
- package/src/client/components/ui/icons-data.ts +8476 -0
- package/src/client/components/ui/input.tsx +20 -0
- package/src/client/components/ui/label.tsx +20 -0
- package/src/client/components/ui/popover.tsx +91 -0
- package/src/client/components/ui/select.tsx +204 -0
- package/src/client/components/ui/separator.tsx +23 -0
- package/src/client/components/ui/sheet.tsx +141 -0
- package/src/client/components/ui/sidebar.tsx +722 -0
- package/src/client/components/ui/skeleton.tsx +13 -0
- package/src/client/components/ui/sonner.tsx +47 -0
- package/src/client/components/ui/switch.tsx +30 -0
- package/src/client/components/ui/table.tsx +116 -0
- package/src/client/components/ui/tabs.tsx +80 -0
- package/src/client/components/ui/textarea.tsx +18 -0
- package/src/client/components/ui/tooltip.tsx +68 -0
- package/src/client/hooks/use-mobile.ts +19 -0
- package/src/client/index.css +149 -0
- package/src/client/index.ts +7 -0
- package/src/client/layouts/admin-layout.tsx +93 -0
- package/src/client/layouts/settings-layout.tsx +104 -0
- package/src/client/lib/api.ts +72 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.tsx +10 -0
- package/src/client/pages/collection-detail.tsx +634 -0
- package/src/client/pages/collections.tsx +180 -0
- package/src/client/pages/dashboard.tsx +133 -0
- package/src/client/pages/device.tsx +66 -0
- package/src/client/pages/document-detail-page.tsx +139 -0
- package/src/client/pages/documents-page.tsx +103 -0
- package/src/client/pages/login.tsx +345 -0
- package/src/client/pages/settings.tsx +65 -0
- package/src/client/pages/setup.tsx +129 -0
- package/src/client/pages/signup.tsx +188 -0
- package/src/client/store/auth.ts +30 -0
- package/src/client/store/collections.ts +13 -0
- package/src/client/store/config.ts +12 -0
- package/src/client/store/fetcher.ts +30 -0
- package/src/client/store/router.ts +95 -0
- package/src/client/store/schema.ts +39 -0
- package/src/client/store/settings.ts +31 -0
- package/src/client/types.ts +34 -0
- package/src/db/dynamic.ts +70 -0
- package/src/db/index.ts +16 -0
- package/src/db/migrations/001_initial_schema.ts +57 -0
- package/src/db/migrations/002_auth_tables.ts +84 -0
- package/src/db/migrator.ts +61 -0
- package/src/db/schema.ts +142 -0
- package/src/index.ts +12 -0
- package/src/server/index.ts +66 -0
- package/src/types.ts +20 -0
- package/style.css.d.ts +8 -0
- package/tests/css.test.ts +21 -0
- package/tests/modular.test.ts +29 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useStore } from '@nanostores/react';
|
|
2
|
+
import { $router, navigate } from '../store/router';
|
|
3
|
+
import {
|
|
4
|
+
Settings as SettingsIcon,
|
|
5
|
+
Search as SearchIcon,
|
|
6
|
+
ShieldCheck as SecurityIcon,
|
|
7
|
+
Globe as SeoIcon,
|
|
8
|
+
Users as SignupIcon,
|
|
9
|
+
Key as ApiIcon,
|
|
10
|
+
ChevronRight as ChevronRightIcon,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import { cn } from '../lib/utils';
|
|
13
|
+
import { Button } from '../components/ui/button';
|
|
14
|
+
|
|
15
|
+
export function SettingsSidebar() {
|
|
16
|
+
const page = useStore($router);
|
|
17
|
+
|
|
18
|
+
const nav = [
|
|
19
|
+
{ label: 'General', route: 'settings_general', icon: SettingsIcon },
|
|
20
|
+
{ label: 'SEO', route: 'settings_seo', icon: SeoIcon },
|
|
21
|
+
{ label: 'Security', route: 'settings_security', icon: SecurityIcon },
|
|
22
|
+
{ label: 'Signup', route: 'settings_signup', icon: SignupIcon },
|
|
23
|
+
{ label: 'API Tokens', route: 'settings', icon: ApiIcon },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="w-full lg:w-64 space-y-1 pr-4">
|
|
28
|
+
<div className="mb-8 px-3">
|
|
29
|
+
<h2 className="text-xl font-bold tracking-tight text-foreground flex items-center gap-2">
|
|
30
|
+
Settings
|
|
31
|
+
</h2>
|
|
32
|
+
<p className="text-[10px] uppercase tracking-[0.2em] font-semibold text-muted-foreground opacity-50 mt-1">
|
|
33
|
+
System Configuration
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div className="space-y-1">
|
|
38
|
+
{nav.map((item) => {
|
|
39
|
+
const isActive = page?.route === item.route;
|
|
40
|
+
return (
|
|
41
|
+
<Button
|
|
42
|
+
key={item.route}
|
|
43
|
+
variant="ghost"
|
|
44
|
+
className={cn(
|
|
45
|
+
'w-full justify-start gap-3 h-10 px-3 text-xs font-semibold tracking-wide transition-all group relative overflow-hidden',
|
|
46
|
+
isActive
|
|
47
|
+
? 'bg-primary/5 text-primary'
|
|
48
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
49
|
+
)}
|
|
50
|
+
onClick={() => navigate(item.route)}
|
|
51
|
+
>
|
|
52
|
+
{isActive && (
|
|
53
|
+
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-4 bg-primary rounded-full" />
|
|
54
|
+
)}
|
|
55
|
+
<item.icon
|
|
56
|
+
className={cn(
|
|
57
|
+
'size-4 transition-colors',
|
|
58
|
+
isActive
|
|
59
|
+
? 'text-primary'
|
|
60
|
+
: 'opacity-40 group-hover:opacity-100',
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
{item.label}
|
|
64
|
+
<ChevronRightIcon
|
|
65
|
+
className={cn(
|
|
66
|
+
'size-3 ml-auto opacity-0 -translate-x-1 transition-all',
|
|
67
|
+
isActive && 'opacity-20 translate-x-0',
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</Button>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function SettingsLayout({
|
|
79
|
+
children,
|
|
80
|
+
title,
|
|
81
|
+
subtitle,
|
|
82
|
+
}: {
|
|
83
|
+
children: React.ReactNode;
|
|
84
|
+
title: string;
|
|
85
|
+
subtitle: string;
|
|
86
|
+
}) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="max-w-container mx-auto py-8 px-6 flex flex-col lg:flex-row gap-12">
|
|
89
|
+
<SettingsSidebar />
|
|
90
|
+
<div className="flex-1 space-y-8">
|
|
91
|
+
<div className="border-b border-border/50 pb-8 relative">
|
|
92
|
+
<h1 className="text-3xl font-black tracking-tight text-foreground">
|
|
93
|
+
{title}
|
|
94
|
+
</h1>
|
|
95
|
+
<p className="text-sm text-muted-foreground mt-2 font-medium">
|
|
96
|
+
{subtitle}
|
|
97
|
+
</p>
|
|
98
|
+
<div className="absolute -bottom-px left-0 w-12 h-px bg-primary" />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="mt-8">{children}</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import ky from 'ky';
|
|
2
|
+
import { $auth } from '../store/auth';
|
|
3
|
+
import { navigate } from '../store/router';
|
|
4
|
+
import { $apiBaseUrl } from '../store/config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Core API instance pre-configured with:
|
|
8
|
+
* 1. Automatic Bearer Token injection via beforeRequest hook
|
|
9
|
+
* 2. Automatic Zero-Config redirect via afterResponse hook
|
|
10
|
+
* 3. Standard error handling for non-2xx responses (built-in ky behavior)
|
|
11
|
+
*/
|
|
12
|
+
export const api = ky.create({
|
|
13
|
+
prefix: $apiBaseUrl.get(),
|
|
14
|
+
hooks: {
|
|
15
|
+
beforeRequest: [
|
|
16
|
+
({ request }) => {
|
|
17
|
+
const { token } = $auth.get();
|
|
18
|
+
if (token) {
|
|
19
|
+
request.headers.set('Authorization', `Bearer ${token}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
afterResponse: [
|
|
24
|
+
async ({ request, response }) => {
|
|
25
|
+
// Intercept Unauthorized (Session Expired)
|
|
26
|
+
if (response.status === 401 && !request.url.includes('/auth/')) {
|
|
27
|
+
$auth.set({ token: null, user: null });
|
|
28
|
+
if (!request.url.includes('/login')) {
|
|
29
|
+
navigate('login');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Intercept Setup Required (Zero-Config flow)
|
|
34
|
+
if (response.status === 403) {
|
|
35
|
+
try {
|
|
36
|
+
const data: any = await response.clone().json();
|
|
37
|
+
if (data.code === 'SETUP_REQUIRED' && !request.url.includes('/setup')) {
|
|
38
|
+
navigate('setup');
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Not JSON or parse error - ignore
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A legacy wrapper for components still expecting a standard Response object from apiFetch.
|
|
51
|
+
* Uses the ky 'api' instance under the hood.
|
|
52
|
+
*
|
|
53
|
+
* @deprecated Use the 'api' instance directly (e.g., api.get().json()) for new code.
|
|
54
|
+
*/
|
|
55
|
+
export async function apiFetch(path: string, options: RequestInit = {}) {
|
|
56
|
+
// ky handles most RequestInit options directly
|
|
57
|
+
const { headers, ...rest } = options;
|
|
58
|
+
|
|
59
|
+
// Cast method to something ky accepts (strict string types)
|
|
60
|
+
const method = (options.method?.toLowerCase() || 'get') as any;
|
|
61
|
+
|
|
62
|
+
// We cast to any to allow the caller to treat it as a standard fetch Response
|
|
63
|
+
// which has .json(): Promise<any> instead of Promise<unknown>
|
|
64
|
+
return api(path, {
|
|
65
|
+
method,
|
|
66
|
+
headers,
|
|
67
|
+
...rest,
|
|
68
|
+
// ky throws on non-2xx by default, but standard fetch/apiFetch does not.
|
|
69
|
+
// We set throwHttpErrors: false to maintain backward compatibility during the transition.
|
|
70
|
+
throwHttpErrors: false,
|
|
71
|
+
}) as any;
|
|
72
|
+
}
|