@c-time/frelio-cms 1.3.5 → 1.3.7

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.
Files changed (35) hide show
  1. package/dist/assets/{index-Qi4Cp3k2.js → index-D36XnvJP.js} +1 -1
  2. package/dist/assets/{index-Dr4qYonI.js → index-DCWmZub0.js} +4 -4
  3. package/dist/index.html +1 -1
  4. package/functions/api/auth/callback.ts +138 -0
  5. package/functions/api/storage/_middleware.ts +76 -0
  6. package/functions/api/storage/files/[uuid].ts +35 -0
  7. package/functions/api/storage/list.ts +19 -0
  8. package/functions/api/storage/rebuild/[year].ts +27 -0
  9. package/functions/api/storage/upload-set.ts +18 -0
  10. package/functions/api/storage/upload.ts +18 -0
  11. package/functions/api/storage/years.ts +18 -0
  12. package/package.json +6 -4
  13. package/workers/file-upload/package-lock.json +1606 -0
  14. package/workers/file-upload/package.json +14 -0
  15. package/workers/file-upload/src/controller/DIContainer.ts +103 -0
  16. package/workers/file-upload/src/controller/FileController.ts +248 -0
  17. package/workers/file-upload/src/domain/AuthEntity.ts +17 -0
  18. package/workers/file-upload/src/domain/FileEntity.ts +113 -0
  19. package/workers/file-upload/src/index.ts +134 -0
  20. package/workers/file-upload/src/infra/LoggerSingleton.ts +162 -0
  21. package/workers/file-upload/src/presenter/HttpPresenterImpl.ts +86 -0
  22. package/workers/file-upload/src/repository/AuthRepositoryPort.ts +11 -0
  23. package/workers/file-upload/src/repository/GitHubAuthRepositoryImpl.ts +58 -0
  24. package/workers/file-upload/src/repository/R2RepositoryImpl.ts +145 -0
  25. package/workers/file-upload/src/repository/R2RepositoryPort.ts +86 -0
  26. package/workers/file-upload/src/usecase/DeleteFileUseCase.ts +97 -0
  27. package/workers/file-upload/src/usecase/GetFileUseCase.ts +50 -0
  28. package/workers/file-upload/src/usecase/ListFilesUseCase.ts +52 -0
  29. package/workers/file-upload/src/usecase/RebuildIndexUseCase.ts +182 -0
  30. package/workers/file-upload/src/usecase/UpdateFileMetadataUseCase.ts +43 -0
  31. package/workers/file-upload/src/usecase/UploadFileUseCase.ts +62 -0
  32. package/workers/file-upload/src/usecase/UploadImageSetUseCase.ts +95 -0
  33. package/workers/file-upload/src/usecase/ValidateAuthUseCase.ts +26 -0
  34. package/workers/file-upload/tsconfig.json +13 -0
  35. package/workers/file-upload/wrangler.toml.example +11 -0
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Frelio CMS</title>
7
- <script type="module" crossorigin src="/admin/assets/index-Dr4qYonI.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DCWmZub0.js"></script>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -0,0 +1,138 @@
1
+ interface Env {
2
+ GITHUB_CLIENT_ID?: string
3
+ GITHUB_CLIENT_SECRET: string
4
+ VITE_GITHUB_CLIENT_ID?: string
5
+ }
6
+
7
+ function errorHtml(title: string, message: string): string {
8
+ return `<!DOCTYPE html>
9
+ <html lang="ja">
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>${title} - Frelio</title>
14
+ <style>
15
+ * { margin: 0; padding: 0; box-sizing: border-box; }
16
+ body {
17
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
18
+ background: #f5f5f5;
19
+ display: flex;
20
+ justify-content: center;
21
+ align-items: center;
22
+ min-height: 100vh;
23
+ color: #333;
24
+ }
25
+ .card {
26
+ background: #fff;
27
+ border-radius: 12px;
28
+ box-shadow: 0 2px 12px rgba(0,0,0,0.1);
29
+ padding: 40px;
30
+ max-width: 480px;
31
+ width: 90%;
32
+ text-align: center;
33
+ }
34
+ .icon {
35
+ font-size: 48px;
36
+ margin-bottom: 16px;
37
+ }
38
+ h1 {
39
+ font-size: 20px;
40
+ font-weight: 600;
41
+ margin-bottom: 12px;
42
+ }
43
+ p {
44
+ font-size: 14px;
45
+ color: #666;
46
+ line-height: 1.6;
47
+ margin-bottom: 24px;
48
+ }
49
+ a {
50
+ display: inline-block;
51
+ background: #1976d2;
52
+ color: #fff;
53
+ text-decoration: none;
54
+ padding: 10px 24px;
55
+ border-radius: 6px;
56
+ font-size: 14px;
57
+ font-weight: 500;
58
+ transition: background 0.2s;
59
+ }
60
+ a:hover { background: #1565c0; }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <div class="card">
65
+ <div class="icon">⚠️</div>
66
+ <h1>${title}</h1>
67
+ <p>${message}</p>
68
+ <a href="/">ダッシュボードに戻る</a>
69
+ </div>
70
+ </body>
71
+ </html>`
72
+ }
73
+
74
+ export const onRequestGet: PagesFunction<Env> = async (context) => {
75
+ const url = new URL(context.request.url)
76
+ const code = url.searchParams.get('code')
77
+
78
+ if (!code) {
79
+ return new Response(
80
+ errorHtml('認証エラー', '認証コードが見つかりませんでした。ダッシュボードに戻って再度ログインしてください。'),
81
+ { status: 400, headers: { 'Content-Type': 'text/html; charset=utf-8' } },
82
+ )
83
+ }
84
+
85
+ const clientId =
86
+ context.env.GITHUB_CLIENT_ID || context.env.VITE_GITHUB_CLIENT_ID
87
+
88
+ if (!clientId) {
89
+ return new Response(
90
+ errorHtml('サーバー設定エラー', 'サーバーの設定に問題があります。管理者にお問い合わせください。'),
91
+ { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } },
92
+ )
93
+ }
94
+
95
+ try {
96
+ const tokenResponse = await fetch(
97
+ 'https://github.com/login/oauth/access_token',
98
+ {
99
+ method: 'POST',
100
+ headers: {
101
+ Accept: 'application/json',
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ body: JSON.stringify({
105
+ client_id: clientId,
106
+ client_secret: context.env.GITHUB_CLIENT_SECRET,
107
+ code,
108
+ }),
109
+ },
110
+ )
111
+
112
+ const tokenData = (await tokenResponse.json()) as {
113
+ access_token?: string
114
+ error?: string
115
+ error_description?: string
116
+ }
117
+
118
+ if (tokenData.error) {
119
+ return new Response(
120
+ errorHtml(
121
+ '認証に失敗しました',
122
+ '認証コードが無効または期限切れです。ダッシュボードに戻ってブラウザをリロードし、再度ログインしてください。',
123
+ ),
124
+ { status: 400, headers: { 'Content-Type': 'text/html; charset=utf-8' } },
125
+ )
126
+ }
127
+
128
+ // Redirect to frontend callback with token
129
+ const origin = url.origin
130
+ const redirectUrl = `${origin}/auth/callback?token=${tokenData.access_token}`
131
+ return Response.redirect(redirectUrl, 302)
132
+ } catch {
133
+ return new Response(
134
+ errorHtml('認証エラー', '認証処理中にエラーが発生しました。ダッシュボードに戻って再度ログインしてください。'),
135
+ { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } },
136
+ )
137
+ }
138
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Storage API Middleware - 認証 + CORS + DI
3
+ *
4
+ * /api/storage/* の全リクエストに対して実行される。
5
+ * FileController を組み立てて context.data に渡す。
6
+ */
7
+
8
+ import { FileController } from '../../../../../workers/file-upload/src/controller/FileController'
9
+ import { AuthError } from '../../../../../workers/file-upload/src/domain/AuthEntity'
10
+ import { logger } from '../../../../../workers/file-upload/src/infra/LoggerSingleton'
11
+ import { HttpPresenterImpl } from '../../../../../workers/file-upload/src/presenter/HttpPresenterImpl'
12
+ import { GitHubAuthRepositoryImpl } from '../../../../../workers/file-upload/src/repository/GitHubAuthRepositoryImpl'
13
+ import { R2RepositoryImpl } from '../../../../../workers/file-upload/src/repository/R2RepositoryImpl'
14
+ import { ValidateAuthUseCase } from '../../../../../workers/file-upload/src/usecase/ValidateAuthUseCase'
15
+
16
+ interface Env {
17
+ R2: R2Bucket
18
+ R2_PUBLIC_URL: string
19
+ ALLOWED_ORIGINS?: string
20
+ }
21
+
22
+ type DataWithController = {
23
+ controller: FileController
24
+ }
25
+
26
+ export const onRequest: PagesFunction<Env, string, DataWithController> = async (context) => {
27
+ const requestId = crypto.randomUUID().slice(0, 8)
28
+ logger.setRequestId(requestId)
29
+
30
+ try {
31
+ // CORS ヘッダー構築
32
+ const origin = context.request.headers.get('Origin') ?? ''
33
+ const allowedOrigins = context.env.ALLOWED_ORIGINS?.split(',').map((s) => s.trim()) ?? []
34
+ // Pages Functions では同一オリジンも許可
35
+ const isSameOrigin = origin === new URL(context.request.url).origin
36
+ const matchedOrigin = isSameOrigin || allowedOrigins.includes(origin) ? origin : null
37
+ const corsHeaders = {
38
+ 'Access-Control-Allow-Origin': matchedOrigin ?? '',
39
+ 'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',
40
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
41
+ Vary: 'Origin',
42
+ }
43
+
44
+ // CORS プリフライト
45
+ if (context.request.method === 'OPTIONS') {
46
+ return new Response(null, { headers: corsHeaders })
47
+ }
48
+
49
+ // 認証
50
+ const authRepository = new GitHubAuthRepositoryImpl()
51
+ const validateAuth = new ValidateAuthUseCase(authRepository)
52
+ const presenter = new HttpPresenterImpl(corsHeaders)
53
+ try {
54
+ await validateAuth.execute(context.request.headers.get('Authorization'))
55
+ } catch (error) {
56
+ if (error instanceof AuthError) {
57
+ return presenter.unauthorized(error.message)
58
+ }
59
+ throw error
60
+ }
61
+
62
+ // DI: FileController を組み立てて context.data に渡す
63
+ const r2Repository = new R2RepositoryImpl(context.env.R2, context.env.R2_PUBLIC_URL)
64
+ context.data.controller = new FileController(r2Repository, corsHeaders)
65
+
66
+ return await context.next()
67
+ } catch (error) {
68
+ logger.error('StorageMiddleware', 'Unhandled error', { error })
69
+ return new Response(JSON.stringify({ error: (error as Error).message }), {
70
+ status: 500,
71
+ headers: { 'Content-Type': 'application/json' },
72
+ })
73
+ } finally {
74
+ logger.clearRequestId()
75
+ }
76
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * /api/storage/files/:uuid
3
+ * GET - ファイル情報
4
+ * PATCH - メタデータ更新
5
+ * DELETE - ファイル削除
6
+ */
7
+
8
+ import type { FileController } from '../../../../../../workers/file-upload/src/controller/FileController'
9
+
10
+ interface Env {
11
+ R2: R2Bucket
12
+ R2_PUBLIC_URL: string
13
+ }
14
+
15
+ type Data = {
16
+ controller: FileController
17
+ }
18
+
19
+ type Params = 'uuid'
20
+
21
+ export const onRequestGet: PagesFunction<Env, Params, Data> = async (context) => {
22
+ const uuid = context.params.uuid as string
23
+ return context.data.controller.handleGetFile(uuid)
24
+ }
25
+
26
+ export const onRequestPatch: PagesFunction<Env, Params, Data> = async (context) => {
27
+ const uuid = context.params.uuid as string
28
+ return context.data.controller.handleUpdateFileMetadata(uuid, context.request)
29
+ }
30
+
31
+ export const onRequestDelete: PagesFunction<Env, Params, Data> = async (context) => {
32
+ const uuid = context.params.uuid as string
33
+ const url = new URL(context.request.url)
34
+ return context.data.controller.handleDeleteFile(uuid, url)
35
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * GET /api/storage/list - ファイル一覧
3
+ */
4
+
5
+ import type { FileController } from '../../../../../workers/file-upload/src/controller/FileController'
6
+
7
+ interface Env {
8
+ R2: R2Bucket
9
+ R2_PUBLIC_URL: string
10
+ }
11
+
12
+ type Data = {
13
+ controller: FileController
14
+ }
15
+
16
+ export const onRequestGet: PagesFunction<Env, string, Data> = async (context) => {
17
+ const url = new URL(context.request.url)
18
+ return context.data.controller.handleListFiles(url)
19
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * POST /api/storage/rebuild/:year - インデックス再構築
3
+ */
4
+
5
+ import type { FileController } from '../../../../../../workers/file-upload/src/controller/FileController'
6
+
7
+ interface Env {
8
+ R2: R2Bucket
9
+ R2_PUBLIC_URL: string
10
+ }
11
+
12
+ type Data = {
13
+ controller: FileController
14
+ }
15
+
16
+ type Params = 'year'
17
+
18
+ export const onRequestPost: PagesFunction<Env, Params, Data> = async (context) => {
19
+ const year = parseInt(context.params.year as string)
20
+ if (isNaN(year)) {
21
+ return new Response(JSON.stringify({ error: 'Invalid year' }), {
22
+ status: 400,
23
+ headers: { 'Content-Type': 'application/json' },
24
+ })
25
+ }
26
+ return context.data.controller.handleRebuildIndex(year)
27
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * POST /api/storage/upload-set - 画像セットのアップロード
3
+ */
4
+
5
+ import type { FileController } from '../../../../../workers/file-upload/src/controller/FileController'
6
+
7
+ interface Env {
8
+ R2: R2Bucket
9
+ R2_PUBLIC_URL: string
10
+ }
11
+
12
+ type Data = {
13
+ controller: FileController
14
+ }
15
+
16
+ export const onRequestPost: PagesFunction<Env, string, Data> = async (context) => {
17
+ return context.data.controller.handleUploadSet(context.request, context)
18
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * POST /api/storage/upload - 単一ファイルのアップロード
3
+ */
4
+
5
+ import type { FileController } from '../../../../../workers/file-upload/src/controller/FileController'
6
+
7
+ interface Env {
8
+ R2: R2Bucket
9
+ R2_PUBLIC_URL: string
10
+ }
11
+
12
+ type Data = {
13
+ controller: FileController
14
+ }
15
+
16
+ export const onRequestPost: PagesFunction<Env, string, Data> = async (context) => {
17
+ return context.data.controller.handleUpload(context.request)
18
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GET /api/storage/years - 利用可能な年一覧
3
+ */
4
+
5
+ import type { FileController } from '../../../../../workers/file-upload/src/controller/FileController'
6
+
7
+ interface Env {
8
+ R2: R2Bucket
9
+ R2_PUBLIC_URL: string
10
+ }
11
+
12
+ type Data = {
13
+ controller: FileController
14
+ }
15
+
16
+ export const onRequestGet: PagesFunction<Env, string, Data> = async (context) => {
17
+ return context.data.controller.handleGetYears()
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-time/frelio-cms",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "description": "Frelio CMS Admin - pre-built static bundle for 1-repo setup",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -12,15 +12,17 @@
12
12
  "access": "public"
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "functions",
17
+ "workers"
16
18
  ],
17
19
  "type": "module",
18
20
  "scripts": {
19
21
  "dev": "vite",
20
22
  "dev:full": "wrangler pages dev --compatibility-date=2024-01-01 -- vite",
21
23
  "build": "vite build",
22
- "build:publish": "cross-env FRELIO_ADMIN_BASE=/admin/ vite build",
23
- "prepublishOnly": "npm run build:publish",
24
+ "prepublishOnly": "vite build && cp -r ../../workers ./workers",
25
+ "postpublish": "rm -rf ./workers",
24
26
  "preview": "vite preview",
25
27
  "typecheck": "tsc --noEmit",
26
28
  "test": "vitest",