@zhang_libo/resource-hub 1.0.2

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +80 -0
  3. package/README.ja.md +80 -0
  4. package/README.md +79 -0
  5. package/README.zh-TW.md +80 -0
  6. package/bin/cli.js +10 -0
  7. package/dist/app.d.ts +2 -0
  8. package/dist/app.d.ts.map +1 -0
  9. package/dist/app.js +59 -0
  10. package/dist/app.js.map +1 -0
  11. package/dist/db/index.js +12 -0
  12. package/dist/db/index.js.map +1 -0
  13. package/dist/db/migrate.d.ts +3 -0
  14. package/dist/db/migrate.d.ts.map +1 -0
  15. package/dist/db/migrate.js +169 -0
  16. package/dist/db/migrate.js.map +1 -0
  17. package/dist/db/schema.d.ts +743 -0
  18. package/dist/db/schema.d.ts.map +1 -0
  19. package/dist/db/schema.js +88 -0
  20. package/dist/db/schema.js.map +1 -0
  21. package/dist/i18n.js +309 -0
  22. package/dist/i18n.js.map +1 -0
  23. package/dist/plugins/admin.d.ts +4 -0
  24. package/dist/plugins/admin.d.ts.map +1 -0
  25. package/dist/plugins/admin.js +19 -0
  26. package/dist/plugins/admin.js.map +1 -0
  27. package/dist/plugins/auth.d.ts +4 -0
  28. package/dist/plugins/auth.d.ts.map +1 -0
  29. package/dist/plugins/auth.js +35 -0
  30. package/dist/plugins/auth.js.map +1 -0
  31. package/dist/routes/auth.d.ts +4 -0
  32. package/dist/routes/auth.d.ts.map +1 -0
  33. package/dist/routes/auth.js +352 -0
  34. package/dist/routes/auth.js.map +1 -0
  35. package/dist/routes/categories.d.ts +4 -0
  36. package/dist/routes/categories.d.ts.map +1 -0
  37. package/dist/routes/categories.js +112 -0
  38. package/dist/routes/categories.js.map +1 -0
  39. package/dist/routes/config.d.ts +4 -0
  40. package/dist/routes/config.d.ts.map +1 -0
  41. package/dist/routes/config.js +227 -0
  42. package/dist/routes/config.js.map +1 -0
  43. package/dist/routes/resources.d.ts +4 -0
  44. package/dist/routes/resources.d.ts.map +1 -0
  45. package/dist/routes/resources.js +474 -0
  46. package/dist/routes/resources.js.map +1 -0
  47. package/dist/routes/tags.d.ts +4 -0
  48. package/dist/routes/tags.d.ts.map +1 -0
  49. package/dist/routes/tags.js +37 -0
  50. package/dist/routes/tags.js.map +1 -0
  51. package/dist/routes/users.d.ts +4 -0
  52. package/dist/routes/users.d.ts.map +1 -0
  53. package/dist/routes/users.js +181 -0
  54. package/dist/routes/users.js.map +1 -0
  55. package/dist/services/crypto.js +49 -0
  56. package/dist/services/crypto.js.map +1 -0
  57. package/dist/services/mail.d.ts +16 -0
  58. package/dist/services/mail.d.ts.map +1 -0
  59. package/dist/services/mail.js +33 -0
  60. package/dist/services/mail.js.map +1 -0
  61. package/dist/services/rsa.js +49 -0
  62. package/dist/services/rsa.js.map +1 -0
  63. package/dist/services/token.d.ts +9 -0
  64. package/dist/services/token.d.ts.map +1 -0
  65. package/dist/services/token.js +29 -0
  66. package/dist/services/token.js.map +1 -0
  67. package/dist/types.d.ts +80 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +73 -0
  72. package/public/admin/AdminCategories.jsx +310 -0
  73. package/public/admin/AdminConfig.jsx +254 -0
  74. package/public/admin/AdminEmail.jsx +279 -0
  75. package/public/admin/AdminTags.jsx +263 -0
  76. package/public/admin/AdminUsers.jsx +452 -0
  77. package/public/app.jsx +186 -0
  78. package/public/components/ConfirmDialog.jsx +78 -0
  79. package/public/components/DropdownSelect.jsx +281 -0
  80. package/public/components/EmailPreviewModal.jsx +104 -0
  81. package/public/components/EmptyState.jsx +50 -0
  82. package/public/components/Modal.jsx +127 -0
  83. package/public/components/PasswordStrength.jsx +45 -0
  84. package/public/components/Skeleton.jsx +68 -0
  85. package/public/components/Toast.jsx +80 -0
  86. package/public/components/TooltipIconButton.jsx +55 -0
  87. package/public/context/AppContext.jsx +314 -0
  88. package/public/features/BatchResourceModal.jsx +606 -0
  89. package/public/features/ChangePasswordModal.jsx +187 -0
  90. package/public/features/ProfileModal.jsx +170 -0
  91. package/public/features/ResourceCard.jsx +422 -0
  92. package/public/features/ResourceFormModal.jsx +915 -0
  93. package/public/features/ResourceRow.jsx +287 -0
  94. package/public/features/ResourceTimeline.jsx +472 -0
  95. package/public/hooks/useApi.jsx +26 -0
  96. package/public/hooks/useRouter.jsx +35 -0
  97. package/public/index.html +258 -0
  98. package/public/layout/AdminLayout.jsx +167 -0
  99. package/public/layout/AppLayout.jsx +119 -0
  100. package/public/layout/AuthLayout.jsx +503 -0
  101. package/public/layout/Header.jsx +543 -0
  102. package/public/layout/Sidebar.jsx +175 -0
  103. package/public/pages/AdminPage.jsx +30 -0
  104. package/public/pages/ForgotPasswordPage.jsx +93 -0
  105. package/public/pages/HomePage.jsx +2297 -0
  106. package/public/pages/LoginPage.jsx +191 -0
  107. package/public/pages/RegisterPage.jsx +137 -0
  108. package/public/pages/ResetPasswordPage.jsx +169 -0
  109. package/public/pages/SetupPage.jsx +157 -0
  110. package/public/utils/helpers.jsx +152 -0
  111. package/public/utils/i18n.jsx +1374 -0
  112. package/public/utils/preferences.jsx +220 -0
  113. package/public/utils/security.jsx +88 -0
  114. package/public/utils/theme.jsx +24 -0
  115. package/public/vendor/babel.min.js +2 -0
  116. package/public/vendor/lucide-react.min.js +9 -0
  117. package/public/vendor/react-dom.development.js +29869 -0
  118. package/public/vendor/react.development.js +3342 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BadIdea
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,80 @@
1
+ ## ResourceHub Resource Navigation System
2
+
3
+ [简体中文](README.md) | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md)
4
+
5
+ ResourceHub is a general-purpose resource management product used to centrally organize, access, and maintain various sites, tools, knowledge bases, and external links. The system supports user login, favorites, visit history, and provides an admin console to manage categories, tags, users, and system configuration.
6
+
7
+ ### Features
8
+
9
+ - **Resource Management**: Centrally organize and maintain frequently used links, and browse them by category and tag.
10
+ - **Search & Filtering**: Search by name, description, URL, and tags, with linked filters for categories, tags, and quick-access shortcuts.
11
+ - **Multiple Views**: Card view, list view, and timeline view to match different browsing preferences.
12
+ - **Favorites & History**: Supports "My Favorites", "Recently Visited", and "Created by Me" views. Visit history automatically keeps only the latest 200 entries.
13
+ - **Accounts & Permissions**: Supports initial admin setup, registration (configurable), login, forgot password, reset password, and change password. Differentiates between regular users and admins.
14
+ - **Admin Console**: Admins can manage categories, tags, users, system settings, and email configuration.
15
+ - **Mock Email Mode**: When SMTP is not configured, emails are not actually sent. Instead, the API response includes an email preview for the frontend to display.
16
+
17
+ ### Tech Stack Overview
18
+
19
+ - **Frontend**: React 18 (CDN UMD) + Babel Standalone + Tailwind CSS (CDN), hash router, no bundler.
20
+ - **Backend**: Node.js 18+, TypeScript, Fastify v4, `@fastify/jwt`, `@fastify/cors`, `@fastify/static`.
21
+ - **Database**: SQLite (via `better-sqlite3`) + Drizzle ORM, single-file database.
22
+ - **Email**: Nodemailer with support for real SMTP or mock email preview mode.
23
+
24
+ For detailed technical documentation, see `[docs/tech-stack.md](docs/tech-stack.md)`.
25
+
26
+ ### Install & Run via npm
27
+
28
+ ```bash
29
+ npm i resource-hub
30
+ npx resource-hub
31
+ ```
32
+
33
+ Default URL: `http://localhost:3000`. On first run, the app will redirect to `#/setup` to initialize the admin user. Runtime parameters can be set via environment variables (e.g. `PORT`, `DB_PATH`, `JWT_SECRET`, `NODE_ENV`); see "Install & Run from Source" below for details.
34
+
35
+ ### Install & Run from Source
36
+
37
+ - **Local Development**
38
+
39
+ ```bash
40
+ npm install
41
+ npm run dev
42
+ ```
43
+
44
+ Default URL: `http://localhost:3000`. On first run, the app will redirect to `#/setup` to initialize the admin user.
45
+
46
+ - **Production Mode**
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build
51
+ npm start
52
+ ```
53
+
54
+ Runtime parameters are configured via environment variables only (running `npm start --PORT=4000` is not supported). On PowerShell, for example:
55
+
56
+ ```bash
57
+ $env:PORT = 4000
58
+ $env:DB_PATH = "D:\data\resourcehub.db"
59
+ $env:JWT_SECRET = "please-change-me-in-production"
60
+ $env:NODE_ENV = "production"
61
+ npm start
62
+ ```
63
+
64
+ Key environment variables:
65
+
66
+ - `PORT` (default `3000`)
67
+ - `DB_PATH` (default `data/resource-hub.db`)
68
+ - `JWT_SECRET` (must be a secure random value)
69
+ - `NODE_ENV` (`development` / `production`)
70
+
71
+ ### Documentation Navigation
72
+
73
+ - **Requirements**: `[docs/requirements.md](docs/requirements.md)`
74
+ - **Tech Stack**: `[docs/tech-stack.md](docs/tech-stack.md)`
75
+ - **Database**: `[docs/database.md](docs/database.md)`
76
+ - **Backend Design**: `[docs/backend-design.md](docs/backend-design.md)`
77
+ - **Frontend Design**: `[docs/frontend-design.md](docs/frontend-design.md)`
78
+
79
+ We recommend reading the requirements and tech stack documents first before developing or refactoring features, and then consulting the backend/frontend design and database docs as needed.
80
+
package/README.ja.md ADDED
@@ -0,0 +1,80 @@
1
+ ## ResourceHub リソースナビゲーションシステム
2
+
3
+ [简体中文](README.md) | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md)
4
+
5
+ ResourceHub は、さまざまなサイト・ツール・ナレッジベース・外部リンクを一元的に整理・閲覧・管理するための汎用リソース管理プロダクトです。ユーザーのログイン、リソースのお気に入り、閲覧履歴をサポートし、管理画面からカテゴリ・タグ・ユーザー・システム設定をメンテナンスできます。
6
+
7
+ ### 機能概要
8
+
9
+ - **リソース管理**:よく使うリンクを一元管理し、カテゴリやタグで閲覧可能。
10
+ - **検索とフィルタ**:名前・説明・URL・タグで検索でき、カテゴリ・タグ・クイックフィルタと連動。
11
+ - **複数ビュー**:カードビュー、リストビュー、タイムラインビューを提供し、異なる閲覧スタイルに対応。
12
+ - **お気に入りと履歴**:「お気に入り」「最近の閲覧」「自分が作成した」の 3 つのビューを提供し、閲覧履歴は最新 200 件まで自動ローテーションで保持。
13
+ - **アカウントと権限**:初回管理者の初期化、ユーザー登録(有効 / 無効を設定可能)、ログイン、パスワードリセット、パスワード変更をサポートし、一般ユーザーと管理者を区別。
14
+ - **管理コンソール**:管理者はカテゴリ・タグ・ユーザー・システム設定・メール設定を管理可能。
15
+ - **モックメールモード**:SMTP が未設定の場合はメールを実送信せず、レスポンスにメールプレビューを含め、フロントエンドで表示できます。
16
+
17
+ ### 技術スタック概要
18
+
19
+ - **フロントエンド**:React 18(CDN UMD)+ Babel Standalone + Tailwind CSS(CDN)、ハッシュルーター、ビルドツールなし。
20
+ - **バックエンド**:Node.js 18+、TypeScript、Fastify v4、`@fastify/jwt`、`@fastify/cors`、`@fastify/static`。
21
+ - **データベース**:SQLite(`better-sqlite3` ドライバ)+ Drizzle ORM、単一ファイルデータベース。
22
+ - **メール**:Nodemailer。実際の SMTP とモックメールプレビューモードの両方に対応。
23
+
24
+ 詳細な技術情報は `[docs/tech-stack.md](docs/tech-stack.md)` を参照してください。
25
+
26
+ ### npm でのインストールと起動
27
+
28
+ ```bash
29
+ npm i resource-hub
30
+ npx resource-hub
31
+ ```
32
+
33
+ デフォルトのアクセス URL は `http://localhost:3000` です。初回起動時は `#/setup` にリダイレクトされ、管理者アカウントの初期化が行われます。実行時パラメータは環境変数で指定できます(`PORT`、`DB_PATH`、`JWT_SECRET`、`NODE_ENV` など)。詳細は下記「インストールと起動(ソースコードから)」を参照してください。
34
+
35
+ ### インストールと起動(ソースコードから)
36
+
37
+ - **ローカル開発**
38
+
39
+ ```bash
40
+ npm install
41
+ npm run dev
42
+ ```
43
+
44
+ デフォルトのアクセス URL は `http://localhost:3000` です。初回起動時は `#/setup` にリダイレクトされ、管理者アカウントの初期化が行われます。
45
+
46
+ - **本番モード**
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build
51
+ npm start
52
+ ```
53
+
54
+ 実行時パラメータは環境変数でのみ指定します(`npm start --PORT=4000` のような指定はサポートされません)。PowerShell の例:
55
+
56
+ ```bash
57
+ $env:PORT = 4000
58
+ $env:DB_PATH = "D:\data\resourcehub.db"
59
+ $env:JWT_SECRET = "please-change-me-in-production"
60
+ $env:NODE_ENV = "production"
61
+ npm start
62
+ ```
63
+
64
+ 主要な環境変数:
65
+
66
+ - `PORT`(デフォルト `3000`)
67
+ - `DB_PATH`(デフォルト `data/resource-hub.db`)
68
+ - `JWT_SECRET`(十分に安全なランダム値である必要があります)
69
+ - `NODE_ENV`(`development` / `production`)
70
+
71
+ ### ドキュメントナビゲーション
72
+
73
+ - **要件ドキュメント**:`[docs/requirements.md](docs/requirements.md)`
74
+ - **技術スタック**:`[docs/tech-stack.md](docs/tech-stack.md)`
75
+ - **データベースドキュメント**:`[docs/database.md](docs/database.md)`
76
+ - **バックエンド設計**:`[docs/backend-design.md](docs/backend-design.md)`
77
+ - **フロントエンド設計**:`[docs/frontend-design.md](docs/frontend-design.md)`
78
+
79
+ 機能の開発やリファクタリングの前に、まず要件ドキュメントと技術スタックドキュメントを読み、その後必要に応じてバックエンド / フロントエンド設計およびデータベース関連ドキュメントを参照することを推奨します。
80
+
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ ## ResourceHub 资源导航系统
2
+
3
+ [简体中文](README.md) | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md)
4
+
5
+ ResourceHub 是一个通用的资源管理产品,用来集中整理、访问和维护各类站点、工具、知识库和外部链接。系统支持用户登录、资源收藏与访问历史记录,并提供后台管理界面维护类别、标签、用户和系统配置。
6
+
7
+ ### 功能简介
8
+
9
+ - **资源管理**:统一整理和维护常用链接,支持按类别和标签浏览。
10
+ - **搜索与筛选**:按名称、描述、URL、标签搜索,联动类别、标签和快速访问筛选。
11
+ - **多种视图**:卡片视图、列表视图、时间轴视图,满足不同浏览习惯。
12
+ - **收藏与访问历史**:支持“我的收藏”“最近访问”“我创建的”三类视图,访问历史自动滚动保留最近 200 条。
13
+ - **账号与权限**:支持初始化管理员、注册(可配置开关)、登录、忘记密码、重置密码和修改密码,区分普通用户与管理员。
14
+ - **后台管理**:管理员可维护类别、标签、用户、系统配置和邮件配置。
15
+ - **Mock 邮件模式**:在未配置 SMTP 时,不真实发送邮件,而是在响应中返回邮件预览供前端展示。
16
+
17
+ ### 技术栈概览
18
+
19
+ - **前端**:React 18(CDN UMD)+ Babel Standalone + Tailwind CSS(CDN),Hash Router,无打包工具。
20
+ - **后端**:Node.js 18+、TypeScript、Fastify v4、`@fastify/jwt`、`@fastify/cors`、`@fastify/static`。
21
+ - **数据库**:SQLite(`better-sqlite3` 驱动)+ Drizzle ORM,单文件数据库。
22
+ - **邮件**:Nodemailer,支持真实 SMTP 或 Mock 邮件预览模式。
23
+
24
+ 详细技术说明见 [docs/tech-stack.md](docs/tech-stack.md)。
25
+
26
+ ### 使用 npm 安装与运行
27
+
28
+ ```bash
29
+ npm i resource-hub
30
+ npx resource-hub
31
+ ```
32
+
33
+ 默认访问 `http://localhost:3000`,首次会跳转到 `#/setup` 初始化管理员。运行参数可通过环境变量配置(如 `PORT`、`DB_PATH`、`JWT_SECRET`、`NODE_ENV`),详见下方「安装与运行(源码方式)」中的说明。
34
+
35
+ ### 安装与运行(源码方式)
36
+
37
+ - **本地开发**
38
+
39
+ ```bash
40
+ npm install
41
+ npm run dev
42
+ ```
43
+
44
+ 默认访问 `http://localhost:3000`,首次会跳转到 `#/setup` 初始化管理员。
45
+
46
+ - **生产模式**
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build
51
+ npm start
52
+ ```
53
+
54
+ 仅支持通过环境变量配置运行参数(不支持 `npm start --PORT=4000`),例如在 PowerShell 中:
55
+
56
+ ```bash
57
+ $env:PORT = 4000
58
+ $env:DB_PATH = "D:\data\resourcehub.db"
59
+ $env:JWT_SECRET = "please-change-me-in-production"
60
+ $env:NODE_ENV = "production"
61
+ npm start
62
+ ```
63
+
64
+ 关键环境变量:
65
+
66
+ - `PORT`(默认 `3000`)
67
+ - `DB_PATH`(默认 `data/resource-hub.db`)
68
+ - `JWT_SECRET`(必须为安全随机值)
69
+ - `NODE_ENV`(`development` / `production`)
70
+
71
+ ### 文档导航
72
+
73
+ - **需求文档**:[docs/requirements.md](docs/requirements.md)
74
+ - **技术选型**:[docs/tech-stack.md](docs/tech-stack.md)
75
+ - **数据库文档**:[docs/database.md](docs/database.md)
76
+ - **后台设计**:[docs/backend-design.md](docs/backend-design.md)
77
+ - **前台设计**:[docs/frontend-design.md](docs/frontend-design.md)
78
+
79
+ 建议在开发或重构功能前,先阅读需求文档与技术选型文档,再根据需要查阅后台/前台设计与数据库文档。
@@ -0,0 +1,80 @@
1
+ ## ResourceHub 資源導航系統
2
+
3
+ [简体中文](README.md) | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md)
4
+
5
+ ResourceHub 是一個通用的資源管理產品,用來集中整理、存取與維護各類網站、工具、知識庫與外部連結。系統支援使用者登入、資源收藏與存取歷史,並提供後台管理介面維護分類、標籤、使用者與系統設定。
6
+
7
+ ### 功能簡介
8
+
9
+ - **資源管理**:統一整理與維護常用連結,支援依分類與標籤瀏覽。
10
+ - **搜尋與篩選**:可依名稱、描述、URL、標籤搜尋,並與分類、標籤及快速存取篩選聯動。
11
+ - **多種檢視模式**:卡片檢視、列表檢視、時間軸檢視,滿足不同瀏覽習慣。
12
+ - **收藏與存取歷史**:支援「我的收藏」「最近存取」「我建立的」三種檢視,存取歷史會自動滾動保留最近 200 筆。
13
+ - **帳號與權限**:支援初始化管理員、註冊(可設定開關)、登入、忘記密碼、重設密碼與修改密碼,區分一般使用者與管理員。
14
+ - **後台管理**:管理員可維護分類、標籤、使用者、系統設定與郵件設定。
15
+ - **Mock 郵件模式**:在未設定 SMTP 時,不實際寄出郵件,而是在回應中回傳郵件預覽供前端顯示。
16
+
17
+ ### 技術棧概覽
18
+
19
+ - **前端**:React 18(CDN UMD)+ Babel Standalone + Tailwind CSS(CDN),Hash Router,無打包工具。
20
+ - **後端**:Node.js 18+、TypeScript、Fastify v4、`@fastify/jwt`、`@fastify/cors`、`@fastify/static`。
21
+ - **資料庫**:SQLite(`better-sqlite3` 驅動)+ Drizzle ORM,單一檔案資料庫。
22
+ - **郵件**:Nodemailer,支援真實 SMTP 或 Mock 郵件預覽模式。
23
+
24
+ 詳細技術說明請參見 `[docs/tech-stack.md](docs/tech-stack.md)`。
25
+
26
+ ### 使用 npm 安裝與執行
27
+
28
+ ```bash
29
+ npm i resource-hub
30
+ npx resource-hub
31
+ ```
32
+
33
+ 預設訪問位址為 `http://localhost:3000`,首次啟動會導向 `#/setup` 以初始化管理員。執行參數可透過環境變數設定(如 `PORT`、`DB_PATH`、`JWT_SECRET`、`NODE_ENV`),詳見下方「安裝與執行(原始碼方式)」中的說明。
34
+
35
+ ### 安裝與執行(原始碼方式)
36
+
37
+ - **本機開發**
38
+
39
+ ```bash
40
+ npm install
41
+ npm run dev
42
+ ```
43
+
44
+ 預設訪問位址為 `http://localhost:3000`,首次啟動會導向 `#/setup` 以初始化管理員。
45
+
46
+ - **正式環境模式**
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build
51
+ npm start
52
+ ```
53
+
54
+ 僅支援透過環境變數設定執行參數(不支援 `npm start --PORT=4000`),例如在 PowerShell 中:
55
+
56
+ ```bash
57
+ $env:PORT = 4000
58
+ $env:DB_PATH = "D:\data\resourcehub.db"
59
+ $env:JWT_SECRET = "please-change-me-in-production"
60
+ $env:NODE_ENV = "production"
61
+ npm start
62
+ ```
63
+
64
+ 關鍵環境變數:
65
+
66
+ - `PORT`(預設 `3000`)
67
+ - `DB_PATH`(預設 `data/resource-hub.db`)
68
+ - `JWT_SECRET`(必須為安全且隨機的值)
69
+ - `NODE_ENV`(`development` / `production`)
70
+
71
+ ### 文件導覽
72
+
73
+ - **需求文件**:`[docs/requirements.md](docs/requirements.md)`
74
+ - **技術選型**:`[docs/tech-stack.md](docs/tech-stack.md)`
75
+ - **資料庫文件**:`[docs/database.md](docs/database.md)`
76
+ - **後端設計**:`[docs/backend-design.md](docs/backend-design.md)`
77
+ - **前端設計**:`[docs/frontend-design.md](docs/frontend-design.md)`
78
+
79
+ 建議在開發或重構功能前,先閱讀需求文件與技術選型文件,再視需要查閱後端 / 前端設計與資料庫相關文件。
80
+
package/bin/cli.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'url'
3
+ import { dirname, join } from 'path'
4
+ import { spawnSync } from 'child_process'
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url))
7
+ const root = join(__dirname, '..')
8
+ const appPath = join(root, 'dist', 'app.js')
9
+ const result = spawnSync(process.execPath, [appPath], { cwd: root, stdio: 'inherit' })
10
+ process.exit(result.status ?? 0)
package/dist/app.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":""}
package/dist/app.js ADDED
@@ -0,0 +1,59 @@
1
+ import { join } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import Fastify from 'fastify';
4
+ import cors from '@fastify/cors';
5
+ import jwt from '@fastify/jwt';
6
+ import staticPlugin from '@fastify/static';
7
+ import { runMigrations } from './db/migrate.js';
8
+ import { ensureRsaKeyPair } from './services/rsa.js';
9
+ import authPlugin from './plugins/auth.js';
10
+ import adminPlugin from './plugins/admin.js';
11
+ import authRoutes from './routes/auth.js';
12
+ import categoriesRoutes from './routes/categories.js';
13
+ import tagsRoutes from './routes/tags.js';
14
+ import resourcesRoutes from './routes/resources.js';
15
+ import usersRoutes from './routes/users.js';
16
+ import configRoutes from './routes/config.js';
17
+ const PORT = parseInt(process.env.PORT ?? '3000', 10);
18
+ const JWT_SECRET = process.env.JWT_SECRET ?? 'dev-secret-change-me';
19
+ const NODE_ENV = process.env.NODE_ENV ?? 'development';
20
+ export async function buildApp() {
21
+ runMigrations();
22
+ ensureRsaKeyPair();
23
+ const fastify = Fastify({
24
+ logger: NODE_ENV === 'development' ? { level: 'info' } : false,
25
+ });
26
+ await fastify.register(cors, {
27
+ origin: NODE_ENV === 'development' ? true : false,
28
+ credentials: true,
29
+ });
30
+ await fastify.register(jwt, { secret: JWT_SECRET });
31
+ await fastify.register(staticPlugin, {
32
+ root: join(process.cwd(), 'public'),
33
+ prefix: '/',
34
+ });
35
+ await fastify.register(authPlugin);
36
+ await fastify.register(adminPlugin);
37
+ await fastify.register(authRoutes, { prefix: '/api/auth' });
38
+ await fastify.register(categoriesRoutes, { prefix: '/api/categories' });
39
+ await fastify.register(tagsRoutes, { prefix: '/api/tags' });
40
+ await fastify.register(resourcesRoutes, { prefix: '/api/resources' });
41
+ await fastify.register(usersRoutes, { prefix: '/api/users' });
42
+ await fastify.register(configRoutes, { prefix: '/api/config' });
43
+ return fastify;
44
+ }
45
+ export async function startServer() {
46
+ const fastify = await buildApp();
47
+ await fastify.listen({ port: PORT, host: '0.0.0.0' });
48
+ console.log(`Server listening at http://0.0.0.0:${PORT}`);
49
+ return fastify;
50
+ }
51
+ const entryPath = process.argv[1];
52
+ const isMain = entryPath && fileURLToPath(import.meta.url) === entryPath;
53
+ if (isMain) {
54
+ startServer().catch((err) => {
55
+ console.error(err);
56
+ process.exit(1);
57
+ });
58
+ }
59
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import Database from 'better-sqlite3';
2
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
3
+ import { mkdirSync } from 'fs';
4
+ import { dirname } from 'path';
5
+ import * as schema from './schema.js';
6
+ const DB_PATH = process.env.DB_PATH ?? 'data/resource-hub.db';
7
+ mkdirSync(dirname(DB_PATH), { recursive: true });
8
+ export const sqlite = new Database(DB_PATH);
9
+ sqlite.pragma('foreign_keys = ON');
10
+ sqlite.pragma('journal_mode = WAL');
11
+ export const db = drizzle(sqlite, { schema });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAC9B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,sBAAsB,CAAA;AAE7D,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;AAEhD,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;AAC3C,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAA;AAClC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;AAEnC,MAAM,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare function runMigrations(): void;
2
+ export declare function seedMockData(adminId: string): void;
3
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,IAAI,IAAI,CA8FpC;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA+ClD"}
@@ -0,0 +1,169 @@
1
+ import { sqlite, db } from './index.js';
2
+ import { categories, resources, resourceTags } from './schema.js';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ export function runMigrations() {
5
+ sqlite.exec(`
6
+ CREATE TABLE IF NOT EXISTS initialized (
7
+ id TEXT PRIMARY KEY DEFAULT 'default',
8
+ done INTEGER NOT NULL DEFAULT 0
9
+ );
10
+
11
+ CREATE TABLE IF NOT EXISTS users (
12
+ id TEXT PRIMARY KEY,
13
+ username TEXT NOT NULL UNIQUE,
14
+ display_name TEXT NOT NULL,
15
+ email TEXT NOT NULL UNIQUE,
16
+ role TEXT NOT NULL DEFAULT 'user',
17
+ status TEXT NOT NULL DEFAULT 'active',
18
+ password_hash TEXT NOT NULL,
19
+ created_at INTEGER NOT NULL
20
+ );
21
+
22
+ CREATE TABLE IF NOT EXISTS categories (
23
+ id TEXT PRIMARY KEY,
24
+ name TEXT NOT NULL UNIQUE,
25
+ color TEXT NOT NULL DEFAULT '#0071E3',
26
+ created_at INTEGER NOT NULL
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS resources (
30
+ id TEXT PRIMARY KEY,
31
+ name TEXT NOT NULL,
32
+ url TEXT NOT NULL,
33
+ category_id TEXT REFERENCES categories(id) ON DELETE SET NULL,
34
+ visibility TEXT NOT NULL DEFAULT 'public',
35
+ logo_url TEXT DEFAULT '',
36
+ description TEXT DEFAULT '',
37
+ enabled INTEGER NOT NULL DEFAULT 1,
38
+ owner_id TEXT NOT NULL REFERENCES users(id),
39
+ visit_count INTEGER NOT NULL DEFAULT 0,
40
+ created_at INTEGER NOT NULL,
41
+ updated_at INTEGER NOT NULL
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS resource_tags (
45
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
46
+ tag TEXT NOT NULL,
47
+ PRIMARY KEY (resource_id, tag)
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS favorites (
51
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
52
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
53
+ created_at INTEGER NOT NULL,
54
+ PRIMARY KEY (user_id, resource_id)
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS visit_history (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
60
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
61
+ visited_at INTEGER NOT NULL
62
+ );
63
+
64
+ CREATE TABLE IF NOT EXISTS visit_hourly (
65
+ visit_hour INTEGER NOT NULL,
66
+ visit_count INTEGER NOT NULL DEFAULT 0,
67
+ PRIMARY KEY (visit_hour)
68
+ );
69
+
70
+ CREATE TABLE IF NOT EXISTS system_config (
71
+ id TEXT PRIMARY KEY DEFAULT 'default',
72
+ site_title TEXT NOT NULL DEFAULT '资源导航系统',
73
+ site_subtitle TEXT NOT NULL DEFAULT '统一管理与访问你的资源',
74
+ logo_url TEXT DEFAULT '',
75
+ token_expiry INTEGER NOT NULL DEFAULT 60,
76
+ reset_token_expiry INTEGER NOT NULL DEFAULT 60,
77
+ enable_register INTEGER NOT NULL DEFAULT 1,
78
+ restrict_email_domain INTEGER NOT NULL DEFAULT 0,
79
+ email_domain_whitelist TEXT DEFAULT ''
80
+ );
81
+
82
+ CREATE TABLE IF NOT EXISTS email_config (
83
+ id TEXT PRIMARY KEY DEFAULT 'default',
84
+ smtp_host TEXT DEFAULT '',
85
+ smtp_port INTEGER DEFAULT 465,
86
+ encryption TEXT DEFAULT 'ssl',
87
+ from_email TEXT DEFAULT '',
88
+ from_name TEXT DEFAULT '资源导航系统',
89
+ smtp_user TEXT DEFAULT '',
90
+ smtp_password TEXT DEFAULT ''
91
+ );
92
+
93
+ CREATE TABLE IF NOT EXISTS reset_tokens (
94
+ token TEXT PRIMARY KEY,
95
+ email TEXT NOT NULL,
96
+ expires_at INTEGER NOT NULL,
97
+ used INTEGER NOT NULL DEFAULT 0
98
+ );
99
+
100
+ CREATE TABLE IF NOT EXISTS rsa_keys (
101
+ id TEXT PRIMARY KEY DEFAULT 'current',
102
+ public_key TEXT NOT NULL,
103
+ private_key TEXT NOT NULL,
104
+ created_at INTEGER NOT NULL
105
+ );
106
+
107
+ INSERT OR IGNORE INTO initialized (id, done) VALUES ('default', 0);
108
+ INSERT OR IGNORE INTO system_config (id) VALUES ('default');
109
+ INSERT OR IGNORE INTO email_config (id) VALUES ('default');
110
+ `);
111
+ const oldVisitHourly = sqlite.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='resource_visit_hourly'").get();
112
+ if (oldVisitHourly?.name) {
113
+ const newCountRow = sqlite.prepare('SELECT COUNT(*) as count FROM visit_hourly').get();
114
+ if ((newCountRow?.count || 0) === 0) {
115
+ sqlite.exec(`
116
+ INSERT INTO visit_hourly (visit_hour, visit_count)
117
+ SELECT visit_hour, SUM(visit_count) AS visit_count
118
+ FROM resource_visit_hourly
119
+ GROUP BY visit_hour
120
+ `);
121
+ }
122
+ sqlite.exec('DROP TABLE IF EXISTS resource_visit_hourly;');
123
+ }
124
+ }
125
+ export function seedMockData(adminId) {
126
+ const now = Math.floor(Date.now() / 1000);
127
+ // Insert 5 categories with fixed IDs
128
+ db.insert(categories).values([
129
+ { id: 'cat-001', name: '开发工具', color: '#0071E3', createdAt: now },
130
+ { id: 'cat-002', name: '设计资源', color: '#AF52DE', createdAt: now },
131
+ { id: 'cat-003', name: '文档知识', color: '#34C759', createdAt: now },
132
+ { id: 'cat-004', name: '效率工具', color: '#FF9500', createdAt: now },
133
+ { id: 'cat-005', name: 'AI 工具', color: '#FF3B30', createdAt: now },
134
+ ]).run();
135
+ // Insert 10 resources
136
+ const resourceData = [
137
+ { name: 'GitHub', url: 'https://github.com', categoryId: 'cat-001', visibility: 'public', visitCount: 100, tags: ['前端', '后端', '开源'] },
138
+ { name: 'Figma', url: 'https://figma.com', categoryId: 'cat-002', visibility: 'public', visitCount: 80, tags: ['设计'] },
139
+ { name: 'MDN Web Docs', url: 'https://developer.mozilla.org', categoryId: 'cat-003', visibility: 'public', visitCount: 90, tags: ['前端', '文档'] },
140
+ { name: 'Notion', url: 'https://notion.so', categoryId: 'cat-004', visibility: 'public', visitCount: 70, tags: ['效率', '文档'] },
141
+ { name: 'ChatGPT', url: 'https://chat.openai.com', categoryId: 'cat-005', visibility: 'public', visitCount: 120, tags: ['AI'] },
142
+ { name: 'VS Code', url: 'https://code.visualstudio.com', categoryId: 'cat-001', visibility: 'public', visitCount: 60, tags: ['前端', '后端', '开源'] },
143
+ { name: 'Dribbble', url: 'https://dribbble.com', categoryId: 'cat-002', visibility: 'public', visitCount: 40, tags: ['设计'] },
144
+ { name: 'Postman', url: 'https://postman.com', categoryId: 'cat-001', visibility: 'public', visitCount: 50, tags: ['后端', 'API'] },
145
+ { name: 'Obsidian', url: 'https://obsidian.md', categoryId: 'cat-004', visibility: 'public', visitCount: 30, tags: ['效率'] },
146
+ { name: '私有资源(示例)', url: 'https://private.example.com', categoryId: 'cat-003', visibility: 'private', visitCount: 5, tags: ['文档'] },
147
+ ];
148
+ for (const r of resourceData) {
149
+ const id = uuidv4();
150
+ db.insert(resources).values({
151
+ id,
152
+ name: r.name,
153
+ url: r.url,
154
+ categoryId: r.categoryId,
155
+ visibility: r.visibility,
156
+ logoUrl: '',
157
+ description: '',
158
+ enabled: true,
159
+ ownerId: adminId,
160
+ visitCount: r.visitCount,
161
+ createdAt: now,
162
+ updatedAt: now,
163
+ }).run();
164
+ for (const tag of r.tags) {
165
+ db.insert(resourceTags).values({ resourceId: id, tag }).run();
166
+ }
167
+ }
168
+ }
169
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"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"}