create-aphex 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 +56 -0
- package/dist/index.js +160 -0
- package/package.json +40 -0
- package/templates/base/.dockerignore +44 -0
- package/templates/base/.env.example +38 -0
- package/templates/base/CHANGELOG.md +118 -0
- package/templates/base/Dockerfile +69 -0
- package/templates/base/README.md +147 -0
- package/templates/base/aphex.config.ts +45 -0
- package/templates/base/docker-compose.yml +31 -0
- package/templates/base/drizzle/0000_tiny_redwing.sql +226 -0
- package/templates/base/drizzle/meta/0000_snapshot.json +1379 -0
- package/templates/base/drizzle/meta/_journal.json +13 -0
- package/templates/base/drizzle.config.ts +17 -0
- package/templates/base/package.json +81 -0
- package/templates/base/prod.docker-compose.yml +61 -0
- package/templates/base/src/app.css +32 -0
- package/templates/base/src/app.d.ts +26 -0
- package/templates/base/src/app.html +11 -0
- package/templates/base/src/hooks.server.ts +52 -0
- package/templates/base/src/lib/assets/favicon.svg +5 -0
- package/templates/base/src/lib/auth-client.ts +19 -0
- package/templates/base/src/lib/index.ts +1 -0
- package/templates/base/src/lib/schemaTypes/index.ts +11 -0
- package/templates/base/src/lib/schemaTypes/post.ts +47 -0
- package/templates/base/src/lib/server/auth/better-auth/instance.ts +165 -0
- package/templates/base/src/lib/server/auth/index.ts +25 -0
- package/templates/base/src/lib/server/auth/instance.ts +7 -0
- package/templates/base/src/lib/server/auth/service.ts +486 -0
- package/templates/base/src/lib/server/cache/index.ts +10 -0
- package/templates/base/src/lib/server/db/auth-schema.ts +86 -0
- package/templates/base/src/lib/server/db/cms-schema.ts +23 -0
- package/templates/base/src/lib/server/db/index.ts +55 -0
- package/templates/base/src/lib/server/db/schema.ts +3 -0
- package/templates/base/src/lib/server/email/components/EmailLayout.svelte +24 -0
- package/templates/base/src/lib/server/email/components/EmailVerification.svelte +26 -0
- package/templates/base/src/lib/server/email/components/Invitation.svelte +30 -0
- package/templates/base/src/lib/server/email/components/PasswordReset.svelte +28 -0
- package/templates/base/src/lib/server/email/index.ts +28 -0
- package/templates/base/src/lib/server/email/invitation-hook.ts +56 -0
- package/templates/base/src/lib/server/email/renderer.ts +13 -0
- package/templates/base/src/lib/server/email/templates/email-verification.ts +8 -0
- package/templates/base/src/lib/server/email/templates/invitation.ts +8 -0
- package/templates/base/src/lib/server/email/templates/password-reset.ts +7 -0
- package/templates/base/src/lib/server/services/organization.ts +68 -0
- package/templates/base/src/lib/server/storage/index.ts +28 -0
- package/templates/base/src/lib/stores/activeTab.svelte.ts +14 -0
- package/templates/base/src/lib/utils/imageBuilder.ts +3 -0
- package/templates/base/src/lib/utils/slug.ts +33 -0
- package/templates/base/src/routes/(protected)/admin/+layout.server.ts +86 -0
- package/templates/base/src/routes/(protected)/admin/+layout.svelte +78 -0
- package/templates/base/src/routes/(protected)/admin/+page.server.ts +60 -0
- package/templates/base/src/routes/(protected)/admin/+page.svelte +34 -0
- package/templates/base/src/routes/(protected)/admin/organizations/+page.server.ts +39 -0
- package/templates/base/src/routes/(protected)/admin/organizations/+page.svelte +31 -0
- package/templates/base/src/routes/(protected)/admin/organizations/_components/CreateOrganization.svelte +141 -0
- package/templates/base/src/routes/(protected)/admin/organizations/_components/OrganizationsList.svelte +102 -0
- package/templates/base/src/routes/(protected)/admin/settings/+layout.server.ts +42 -0
- package/templates/base/src/routes/(protected)/admin/settings/+layout.svelte +116 -0
- package/templates/base/src/routes/(protected)/admin/settings/+page.server.ts +16 -0
- package/templates/base/src/routes/(protected)/admin/settings/+page.svelte +32 -0
- package/templates/base/src/routes/(protected)/admin/settings/_components/AccountSettings.svelte +177 -0
- package/templates/base/src/routes/(protected)/admin/settings/_components/ApiKeysSettings.svelte +472 -0
- package/templates/base/src/routes/(protected)/admin/settings/_components/OrganizationsSettings.svelte +182 -0
- package/templates/base/src/routes/(protected)/admin/settings/account/+page.server.ts +25 -0
- package/templates/base/src/routes/(protected)/admin/settings/account/+page.svelte +23 -0
- package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.server.ts +54 -0
- package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.svelte +14 -0
- package/templates/base/src/routes/(protected)/admin/settings/members/+page.server.ts +31 -0
- package/templates/base/src/routes/(protected)/admin/settings/members/+page.svelte +338 -0
- package/templates/base/src/routes/(protected)/admin/settings/roles/+page.server.ts +17 -0
- package/templates/base/src/routes/(protected)/admin/settings/roles/+page.svelte +339 -0
- package/templates/base/src/routes/+layout.svelte +12 -0
- package/templates/base/src/routes/+page.svelte +2 -0
- package/templates/base/src/routes/api/[...slug]/+server.ts +35 -0
- package/templates/base/src/routes/api/instance-settings/+server.ts +70 -0
- package/templates/base/src/routes/api/invitations/+server.ts +54 -0
- package/templates/base/src/routes/api/invitations/[id]/accept/+server.ts +54 -0
- package/templates/base/src/routes/api/invitations/[id]/reject/+server.ts +49 -0
- package/templates/base/src/routes/api/settings/api-keys/+server.ts +57 -0
- package/templates/base/src/routes/api/settings/api-keys/[id]/+server.ts +53 -0
- package/templates/base/src/routes/god-mode/+layout.server.ts +32 -0
- package/templates/base/src/routes/god-mode/+layout.svelte +155 -0
- package/templates/base/src/routes/god-mode/+page.server.ts +10 -0
- package/templates/base/src/routes/god-mode/+page.svelte +32 -0
- package/templates/base/src/routes/god-mode/_components/CreateOrganization.svelte +141 -0
- package/templates/base/src/routes/god-mode/_components/OrganizationsList.svelte +169 -0
- package/templates/base/src/routes/god-mode/organizations/+page.server.ts +43 -0
- package/templates/base/src/routes/god-mode/organizations/+page.svelte +74 -0
- package/templates/base/src/routes/invitations/+page.server.ts +48 -0
- package/templates/base/src/routes/invitations/+page.svelte +264 -0
- package/templates/base/src/routes/invite/[token]/+page.server.ts +59 -0
- package/templates/base/src/routes/invite/[token]/+page.svelte +109 -0
- package/templates/base/src/routes/login/+page.server.ts +14 -0
- package/templates/base/src/routes/login/+page.svelte +332 -0
- package/templates/base/src/routes/media/[id]/[filename]/+server.ts +3 -0
- package/templates/base/src/routes/reset-password/[token]/+page.server.ts +15 -0
- package/templates/base/src/routes/reset-password/[token]/+page.svelte +179 -0
- package/templates/base/src/routes/verify-email/+page.svelte +41 -0
- package/templates/base/static/images/aphex-darkmode.png +0 -0
- package/templates/base/static/images/aphex-lightmode.png +0 -0
- package/templates/base/static/robots.txt +3 -0
- package/templates/base/svelte.config.js +19 -0
- package/templates/base/tsconfig.json +20 -0
- package/templates/base/vite.config.ts +138 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit';
|
|
2
|
+
// import { pgConnectionUrl } from '@aphexcms/postgresql-adapter';
|
|
3
|
+
|
|
4
|
+
// Note: Ideally we'd use pgConnectionUrl from @aphexcms/postgresql-adapter,
|
|
5
|
+
// but drizzle-kit has issues resolving workspace dependencies in monorepos.
|
|
6
|
+
// Inlining the connection URL logic as a workaround.
|
|
7
|
+
const databaseUrl =
|
|
8
|
+
process.env.DATABASE_URL ||
|
|
9
|
+
`postgresql://${process.env.PG_USER || 'root'}:${process.env.PG_PASSWORD || 'mysecretpassword'}@${process.env.PG_HOST || 'localhost'}:${process.env.PG_PORT || '5432'}/${process.env.PG_DATABASE || 'local'}`;
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
schema: './src/lib/server/db/schema.ts',
|
|
13
|
+
dialect: 'postgresql',
|
|
14
|
+
dbCredentials: { url: databaseUrl },
|
|
15
|
+
verbose: true,
|
|
16
|
+
strict: true
|
|
17
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aphexcms/base",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite dev --force --host",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
11
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
12
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
13
|
+
"db:start": "docker compose up -d",
|
|
14
|
+
"db:delete": "docker compose down -v",
|
|
15
|
+
"db:push": "drizzle-kit push",
|
|
16
|
+
"db:generate": "drizzle-kit generate",
|
|
17
|
+
"db:migrate": "drizzle-kit migrate",
|
|
18
|
+
"db:studio": "drizzle-kit studio",
|
|
19
|
+
"generate:types": "aphex generate:types ./src/lib/schemaTypes/index.ts ./src/lib/generated-types.ts",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:verbose": "vitest run --reporter=verbose",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"test:ui": "vitest --ui",
|
|
24
|
+
"test:local": "vitest run comprehensive-local-api",
|
|
25
|
+
"test:http": "vitest run comprehensive-http-api",
|
|
26
|
+
"test:graphql": "vitest run comprehensive-graphql-api",
|
|
27
|
+
"test:all": "vitest run comprehensive",
|
|
28
|
+
"test:coverage": "vitest run --coverage",
|
|
29
|
+
"test:date": "tsx scripts/test-date-localapi.ts"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@aphexcms/cms-core": "^5.0.0",
|
|
33
|
+
"@aphexcms/nodemailer-adapter": "^7.0.0",
|
|
34
|
+
"@aphexcms/postgresql-adapter": "^6.0.0",
|
|
35
|
+
"@aphexcms/resend-adapter": "^6.0.0",
|
|
36
|
+
"@aphexcms/storage-s3": "^6.0.0",
|
|
37
|
+
"@aphexcms/ui": "^0.4.0",
|
|
38
|
+
"@better-auth/api-key": "^1.5.3",
|
|
39
|
+
"@better-auth/drizzle-adapter": "^1.5.3",
|
|
40
|
+
"@envelop/graphql-jit": "^11.0.0",
|
|
41
|
+
"@graphql-yoga/render-graphiql": "^5.16.2",
|
|
42
|
+
"better-auth": "^1.5.3",
|
|
43
|
+
"better-svelte-email": "^1.4.0",
|
|
44
|
+
"drizzle-orm": "^0.44.7",
|
|
45
|
+
"graphql": "^16.12.0",
|
|
46
|
+
"graphql-yoga": "^5.16.2",
|
|
47
|
+
"mode-watcher": "^1.1.0",
|
|
48
|
+
"postgres": "^3.4.7",
|
|
49
|
+
"sharp": "^0.34.5",
|
|
50
|
+
"svelte-sonner": "^1.0.7"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@hono/zod-validator": "^0.7.6",
|
|
54
|
+
"@internationalized/date": "^3.10.0",
|
|
55
|
+
"@lucide/svelte": "^0.554.0",
|
|
56
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
57
|
+
"@sveltejs/adapter-node": "^5.4.0",
|
|
58
|
+
"@sveltejs/kit": "^2.49.0",
|
|
59
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
60
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
61
|
+
"@types/node": "^22.0.0",
|
|
62
|
+
"@vitest/ui": "^4.0.14",
|
|
63
|
+
"bits-ui": "^2.14.4",
|
|
64
|
+
"clsx": "^2.1.1",
|
|
65
|
+
"dotenv": "^17.2.3",
|
|
66
|
+
"drizzle-kit": "^0.31.7",
|
|
67
|
+
"hono": "^4.12.15",
|
|
68
|
+
"mdsvex": "^0.12.6",
|
|
69
|
+
"svelte": "^5.44.0",
|
|
70
|
+
"svelte-check": "^4.3.4",
|
|
71
|
+
"tailwind-merge": "^3.4.0",
|
|
72
|
+
"tailwind-variants": "^3.2.2",
|
|
73
|
+
"tailwindcss": "^4.1.17",
|
|
74
|
+
"tsx": "^4.20.6",
|
|
75
|
+
"tw-animate-css": "^1.4.0",
|
|
76
|
+
"typescript": "^5.9.3",
|
|
77
|
+
"vite": "^7.2.4",
|
|
78
|
+
"vitest": "^4.0.14",
|
|
79
|
+
"zod": "^4.3.6"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
container_name: aphex-postgres
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
env_file:
|
|
7
|
+
- .env.production
|
|
8
|
+
environment:
|
|
9
|
+
POSTGRES_USER: ${DB_USER}
|
|
10
|
+
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
11
|
+
POSTGRES_DB: ${DB_NAME}
|
|
12
|
+
ports:
|
|
13
|
+
- '5433:5432'
|
|
14
|
+
volumes:
|
|
15
|
+
- postgres_data:/var/lib/postgresql/data
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER:-aphex} -d ${DB_NAME:-aphex_cms}']
|
|
18
|
+
interval: 10s
|
|
19
|
+
timeout: 5s
|
|
20
|
+
retries: 5
|
|
21
|
+
|
|
22
|
+
studio:
|
|
23
|
+
build:
|
|
24
|
+
context: ../..
|
|
25
|
+
dockerfile: apps/studio/Dockerfile
|
|
26
|
+
args:
|
|
27
|
+
ACCESS_API_KEY: ${ACCESS_API_KEY}
|
|
28
|
+
GITHUB_TOKEN: ${GITHUB_TOKEN}
|
|
29
|
+
RESEND_API_KEY: ${RESEND_API_KEY}
|
|
30
|
+
AUTH_SECRET: ${AUTH_SECRET}
|
|
31
|
+
AUTH_URL: ${AUTH_URL}
|
|
32
|
+
DATABASE_URL: ${DATABASE_URL}
|
|
33
|
+
container_name: aphex-studio
|
|
34
|
+
restart: unless-stopped
|
|
35
|
+
env_file:
|
|
36
|
+
- .env.production
|
|
37
|
+
ports:
|
|
38
|
+
- '3020:3000'
|
|
39
|
+
environment:
|
|
40
|
+
- .env.production
|
|
41
|
+
depends_on:
|
|
42
|
+
postgres:
|
|
43
|
+
condition: service_healthy
|
|
44
|
+
volumes:
|
|
45
|
+
- ./storage:/app/apps/studio/storage
|
|
46
|
+
|
|
47
|
+
cloudflared:
|
|
48
|
+
image: cloudflare/cloudflared:latest
|
|
49
|
+
container_name: aphex-cloudflared
|
|
50
|
+
restart: unless-stopped
|
|
51
|
+
command: tunnel run
|
|
52
|
+
environment:
|
|
53
|
+
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
|
|
54
|
+
depends_on:
|
|
55
|
+
- studio
|
|
56
|
+
profiles:
|
|
57
|
+
- cloudflare
|
|
58
|
+
|
|
59
|
+
volumes:
|
|
60
|
+
postgres_data:
|
|
61
|
+
driver: local
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import 'tw-animate-css';
|
|
3
|
+
@import '@aphexcms/ui/shadcn/css';
|
|
4
|
+
@source '../node_modules/@aphexcms/ui/dist/**/*.{html,js,svelte,ts}'; /*Import shadcn tailwind styles*/
|
|
5
|
+
@source '../node_modules/@aphexcms/cms-core/dist/**/*.{html,js,svelte,ts}'; /*Import cms-core component styles*/
|
|
6
|
+
|
|
7
|
+
/* Ensure full height layout */
|
|
8
|
+
html,
|
|
9
|
+
body {
|
|
10
|
+
height: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Custom utility classes */
|
|
14
|
+
@layer utilities {
|
|
15
|
+
.full-height-layout {
|
|
16
|
+
height: 100vh;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.admin-content-height {
|
|
22
|
+
height: calc(100vh - 3rem); /* Desktop: subtract top navbar */
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.admin-content-height-mobile {
|
|
28
|
+
height: calc(100vh - 6rem); /* Mobile: subtract top navbar + breadcrumb */
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// See https://svelte.dev/docs/kit/types#app.d.ts
|
|
2
|
+
// for information about these interfaces
|
|
3
|
+
//
|
|
4
|
+
import type { CMSInstances } from '@aphexcms/cms-core/server';
|
|
5
|
+
import type { Auth } from '@aphexcms/cms-core/server';
|
|
6
|
+
|
|
7
|
+
// Side-effect imports that activate module augmentations globally:
|
|
8
|
+
// - generated-types: schema-derived document/collection types
|
|
9
|
+
// - @aphexcms/cms-core/app-augment: PageData.rbac typing
|
|
10
|
+
import '$lib/generated-types';
|
|
11
|
+
import '@aphexcms/cms-core/app-augment';
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
namespace App {
|
|
15
|
+
// interface Error {}
|
|
16
|
+
interface Locals {
|
|
17
|
+
aphexCMS: CMSInstances;
|
|
18
|
+
auth?: Auth; // Available in protected routes
|
|
19
|
+
}
|
|
20
|
+
// interface PageData {}
|
|
21
|
+
// interface PageState {}
|
|
22
|
+
// interface Platform {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
%sveltekit.head%
|
|
7
|
+
</head>
|
|
8
|
+
<body data-sveltekit-preload-data="hover">
|
|
9
|
+
<div style="display: contents">%sveltekit.body%</div>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { sequence } from '@sveltejs/kit/hooks';
|
|
2
|
+
import { redirect } from '@sveltejs/kit';
|
|
3
|
+
import type { Handle } from '@sveltejs/kit';
|
|
4
|
+
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
|
5
|
+
import { building, dev } from '$app/environment';
|
|
6
|
+
import { createCMSHook } from '@aphexcms/cms-core/server';
|
|
7
|
+
import { auth } from '$lib/server/auth';
|
|
8
|
+
|
|
9
|
+
// Better Auth hook (handles /api/auth/* routes)
|
|
10
|
+
const authHook: Handle = async ({ event, resolve }) => {
|
|
11
|
+
return svelteKitHandler({ event, resolve, auth, building });
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// CMS hook for dependency injection and route protection
|
|
15
|
+
// Schema changes invalidate the module graph and set __aphexSchemasDirty,
|
|
16
|
+
// which resets the hook instance so the next request re-initializes.
|
|
17
|
+
let aphexHookInstance: Handle | null = null;
|
|
18
|
+
|
|
19
|
+
const aphexHook: Handle = async ({ event, resolve }) => {
|
|
20
|
+
// Check if schemas changed (set by Vite plugin)
|
|
21
|
+
if ((global as any).__aphexSchemasDirty) {
|
|
22
|
+
(global as any).__aphexSchemasDirty = false;
|
|
23
|
+
aphexHookInstance = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!aphexHookInstance) {
|
|
27
|
+
// Cache-bust so Vite returns a freshly evaluated module after HMR —
|
|
28
|
+
// plain `import('../aphex.config.ts')` returns the cached instance even
|
|
29
|
+
// after module-graph invalidation, so the schemaTypes array stays stale.
|
|
30
|
+
const cacheBust = dev ? `?t=${Date.now()}` : '';
|
|
31
|
+
const cmsConfig = (await import(/* @vite-ignore */ `../aphex.config.ts${cacheBust}`)).default;
|
|
32
|
+
// During HMR, the module graph may be partially invalidated and the
|
|
33
|
+
// dynamic import can resolve before the config module has re-executed,
|
|
34
|
+
// yielding undefined. Skip this request and let the next one retry.
|
|
35
|
+
if (!cmsConfig) {
|
|
36
|
+
if (dev) console.warn('[CMS] Config not ready during HMR, skipping request');
|
|
37
|
+
return resolve(event);
|
|
38
|
+
}
|
|
39
|
+
aphexHookInstance = createCMSHook(cmsConfig);
|
|
40
|
+
}
|
|
41
|
+
return aphexHookInstance({ event, resolve });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const routingHook: Handle = async ({ event, resolve }) => {
|
|
45
|
+
if (event.url.pathname === '/') {
|
|
46
|
+
throw redirect(302, '/admin');
|
|
47
|
+
}
|
|
48
|
+
return resolve(event);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Combine hooks - authHook must be first, then CMS for DB access and auth protection
|
|
52
|
+
export const handle = sequence(authHook, aphexHook, routingHook);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512">
|
|
3
|
+
<path d="M0 0 C17.1474496 14.16905616 25.92496627 35.45820085 34.56640625 55.4140625 C35.34334316 57.19720923 36.12053079 58.98024674 36.89794922 60.76318359 C51.78722595 94.97195072 66.32335778 129.32882671 80.31640625 163.9140625 C80.6444928 164.72450012 80.97257935 165.53493774 81.31060791 166.36993408 C99.98913061 212.53216334 117.78569191 259.01293728 134.00390625 306.1015625 C134.36190811 307.13669983 134.36190811 307.13669983 134.72714233 308.19274902 C137.32766526 315.72906515 139.80262285 323.3004011 142.1171875 330.9296875 C142.43800545 331.95940926 142.43800545 331.95940926 142.76530457 333.00993347 C145.2565569 341.39135358 146.09964488 351.05219488 142.44140625 359.23046875 C139.92789443 362.992009 136.34564423 365.89944351 132.31640625 367.9140625 C124.8558052 368.61421121 118.36839373 368.62364314 112.31640625 363.9140625 C105.53664487 356.69699976 100.652558 348.68983785 95.94140625 340.0390625 C90.47619491 330.29913602 84.93335438 322.05287151 77.31640625 313.9140625 C75.27331056 311.57880549 73.26394666 309.2152832 71.25390625 306.8515625 C70.24519648 305.67152884 69.23608749 304.49183631 68.2265625 303.3125 C67.73816895 302.74112305 67.24977539 302.16974609 66.74658203 301.58105469 C65.40017275 300.01169952 64.04454028 298.45078423 62.6875 296.890625 C48.81582552 280.69615056 38.44623629 261.89870442 27.89111328 243.45849609 C-8.42360388 178.69858879 -8.42360388 178.69858879 -68.08203125 137.78515625 C-79.11186673 135.11741947 -89.899608 136.09230489 -99.68359375 141.9140625 C-115.46806791 152.47947666 -122.3076261 172.13261849 -126.43359375 189.7890625 C-126.73185059 191.06136719 -126.73185059 191.06136719 -127.03613281 192.359375 C-130.16760015 206.71148601 -131.06828893 221.08194412 -130.99609375 235.7265625 C-130.9939386 236.51966827 -130.99178345 237.31277405 -130.98956299 238.12991333 C-131.31394288 266.6844263 -131.31394288 266.6844263 -123.23828125 293.5859375 C-112.95312656 315.60340432 -111.26097095 345.34400812 -119.5 368.0390625 C-124.86779615 381.07766965 -132.78250397 390.79435644 -145.68359375 396.9140625 C-155.27731384 400.41943083 -164.20346687 401.56551213 -174.37109375 401.4140625 C-176.32958252 401.39702271 -176.32958252 401.39702271 -178.32763672 401.37963867 C-197.70054192 400.99323331 -215.16231823 397.0095896 -230.68359375 384.9140625 C-231.37066406 384.41519531 -232.05773437 383.91632813 -232.765625 383.40234375 C-244.80767188 373.97296976 -251.15090869 358.63090674 -253.68359375 343.9140625 C-253.9270261 339.66653687 -253.91464574 335.41789154 -253.93359375 331.1640625 C-253.96453125 329.45089844 -253.96453125 329.45089844 -253.99609375 327.703125 C-254.00125 326.58164062 -254.00640625 325.46015625 -254.01171875 324.3046875 C-254.02106445 323.29438477 -254.03041016 322.28408203 -254.04003906 321.24316406 C-253.25260131 313.88871121 -250.02140677 307.08532381 -247.0859375 300.359375 C-246.73986954 299.55749252 -246.39380157 298.75561005 -246.0372467 297.9294281 C-245.29136983 296.20150738 -244.54396437 294.47424582 -243.79516602 292.74758911 C-241.75797546 288.04789133 -239.73451175 283.34229198 -237.7109375 278.63671875 C-237.0834108 277.17808662 -237.0834108 277.17808662 -236.44320679 275.68998718 C-232.12759637 265.64489937 -227.89599701 255.56528394 -223.68359375 245.4765625 C-223.31303864 244.58933502 -222.94248352 243.70210754 -222.56069946 242.78799438 C-221.05624911 239.1852842 -219.55230259 235.58236459 -218.04930115 231.97904968 C-215.45620988 225.7624217 -212.85713855 219.54832537 -210.25390625 213.3359375 C-199.87599396 188.56875674 -189.53860671 163.78483086 -179.21582031 138.99462891 C-176.70326253 132.96093438 -174.18966694 126.92767262 -171.67578125 120.89453125 C-171.2632309 119.90436005 -170.85068054 118.91418884 -170.42562866 117.89401245 C-168.35461551 112.92351005 -166.28304289 107.95324201 -164.21044922 102.98339844 C-160.33953189 93.69973915 -156.47385511 84.41400336 -152.62658691 75.12051392 C-150.82992691 70.78139762 -149.03030349 66.44351312 -147.23046875 62.10571289 C-146.38199036 60.05847127 -145.53492768 58.01064218 -144.68945312 55.9621582 C-140.72547669 46.35880091 -136.72273166 36.78723512 -132.3203125 27.375 C-132.00310242 26.69433975 -131.68589233 26.0136795 -131.35906982 25.31239319 C-123.25124472 8.26154126 -109.39227096 -6.16254505 -91.68359375 -13.0859375 C-59.99340229 -22.6620319 -26.67593546 -20.59334511 0 0 Z " fill="#FFFFFF" transform="translate(308.68359375,64.0859375)"/>
|
|
4
|
+
<path d="M0 0 C18.24717952 9.79342293 31.4582424 26.28742033 37.6171875 45.97265625 C39.90862161 58.10790422 38.62948747 76.77026254 32.3125 87.5625 C31.23887442 89.06398132 30.13305826 90.54284625 29 92 C28.04867187 93.39798828 28.04867187 93.39798828 27.078125 94.82421875 C22.05477616 102.1158627 16.59613089 108.96607145 8 112 C-4.55244493 113.74100692 -12.66496084 107.03937869 -22.140625 99.87890625 C-25.14482573 97.61370274 -28.2068393 95.46853473 -31.33056641 93.37231445 C-44.35072813 84.61921656 -53.55106508 73.69367426 -57 58 C-59.27762162 41.63399726 -53.36243485 19.43955677 -44.1875 5.9375 C-32.8352893 -8.66127096 -15.43371672 -7.37348527 0 0 Z " fill="#FFFFFF" transform="translate(254,240)"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Better Auth client for Svelte
|
|
2
|
+
import { createAuthClient } from 'better-auth/svelte';
|
|
3
|
+
import { apiKeyClient } from '@better-auth/api-key/client';
|
|
4
|
+
|
|
5
|
+
export const authClient = createAuthClient({
|
|
6
|
+
// Base URL is same domain, so we don't need to specify it
|
|
7
|
+
plugins: [
|
|
8
|
+
apiKeyClient() // Enable API key management from client
|
|
9
|
+
]
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Export specific methods for convenience
|
|
13
|
+
export const {
|
|
14
|
+
signIn,
|
|
15
|
+
signUp,
|
|
16
|
+
signOut,
|
|
17
|
+
useSession,
|
|
18
|
+
apiKey // API key management methods
|
|
19
|
+
} = authClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// place files you want to import through the `$lib` alias in this folder.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Schema Registry
|
|
2
|
+
// ================
|
|
3
|
+
// Define your content schemas here and add them to the array below.
|
|
4
|
+
// Each schema is either a 'document' (top-level entity) or an 'object' (reusable nested structure).
|
|
5
|
+
//
|
|
6
|
+
// See the Aphex CMS docs for available field types:
|
|
7
|
+
// string, text, number, boolean, slug, image, date, datetime, url, array, object, reference
|
|
8
|
+
|
|
9
|
+
import post from './post.js';
|
|
10
|
+
|
|
11
|
+
export const schemaTypes = [post];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SchemaType } from '@aphexcms/cms-core';
|
|
2
|
+
|
|
3
|
+
const post: SchemaType = {
|
|
4
|
+
type: 'document',
|
|
5
|
+
name: 'post',
|
|
6
|
+
title: 'Post',
|
|
7
|
+
description: 'Blog posts',
|
|
8
|
+
fields: [
|
|
9
|
+
{
|
|
10
|
+
name: 'title',
|
|
11
|
+
type: 'string',
|
|
12
|
+
title: 'Title',
|
|
13
|
+
validation: (Rule) => Rule.required()
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'slug',
|
|
17
|
+
type: 'slug',
|
|
18
|
+
title: 'Slug',
|
|
19
|
+
source: 'title',
|
|
20
|
+
validation: (Rule) => Rule.required()
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'excerpt',
|
|
24
|
+
type: 'text',
|
|
25
|
+
title: 'Excerpt',
|
|
26
|
+
description: 'A short summary of the post'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'content',
|
|
30
|
+
type: 'text',
|
|
31
|
+
title: 'Content'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'coverImage',
|
|
35
|
+
type: 'image',
|
|
36
|
+
title: 'Cover Image'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'published',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
title: 'Published',
|
|
42
|
+
initialValue: false
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default post;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// apps/studio/src/lib/server/auth/better-auth/instance.ts
|
|
2
|
+
|
|
3
|
+
import { env } from '$env/dynamic/private';
|
|
4
|
+
import { betterAuth } from 'better-auth';
|
|
5
|
+
import { apiKey } from '@better-auth/api-key';
|
|
6
|
+
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
|
7
|
+
import { createAuthMiddleware } from 'better-auth/api';
|
|
8
|
+
import type { DatabaseAdapter } from '@aphexcms/cms-core/server';
|
|
9
|
+
import type { EmailAdapter } from '@aphexcms/cms-core/server';
|
|
10
|
+
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
11
|
+
import { cmsLogger } from '@aphexcms/cms-core';
|
|
12
|
+
import { emailConfig } from '../../email';
|
|
13
|
+
import { cacheAdapter } from '../../cache';
|
|
14
|
+
|
|
15
|
+
// Support both AUTH_* (preferred) and BETTER_AUTH_* (backwards-compatible)
|
|
16
|
+
const authSecret = env.AUTH_SECRET || env.BETTER_AUTH_SECRET;
|
|
17
|
+
const authUrl = env.AUTH_URL || env.BETTER_AUTH_URL;
|
|
18
|
+
|
|
19
|
+
// This function creates the Better Auth instance, injecting the necessary dependencies.
|
|
20
|
+
export function createAuthInstance(
|
|
21
|
+
db: DatabaseAdapter,
|
|
22
|
+
drizzleDb: PostgresJsDatabase<any>,
|
|
23
|
+
emailAdapter?: EmailAdapter | null
|
|
24
|
+
) {
|
|
25
|
+
const userSyncHooks = createAuthMiddleware(async (ctx) => {
|
|
26
|
+
// Sync: Create CMS user profile when user signs up
|
|
27
|
+
// Note: Invitation processing is handled in hooks.server.ts
|
|
28
|
+
if (ctx.path === '/sign-up/email' && ctx.context.user) {
|
|
29
|
+
try {
|
|
30
|
+
await db.createUserProfile({
|
|
31
|
+
userId: ctx.context.user.id,
|
|
32
|
+
role: 'editor' // Default role
|
|
33
|
+
});
|
|
34
|
+
cmsLogger.info('[Auth]', 'Created user profile');
|
|
35
|
+
} catch (error) {
|
|
36
|
+
cmsLogger.error('[Auth]', 'Error creating user profile:', error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Sync: Clean up CMS data when user is deleted
|
|
41
|
+
if (ctx.path === '/user/delete-user' && ctx.context.user) {
|
|
42
|
+
try {
|
|
43
|
+
await db.deleteUserProfile(ctx.context.user.id);
|
|
44
|
+
cmsLogger.info('[Auth]', 'Deleted user profile');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
cmsLogger.error('[Auth]', 'Error deleting user profile:', error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return betterAuth({
|
|
52
|
+
baseURL: authUrl,
|
|
53
|
+
secret: authSecret,
|
|
54
|
+
advanced: {
|
|
55
|
+
backgroundTasks: {
|
|
56
|
+
handler: (task) => {
|
|
57
|
+
Promise.resolve(typeof task === 'function' ? task() : task).catch(() => {});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
// Better Auth's internal adapter needs the raw Drizzle client.
|
|
62
|
+
database: drizzleAdapter(drizzleDb, {
|
|
63
|
+
provider: 'pg'
|
|
64
|
+
}),
|
|
65
|
+
emailAndPassword: {
|
|
66
|
+
enabled: true,
|
|
67
|
+
requireEmailVerification: true,
|
|
68
|
+
sendResetPassword: async ({ user, url, token }) => {
|
|
69
|
+
// Manually construct the correct URL format
|
|
70
|
+
// Better Auth URL: http://localhost:5173/reset-password?token=xxx&callbackURL=...
|
|
71
|
+
// We want: http://localhost:5173/reset-password/xxx
|
|
72
|
+
const baseUrl = authUrl || 'http://localhost:5173';
|
|
73
|
+
const resetUrl = `${baseUrl}/reset-password/${token}`;
|
|
74
|
+
|
|
75
|
+
// Send password reset email if adapter is configured
|
|
76
|
+
if (emailAdapter && emailConfig) {
|
|
77
|
+
try {
|
|
78
|
+
const { html, text } = await emailConfig.passwordReset.render(
|
|
79
|
+
user.name || user.email,
|
|
80
|
+
resetUrl
|
|
81
|
+
);
|
|
82
|
+
const result = await emailAdapter.send({
|
|
83
|
+
from: emailConfig.from,
|
|
84
|
+
to: user.email,
|
|
85
|
+
subject: emailConfig.passwordReset.subject,
|
|
86
|
+
html,
|
|
87
|
+
text
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (result.error) {
|
|
91
|
+
cmsLogger.error('[Auth]', 'Failed to send password reset email:', result.error);
|
|
92
|
+
} else {
|
|
93
|
+
cmsLogger.info('[Auth]', 'Password reset email sent');
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
cmsLogger.error('[Auth]', 'Error sending password reset email:', error);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
cmsLogger.warn('[Auth]', 'Email adapter not configured. Password reset email not sent.');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
emailVerification: {
|
|
104
|
+
enabled: true,
|
|
105
|
+
sendOnSignUp: true,
|
|
106
|
+
autoSignInAfterVerification: true,
|
|
107
|
+
verifyEmailPath: '/verify-email',
|
|
108
|
+
sendVerificationEmail: async ({ user, url, token }) => {
|
|
109
|
+
// Send verification email if adapter is configured
|
|
110
|
+
if (emailAdapter && emailConfig) {
|
|
111
|
+
try {
|
|
112
|
+
const { html, text } = await emailConfig.emailVerification.render(
|
|
113
|
+
user.name || user.email,
|
|
114
|
+
url
|
|
115
|
+
);
|
|
116
|
+
const result = await emailAdapter.send({
|
|
117
|
+
from: emailConfig.from,
|
|
118
|
+
to: user.email,
|
|
119
|
+
subject: emailConfig.emailVerification.subject,
|
|
120
|
+
html,
|
|
121
|
+
text
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (result.error) {
|
|
125
|
+
cmsLogger.error('[Auth]', 'Failed to send verification email:', result.error);
|
|
126
|
+
} else {
|
|
127
|
+
cmsLogger.info('[Auth]', 'Verification email sent');
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
cmsLogger.error('[Auth]', 'Error sending verification email:', error);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
cmsLogger.warn('[Auth]', 'Email adapter not configured. Verification email not sent.');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
plugins: [
|
|
138
|
+
apiKey({
|
|
139
|
+
apiKeyHeaders: ['x-api-key'],
|
|
140
|
+
deferUpdates: true,
|
|
141
|
+
rateLimit: {
|
|
142
|
+
enabled: true,
|
|
143
|
+
timeWindow: 1000 * 60 * 60 * 24,
|
|
144
|
+
maxRequests: 10000
|
|
145
|
+
},
|
|
146
|
+
enableMetadata: true,
|
|
147
|
+
...(cacheAdapter
|
|
148
|
+
? {
|
|
149
|
+
storage: 'secondary-storage' as const,
|
|
150
|
+
fallbackToDatabase: true,
|
|
151
|
+
customStorage: {
|
|
152
|
+
get: async (key: string) => cacheAdapter.get(key),
|
|
153
|
+
set: async (key: string, value: string, ttl?: number) =>
|
|
154
|
+
cacheAdapter.set(key, value, ttl),
|
|
155
|
+
delete: async (key: string) => cacheAdapter.delete(key)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
: {})
|
|
159
|
+
})
|
|
160
|
+
],
|
|
161
|
+
hooks: {
|
|
162
|
+
after: userSyncHooks
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// apps/studio/src/lib/server/auth/index.ts
|
|
2
|
+
|
|
3
|
+
import type { AuthProvider } from '@aphexcms/cms-core/server';
|
|
4
|
+
import { authService } from './service';
|
|
5
|
+
|
|
6
|
+
// This file is the clean, public-facing API for the app's auth module.
|
|
7
|
+
|
|
8
|
+
// 1. Export the auth instance (from separate file to avoid circular dependency)
|
|
9
|
+
export { auth } from './instance.js';
|
|
10
|
+
|
|
11
|
+
// 2. Export the authService (which uses the 'auth' instance).
|
|
12
|
+
export { authService } from './service';
|
|
13
|
+
|
|
14
|
+
// 3. Export the authProvider, which connects the authService to the CMS core hook.
|
|
15
|
+
export const authProvider: AuthProvider = {
|
|
16
|
+
getSession: (request, db) => authService.getSession(request, db),
|
|
17
|
+
requireSession: (request, db) => authService.requireSession(request, db),
|
|
18
|
+
validateApiKey: (request) => authService.validateApiKey(request),
|
|
19
|
+
requireApiKey: (request, db, permission) => authService.requireApiKey(request, db, permission),
|
|
20
|
+
getUserById: (userId) => authService.getUserById(userId),
|
|
21
|
+
getUserByEmail: (email) => authService.getUserByEmail(email),
|
|
22
|
+
changeUserName: (userId, name) => authService.changeUserName(userId, name),
|
|
23
|
+
requestPasswordReset: (email, redirectTo) => authService.requestPasswordReset(email, redirectTo),
|
|
24
|
+
resetPassword: (token, newPassword) => authService.resetPassword(token, newPassword)
|
|
25
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Separate file for auth instance to avoid circular dependency
|
|
2
|
+
import { db, drizzleDb } from '$lib/server/db';
|
|
3
|
+
import { email } from '$lib/server/email';
|
|
4
|
+
import { createAuthInstance } from './better-auth/instance.js';
|
|
5
|
+
|
|
6
|
+
// Create the Better Auth instance by injecting the database and email adapter.
|
|
7
|
+
export const auth = createAuthInstance(db, drizzleDb, email);
|