diff --git a/.gitignore b/.gitignore index b0849ea..76f8a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,10 +47,13 @@ logs/ Thumbs.db # 前端 -frontend/node_modules/ -frontend/dist/ -frontend/.vite/ +apps/frontend/node_modules/ +apps/frontend/dist/ +apps/frontend/.vite/ +apps/new-frontend/node_modules/ +apps/new-frontend/dist/ +apps/new-frontend/.vite/ .claude .codex -openspec \ No newline at end of file +openspec diff --git a/README.md b/README.md index 0ab4c7c..62c1c83 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,16 @@ python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Linux/Mac -pip install -r backend/requirements.txt -python3 run.py +pip install -r apps/backend/requirements.txt +python main.py backend # 前端 -cd frontend +cd apps/frontend npm install npm run dev # 创建管理员 -python backend/scripts/create_admin.py +PYTHONPATH=apps python apps/backend/scripts/create_admin.py ``` ### 访问地址 @@ -57,15 +57,11 @@ python backend/scripts/create_admin.py ## 进程管理 ```bash -# Windows -manage.bat start [all/backend/fronted] -manage.bat stop [all/backend/fronted] -manage.bat status - -# Linux/Mac -./manage.sh start [all/backend/fronted] -./manage.sh stop [all/backend/fronted] -./manage.sh status +python main.py backend-daemon +python main.py frontend-daemon +python main.py status +python main.py stop [all|backend|frontend] +python main.py frontend-build ``` ## 配置 diff --git a/backend/api/admin.py b/apps/backend/api/admin.py similarity index 100% rename from backend/api/admin.py rename to apps/backend/api/admin.py diff --git a/backend/api/auth.py b/apps/backend/api/auth.py similarity index 100% rename from backend/api/auth.py rename to apps/backend/api/auth.py diff --git a/backend/api/check_in.py b/apps/backend/api/check_in.py similarity index 100% rename from backend/api/check_in.py rename to apps/backend/api/check_in.py diff --git a/backend/api/tasks.py b/apps/backend/api/tasks.py similarity index 100% rename from backend/api/tasks.py rename to apps/backend/api/tasks.py diff --git a/backend/api/templates.py b/apps/backend/api/templates.py similarity index 100% rename from backend/api/templates.py rename to apps/backend/api/templates.py diff --git a/backend/api/users.py b/apps/backend/api/users.py similarity index 100% rename from backend/api/users.py rename to apps/backend/api/users.py diff --git a/backend/config.py b/apps/backend/config.py similarity index 96% rename from backend/config.py rename to apps/backend/config.py index 0d91616..6fe6cda 100644 --- a/backend/config.py +++ b/apps/backend/config.py @@ -1,10 +1,9 @@ -import os from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict from typing import List # 项目根目录 -BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parents[2] class Settings(BaseSettings): diff --git a/backend/dependencies.py b/apps/backend/dependencies.py similarity index 100% rename from backend/dependencies.py rename to apps/backend/dependencies.py diff --git a/backend/exceptions.py b/apps/backend/exceptions.py similarity index 100% rename from backend/exceptions.py rename to apps/backend/exceptions.py diff --git a/backend/limiter.py b/apps/backend/limiter.py similarity index 100% rename from backend/limiter.py rename to apps/backend/limiter.py diff --git a/backend/main.py b/apps/backend/main.py similarity index 98% rename from backend/main.py rename to apps/backend/main.py index 6a54ac4..08a0439 100644 --- a/backend/main.py +++ b/apps/backend/main.py @@ -167,9 +167,10 @@ app.include_router(templates.router, prefix=f"{settings.API_PREFIX}/templates", if __name__ == "__main__": import uvicorn uvicorn.run( - "main:app", + "backend.main:app", host="0.0.0.0", port=8000, reload=True, + reload_dirs=[str(settings.BASE_DIR / "apps" / "backend")], log_level="info", ) diff --git a/backend/models/__init__.py b/apps/backend/models/__init__.py similarity index 100% rename from backend/models/__init__.py rename to apps/backend/models/__init__.py diff --git a/backend/models/check_in_record.py b/apps/backend/models/check_in_record.py similarity index 100% rename from backend/models/check_in_record.py rename to apps/backend/models/check_in_record.py diff --git a/backend/models/check_in_task.py b/apps/backend/models/check_in_task.py similarity index 100% rename from backend/models/check_in_task.py rename to apps/backend/models/check_in_task.py diff --git a/backend/models/database.py b/apps/backend/models/database.py similarity index 100% rename from backend/models/database.py rename to apps/backend/models/database.py diff --git a/backend/models/task_template.py b/apps/backend/models/task_template.py similarity index 100% rename from backend/models/task_template.py rename to apps/backend/models/task_template.py diff --git a/backend/models/user.py b/apps/backend/models/user.py similarity index 100% rename from backend/models/user.py rename to apps/backend/models/user.py diff --git a/backend/requirements.txt b/apps/backend/requirements.txt similarity index 100% rename from backend/requirements.txt rename to apps/backend/requirements.txt diff --git a/backend/schemas/__init__.py b/apps/backend/schemas/__init__.py similarity index 100% rename from backend/schemas/__init__.py rename to apps/backend/schemas/__init__.py diff --git a/backend/schemas/auth.py b/apps/backend/schemas/auth.py similarity index 100% rename from backend/schemas/auth.py rename to apps/backend/schemas/auth.py diff --git a/backend/schemas/check_in.py b/apps/backend/schemas/check_in.py similarity index 100% rename from backend/schemas/check_in.py rename to apps/backend/schemas/check_in.py diff --git a/backend/schemas/response.py b/apps/backend/schemas/response.py similarity index 100% rename from backend/schemas/response.py rename to apps/backend/schemas/response.py diff --git a/backend/schemas/task.py b/apps/backend/schemas/task.py similarity index 100% rename from backend/schemas/task.py rename to apps/backend/schemas/task.py diff --git a/backend/schemas/template.py b/apps/backend/schemas/template.py similarity index 100% rename from backend/schemas/template.py rename to apps/backend/schemas/template.py diff --git a/backend/schemas/user.py b/apps/backend/schemas/user.py similarity index 100% rename from backend/schemas/user.py rename to apps/backend/schemas/user.py diff --git a/backend/scripts/create_admin.py b/apps/backend/scripts/create_admin.py similarity index 92% rename from backend/scripts/create_admin.py rename to apps/backend/scripts/create_admin.py index b11872a..0ec92e2 100644 --- a/backend/scripts/create_admin.py +++ b/apps/backend/scripts/create_admin.py @@ -3,18 +3,16 @@ 创建管理员用户的脚本 使用方法: - python backend/scripts/create_admin.py + PYTHONPATH=apps python apps/backend/scripts/create_admin.py 或使用虚拟环境: - ./venv/Scripts/python.exe backend/scripts/create_admin.py + PYTHONPATH=apps ./venv/bin/python apps/backend/scripts/create_admin.py """ import sys -import os from pathlib import Path -# 添加项目根目录到路径 -BASE_DIR = Path(__file__).resolve().parent.parent.parent -sys.path.insert(0, str(BASE_DIR)) +APPS_DIR = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(APPS_DIR)) from backend.models import init_db, User from backend.models.database import SessionLocal diff --git a/backend/scripts/migrate_add_account_lockout.py b/apps/backend/scripts/migrate_add_account_lockout.py similarity index 93% rename from backend/scripts/migrate_add_account_lockout.py rename to apps/backend/scripts/migrate_add_account_lockout.py index 3c11efe..bf447cc 100644 --- a/backend/scripts/migrate_add_account_lockout.py +++ b/apps/backend/scripts/migrate_add_account_lockout.py @@ -7,15 +7,15 @@ - last_failed_login: 最后一次登录失败时间 运行方式: + PYTHONPATH=apps python -m backend.scripts.migrate_add_account_lockout python -m backend.scripts.migrate_add_account_lockout """ import sys from pathlib import Path -# 添加项目根目录到 Python 路径 -project_root = Path(__file__).resolve().parent.parent.parent -sys.path.insert(0, str(project_root)) +APPS_DIR = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(APPS_DIR)) from sqlalchemy import text from backend.models.database import engine diff --git a/backend/scripts/test_exceptions.py b/apps/backend/scripts/test_exceptions.py similarity index 95% rename from backend/scripts/test_exceptions.py rename to apps/backend/scripts/test_exceptions.py index 67c5b2c..b99ba58 100644 --- a/backend/scripts/test_exceptions.py +++ b/apps/backend/scripts/test_exceptions.py @@ -2,7 +2,10 @@ 测试新的异常处理系统 """ import sys -sys.path.insert(0, '..') +from pathlib import Path + +APPS_DIR = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(APPS_DIR)) from backend.exceptions import ( ValidationError, @@ -94,7 +97,7 @@ def check_old_exception_patterns(): for pattern_name, pattern in patterns.items(): results[pattern_name] = [] - for root, dirs, files in os.walk('../backend/api'): + for root, dirs, files in os.walk(APPS_DIR / 'backend' / 'api'): for file in files: if file.endswith('.py'): filepath = os.path.join(root, file) diff --git a/backend/services/admin_service.py b/apps/backend/services/admin_service.py similarity index 100% rename from backend/services/admin_service.py rename to apps/backend/services/admin_service.py diff --git a/backend/services/auth_service.py b/apps/backend/services/auth_service.py similarity index 100% rename from backend/services/auth_service.py rename to apps/backend/services/auth_service.py diff --git a/backend/services/check_in_service.py b/apps/backend/services/check_in_service.py similarity index 100% rename from backend/services/check_in_service.py rename to apps/backend/services/check_in_service.py diff --git a/backend/services/email_service.py b/apps/backend/services/email_service.py similarity index 100% rename from backend/services/email_service.py rename to apps/backend/services/email_service.py diff --git a/backend/services/registration_manager.py b/apps/backend/services/registration_manager.py similarity index 100% rename from backend/services/registration_manager.py rename to apps/backend/services/registration_manager.py diff --git a/backend/services/scheduler_service.py b/apps/backend/services/scheduler_service.py similarity index 100% rename from backend/services/scheduler_service.py rename to apps/backend/services/scheduler_service.py diff --git a/backend/services/task_service.py b/apps/backend/services/task_service.py similarity index 100% rename from backend/services/task_service.py rename to apps/backend/services/task_service.py diff --git a/backend/services/template_service.py b/apps/backend/services/template_service.py similarity index 100% rename from backend/services/template_service.py rename to apps/backend/services/template_service.py diff --git a/backend/services/user_service.py b/apps/backend/services/user_service.py similarity index 100% rename from backend/services/user_service.py rename to apps/backend/services/user_service.py diff --git a/backend/utils/db_helpers.py b/apps/backend/utils/db_helpers.py similarity index 100% rename from backend/utils/db_helpers.py rename to apps/backend/utils/db_helpers.py diff --git a/backend/utils/json_helpers.py b/apps/backend/utils/json_helpers.py similarity index 100% rename from backend/utils/json_helpers.py rename to apps/backend/utils/json_helpers.py diff --git a/backend/utils/jwt.py b/apps/backend/utils/jwt.py similarity index 100% rename from backend/utils/jwt.py rename to apps/backend/utils/jwt.py diff --git a/backend/utils/time_helpers.py b/apps/backend/utils/time_helpers.py similarity index 100% rename from backend/utils/time_helpers.py rename to apps/backend/utils/time_helpers.py diff --git a/backend/workers/check_in_worker.py b/apps/backend/workers/check_in_worker.py similarity index 100% rename from backend/workers/check_in_worker.py rename to apps/backend/workers/check_in_worker.py diff --git a/backend/workers/email_notifier.py b/apps/backend/workers/email_notifier.py similarity index 100% rename from backend/workers/email_notifier.py rename to apps/backend/workers/email_notifier.py diff --git a/backend/workers/token_refresher.py b/apps/backend/workers/token_refresher.py similarity index 100% rename from backend/workers/token_refresher.py rename to apps/backend/workers/token_refresher.py diff --git a/frontend/.env.development b/apps/frontend/.env.development similarity index 100% rename from frontend/.env.development rename to apps/frontend/.env.development diff --git a/frontend/.env.production b/apps/frontend/.env.production similarity index 100% rename from frontend/.env.production rename to apps/frontend/.env.production diff --git a/frontend/.gitignore b/apps/frontend/.gitignore similarity index 100% rename from frontend/.gitignore rename to apps/frontend/.gitignore diff --git a/frontend/.prettierignore b/apps/frontend/.prettierignore similarity index 100% rename from frontend/.prettierignore rename to apps/frontend/.prettierignore diff --git a/frontend/.prettierrc b/apps/frontend/.prettierrc similarity index 100% rename from frontend/.prettierrc rename to apps/frontend/.prettierrc diff --git a/frontend/eslint.config.js b/apps/frontend/eslint.config.js similarity index 100% rename from frontend/eslint.config.js rename to apps/frontend/eslint.config.js diff --git a/frontend/index.html b/apps/frontend/index.html similarity index 100% rename from frontend/index.html rename to apps/frontend/index.html diff --git a/frontend/package-lock.json b/apps/frontend/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to apps/frontend/package-lock.json diff --git a/frontend/package.json b/apps/frontend/package.json similarity index 100% rename from frontend/package.json rename to apps/frontend/package.json diff --git a/frontend/postcss.config.js b/apps/frontend/postcss.config.js similarity index 100% rename from frontend/postcss.config.js rename to apps/frontend/postcss.config.js diff --git a/frontend/public/favicon.svg b/apps/frontend/public/favicon.svg similarity index 100% rename from frontend/public/favicon.svg rename to apps/frontend/public/favicon.svg diff --git a/frontend/public/vite.svg b/apps/frontend/public/vite.svg similarity index 100% rename from frontend/public/vite.svg rename to apps/frontend/public/vite.svg diff --git a/frontend/src/App.vue b/apps/frontend/src/App.vue similarity index 100% rename from frontend/src/App.vue rename to apps/frontend/src/App.vue diff --git a/frontend/src/antd-theme.js b/apps/frontend/src/antd-theme.js similarity index 100% rename from frontend/src/antd-theme.js rename to apps/frontend/src/antd-theme.js diff --git a/frontend/src/api/client.js b/apps/frontend/src/api/client.js similarity index 100% rename from frontend/src/api/client.js rename to apps/frontend/src/api/client.js diff --git a/frontend/src/api/index.js b/apps/frontend/src/api/index.js similarity index 100% rename from frontend/src/api/index.js rename to apps/frontend/src/api/index.js diff --git a/frontend/src/assets/vue.svg b/apps/frontend/src/assets/vue.svg similarity index 100% rename from frontend/src/assets/vue.svg rename to apps/frontend/src/assets/vue.svg diff --git a/frontend/src/components/CrontabEditor.vue b/apps/frontend/src/components/CrontabEditor.vue similarity index 100% rename from frontend/src/components/CrontabEditor.vue rename to apps/frontend/src/components/CrontabEditor.vue diff --git a/frontend/src/components/FieldConfigEditor.vue b/apps/frontend/src/components/FieldConfigEditor.vue similarity index 100% rename from frontend/src/components/FieldConfigEditor.vue rename to apps/frontend/src/components/FieldConfigEditor.vue diff --git a/frontend/src/components/FieldTreeNode.vue b/apps/frontend/src/components/FieldTreeNode.vue similarity index 100% rename from frontend/src/components/FieldTreeNode.vue rename to apps/frontend/src/components/FieldTreeNode.vue diff --git a/frontend/src/components/Layout.vue b/apps/frontend/src/components/Layout.vue similarity index 100% rename from frontend/src/components/Layout.vue rename to apps/frontend/src/components/Layout.vue diff --git a/frontend/src/components/Navbar.vue b/apps/frontend/src/components/Navbar.vue similarity index 100% rename from frontend/src/components/Navbar.vue rename to apps/frontend/src/components/Navbar.vue diff --git a/frontend/src/components/QRCodeModal.vue b/apps/frontend/src/components/QRCodeModal.vue similarity index 100% rename from frontend/src/components/QRCodeModal.vue rename to apps/frontend/src/components/QRCodeModal.vue diff --git a/frontend/src/components/common/EmptyState.vue b/apps/frontend/src/components/common/EmptyState.vue similarity index 100% rename from frontend/src/components/common/EmptyState.vue rename to apps/frontend/src/components/common/EmptyState.vue diff --git a/frontend/src/components/common/LoadingState.vue b/apps/frontend/src/components/common/LoadingState.vue similarity index 100% rename from frontend/src/components/common/LoadingState.vue rename to apps/frontend/src/components/common/LoadingState.vue diff --git a/frontend/src/components/common/StatsCard.vue b/apps/frontend/src/components/common/StatsCard.vue similarity index 100% rename from frontend/src/components/common/StatsCard.vue rename to apps/frontend/src/components/common/StatsCard.vue diff --git a/frontend/src/composables/useAsyncAction.js b/apps/frontend/src/composables/useAsyncAction.js similarity index 100% rename from frontend/src/composables/useAsyncAction.js rename to apps/frontend/src/composables/useAsyncAction.js diff --git a/frontend/src/composables/useBreakpoint.js b/apps/frontend/src/composables/useBreakpoint.js similarity index 100% rename from frontend/src/composables/useBreakpoint.js rename to apps/frontend/src/composables/useBreakpoint.js diff --git a/frontend/src/composables/usePollStatus.js b/apps/frontend/src/composables/usePollStatus.js similarity index 100% rename from frontend/src/composables/usePollStatus.js rename to apps/frontend/src/composables/usePollStatus.js diff --git a/frontend/src/composables/useTheme.js b/apps/frontend/src/composables/useTheme.js similarity index 100% rename from frontend/src/composables/useTheme.js rename to apps/frontend/src/composables/useTheme.js diff --git a/frontend/src/composables/useTokenMonitor.js b/apps/frontend/src/composables/useTokenMonitor.js similarity index 100% rename from frontend/src/composables/useTokenMonitor.js rename to apps/frontend/src/composables/useTokenMonitor.js diff --git a/frontend/src/main.js b/apps/frontend/src/main.js similarity index 100% rename from frontend/src/main.js rename to apps/frontend/src/main.js diff --git a/frontend/src/router/index.js b/apps/frontend/src/router/index.js similarity index 100% rename from frontend/src/router/index.js rename to apps/frontend/src/router/index.js diff --git a/frontend/src/stores/admin.js b/apps/frontend/src/stores/admin.js similarity index 100% rename from frontend/src/stores/admin.js rename to apps/frontend/src/stores/admin.js diff --git a/frontend/src/stores/auth.js b/apps/frontend/src/stores/auth.js similarity index 100% rename from frontend/src/stores/auth.js rename to apps/frontend/src/stores/auth.js diff --git a/frontend/src/stores/checkIn.js b/apps/frontend/src/stores/checkIn.js similarity index 100% rename from frontend/src/stores/checkIn.js rename to apps/frontend/src/stores/checkIn.js diff --git a/frontend/src/stores/task.js b/apps/frontend/src/stores/task.js similarity index 100% rename from frontend/src/stores/task.js rename to apps/frontend/src/stores/task.js diff --git a/frontend/src/stores/template.js b/apps/frontend/src/stores/template.js similarity index 100% rename from frontend/src/stores/template.js rename to apps/frontend/src/stores/template.js diff --git a/frontend/src/stores/user.js b/apps/frontend/src/stores/user.js similarity index 100% rename from frontend/src/stores/user.js rename to apps/frontend/src/stores/user.js diff --git a/frontend/src/style.css b/apps/frontend/src/style.css similarity index 100% rename from frontend/src/style.css rename to apps/frontend/src/style.css diff --git a/frontend/src/utils/helpers.js b/apps/frontend/src/utils/helpers.js similarity index 100% rename from frontend/src/utils/helpers.js rename to apps/frontend/src/utils/helpers.js diff --git a/frontend/src/views/DashboardView.vue b/apps/frontend/src/views/DashboardView.vue similarity index 100% rename from frontend/src/views/DashboardView.vue rename to apps/frontend/src/views/DashboardView.vue diff --git a/frontend/src/views/LoginView.vue b/apps/frontend/src/views/LoginView.vue similarity index 100% rename from frontend/src/views/LoginView.vue rename to apps/frontend/src/views/LoginView.vue diff --git a/frontend/src/views/NotFoundView.vue b/apps/frontend/src/views/NotFoundView.vue similarity index 100% rename from frontend/src/views/NotFoundView.vue rename to apps/frontend/src/views/NotFoundView.vue diff --git a/frontend/src/views/PendingApprovalView.vue b/apps/frontend/src/views/PendingApprovalView.vue similarity index 100% rename from frontend/src/views/PendingApprovalView.vue rename to apps/frontend/src/views/PendingApprovalView.vue diff --git a/frontend/src/views/RecordsView.vue b/apps/frontend/src/views/RecordsView.vue similarity index 100% rename from frontend/src/views/RecordsView.vue rename to apps/frontend/src/views/RecordsView.vue diff --git a/frontend/src/views/SettingsView.vue b/apps/frontend/src/views/SettingsView.vue similarity index 100% rename from frontend/src/views/SettingsView.vue rename to apps/frontend/src/views/SettingsView.vue diff --git a/frontend/src/views/TaskRecordsView.vue b/apps/frontend/src/views/TaskRecordsView.vue similarity index 100% rename from frontend/src/views/TaskRecordsView.vue rename to apps/frontend/src/views/TaskRecordsView.vue diff --git a/frontend/src/views/TasksView.vue b/apps/frontend/src/views/TasksView.vue similarity index 100% rename from frontend/src/views/TasksView.vue rename to apps/frontend/src/views/TasksView.vue diff --git a/frontend/src/views/admin/LogsView.vue b/apps/frontend/src/views/admin/LogsView.vue similarity index 100% rename from frontend/src/views/admin/LogsView.vue rename to apps/frontend/src/views/admin/LogsView.vue diff --git a/frontend/src/views/admin/RecordsView.vue b/apps/frontend/src/views/admin/RecordsView.vue similarity index 100% rename from frontend/src/views/admin/RecordsView.vue rename to apps/frontend/src/views/admin/RecordsView.vue diff --git a/frontend/src/views/admin/StatsView.vue b/apps/frontend/src/views/admin/StatsView.vue similarity index 100% rename from frontend/src/views/admin/StatsView.vue rename to apps/frontend/src/views/admin/StatsView.vue diff --git a/frontend/src/views/admin/TemplatesView.vue b/apps/frontend/src/views/admin/TemplatesView.vue similarity index 100% rename from frontend/src/views/admin/TemplatesView.vue rename to apps/frontend/src/views/admin/TemplatesView.vue diff --git a/frontend/src/views/admin/UsersView.vue b/apps/frontend/src/views/admin/UsersView.vue similarity index 100% rename from frontend/src/views/admin/UsersView.vue rename to apps/frontend/src/views/admin/UsersView.vue diff --git a/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js similarity index 100% rename from frontend/tailwind.config.js rename to apps/frontend/tailwind.config.js diff --git a/frontend/vite.config.js b/apps/frontend/vite.config.js similarity index 100% rename from frontend/vite.config.js rename to apps/frontend/vite.config.js diff --git a/nginx.conf.example b/deploy/nginx/checkin-app.conf.example similarity index 96% rename from nginx.conf.example rename to deploy/nginx/checkin-app.conf.example index ead052e..f521802 100644 --- a/nginx.conf.example +++ b/deploy/nginx/checkin-app.conf.example @@ -3,7 +3,7 @@ # ============================================================================== # # Usage: -# 1. Copy this file: sudo cp nginx.conf.example /etc/nginx/sites-available/checkin-app +# 1. Copy this file: sudo cp deploy/nginx/checkin-app.conf.example /etc/nginx/sites-available/checkin-app # 2. Edit the file and replace placeholders with your actual values # 3. Create symlink: sudo ln -s /etc/nginx/sites-available/checkin-app /etc/nginx/sites-enabled/ # 4. Test config: sudo nginx -t @@ -138,7 +138,7 @@ server { # add_header X-Content-Type-Options "nosniff" always; # # # CHANGE THIS: Replace with your actual frontend build path -# root /path/to/CheckInApp/frontend/dist; +# root /path/to/CheckInApp/apps/frontend/dist; # index index.html; # # # ... (rest of the configuration same as HTTP version above) diff --git a/checkin-app.service.example b/deploy/systemd/checkin-app.service.example similarity index 82% rename from checkin-app.service.example rename to deploy/systemd/checkin-app.service.example index 002a914..82fa522 100644 --- a/checkin-app.service.example +++ b/deploy/systemd/checkin-app.service.example @@ -5,7 +5,7 @@ # This file defines a systemd service for running the CheckIn App backend # # Installation: -# 1. Copy this file: sudo cp checkin-app.service.example /etc/systemd/system/checkin-app.service +# 1. Copy this file: sudo cp deploy/systemd/checkin-app.service.example /etc/systemd/system/checkin-app.service # 2. Edit the file and replace placeholders with your actual values # 3. Reload systemd: sudo systemctl daemon-reload # 4. Enable service: sudo systemctl enable checkin-app.service @@ -28,7 +28,7 @@ After=network.target Wants=network-online.target [Service] -Type=forking +Type=simple # CHANGE THIS: Replace with your actual username User=username @@ -36,14 +36,8 @@ User=username # Example: /home/username/CheckInApp WorkingDirectory=/path/to/CheckInApp -# PID file written by manage.sh -PIDFile=/path/to/CheckInApp/backend.pid - -# Start backend using manage.sh script -ExecStart=/path/to/CheckInApp/manage.sh start backend - -# Stop backend using manage.sh script -ExecStop=/path/to/CheckInApp/manage.sh stop backend +# Start backend using the Python project manager +ExecStart=/path/to/CheckInApp/venv/bin/python /path/to/CheckInApp/main.py backend --no-reload # Restart policy Restart=on-failure diff --git a/docs/architecture.md b/docs/architecture.md index 87c6abe..c734f9d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -152,7 +152,7 @@ CheckIn App V2 采用用户-任务分离架构,一个用户可管理多个打 ### 后端分层 ``` -backend/ +apps/backend/ ├── api/ # 路由层(29 个端点) ├── services/ # 业务逻辑层 ├── models/ # 数据模型层 @@ -164,7 +164,7 @@ backend/ ### 前端分层 ``` -frontend/src/ +apps/frontend/src/ ├── api/ # API 调用封装 ├── views/ # 页面组件 ├── components/ # 可复用组件 diff --git a/docs/deployment.md b/docs/deployment.md index 5066bbc..aebbda9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -37,7 +37,7 @@ python3 -m venv venv source venv/bin/activate # 安装依赖 -pip install -r backend/requirements.txt +pip install -r apps/backend/requirements.txt # 生产环境额外依赖 pip install gunicorn @@ -50,7 +50,7 @@ vim .env # 修改环境变量 #### 2. 前端部署 ```bash -cd frontend +cd apps/frontend # 安装依赖 npm install @@ -63,11 +63,11 @@ npm run build **使用 Nginx 托管**: -[示例文件](../nginx.conf.example) +[示例文件](../deploy/nginx/checkin-app.conf.example) #### 3. 使用 Systemd 管理 -[示例文件](../checkin-app.service.example) +[示例文件](../deploy/systemd/checkin-app.service.example) ### 方式二:Docker 部署(推荐) diff --git a/docs/development.md b/docs/development.md index 6c0599c..8513379 100644 --- a/docs/development.md +++ b/docs/development.md @@ -15,18 +15,18 @@ source venv/bin/activate # Linux/Mac venv\Scripts\activate # Windows # 安装依赖 -pip install -r backend/requirements.txt +pip install -r apps/backend/requirements.txt # 安装开发依赖 pip install pytest pytest-asyncio black flake8 -python3 run.py +python main.py backend ``` ### 前端开发 ```bash -cd frontend +cd apps/frontend # 安装依赖 npm install @@ -44,7 +44,7 @@ npm run format ### 后端目录说明 ``` -backend/ +apps/backend/ ├── main.py # FastAPI 应用入口,CORS、路由注册 ├── config.py # Pydantic Settings 配置 ├── dependencies.py # 依赖注入(认证、权限) @@ -89,7 +89,7 @@ backend/ ### 前端目录说明 ``` -frontend/src/ +apps/frontend/src/ ├── main.js # Vue 应用入口 ├── App.vue # 根组件 │ @@ -162,8 +162,8 @@ def create_tag(db: Session, task_id: int, tag_data: TaskTagCreate): def add_tag(task_id: int, tag: TaskTagCreate, db: Session = Depends(get_db)): return tag_service.create_tag(db, task_id, tag) -# main.py -from api import tags +# apps/backend/main.py +from backend.api import tags app.include_router(tags.router, prefix="/api") ``` @@ -211,10 +211,10 @@ export const useTagStore = defineStore('tag', { ```bash # 修改模型后生成迁移脚本 -# 手动创建脚本在 backend/scripts/migrate_*.py +# 手动创建脚本在 apps/backend/scripts/migrate_*.py # 执行迁移 -python backend/scripts/migrate_xxx.py +PYTHONPATH=apps python apps/backend/scripts/migrate_xxx.py ``` ### 测试 @@ -232,7 +232,7 @@ def test_create_task(): assert task.is_active == True # 运行测试 -pytest backend/tests/ +PYTHONPATH=apps pytest apps/backend/tests/ ``` #### 前端测试 @@ -257,7 +257,7 @@ npm run test ### 后端规范 -- 使用 Black 格式化: `black backend/` +- 使用 Black 格式化: `black apps/backend/` - 遵循 PEP 8 - 函数添加类型注解 - API 路由使用 Pydantic 模型验证 diff --git a/main.py b/main.py new file mode 100644 index 0000000..2077e99 --- /dev/null +++ b/main.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +"""Cross-platform project manager for CheckIn App.""" + +from __future__ import annotations + +import argparse +import os +import signal +import subprocess +import sys +import time +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent +APPS_DIR = REPO_ROOT / "apps" +BACKEND_DIR = APPS_DIR / "backend" +FRONTEND_DIR = APPS_DIR / "frontend" +VENV_DIR = REPO_ROOT / "venv" +PYTHON_BIN = VENV_DIR / ("Scripts/python.exe" if os.name == "nt" else "bin/python") +BACKEND_PID = REPO_ROOT / "backend.pid" +FRONTEND_PID = REPO_ROOT / "frontend.pid" +LOGS_DIR = REPO_ROOT / "logs" +BACKEND_LOG = LOGS_DIR / "backend.log" +FRONTEND_LOG = LOGS_DIR / "frontend.log" + +BACKEND_PORT = 8000 +FRONTEND_PORT = 3000 + + +def ensure_import_path() -> None: + apps_path = str(APPS_DIR) + if apps_path not in sys.path: + sys.path.insert(0, apps_path) + os.chdir(REPO_ROOT) + + +def ensure_runtime_dirs() -> None: + for path in (REPO_ROOT / "data", LOGS_DIR, REPO_ROOT / "sessions"): + path.mkdir(parents=True, exist_ok=True) + + +def get_python() -> str: + if PYTHON_BIN.exists(): + return str(PYTHON_BIN) + return sys.executable + + +def read_pid(path: Path) -> int | None: + try: + return int(path.read_text(encoding="utf-8").strip()) + except (FileNotFoundError, ValueError): + return None + + +def is_process_alive(pid: int) -> bool: + try: + os.kill(pid, 0) + except OSError: + return False + return True + + +def stop_pid_file(path: Path, name: str) -> bool: + pid = read_pid(path) + if pid is None: + print(f"{name}: not running") + return False + if not is_process_alive(pid): + path.unlink(missing_ok=True) + print(f"{name}: stale pid removed") + return False + sig = signal.CTRL_BREAK_EVENT if os.name == "nt" else signal.SIGTERM + os.kill(pid, sig) + path.unlink(missing_ok=True) + print(f"{name}: stopped pid {pid}") + return True + + +def backend_env() -> dict[str, str]: + env = os.environ.copy() + existing = env.get("PYTHONPATH") + apps_path = str(APPS_DIR) + env["PYTHONPATH"] = apps_path if not existing else os.pathsep.join([apps_path, existing]) + return env + + +def run_backend(args: argparse.Namespace) -> int: + ensure_import_path() + ensure_runtime_dirs() + if args.check: + try: + import backend.main # noqa: F401 + except ModuleNotFoundError as exc: + print(f"backend import path OK; missing dependency: {exc.name}", file=sys.stderr) + print(f"install dependencies with: {get_python()} -m pip install -r apps/backend/requirements.txt", file=sys.stderr) + return 2 + print("backend.main:app import OK") + return 0 + + import uvicorn + + uvicorn.run( + "backend.main:app", + host=args.host, + port=args.port, + reload=args.reload, + reload_dirs=[str(BACKEND_DIR)] if args.reload else None, + log_level=args.log_level, + access_log=True, + ) + return 0 + + +def start_backend_daemon(args: argparse.Namespace) -> int: + ensure_runtime_dirs() + if BACKEND_PID.exists(): + pid = read_pid(BACKEND_PID) + if pid is not None and is_process_alive(pid): + print(f"backend: already running pid {pid}") + return 0 + BACKEND_PID.unlink(missing_ok=True) + + cmd = [ + get_python(), + str(REPO_ROOT / "main.py"), + "backend", + "--host", + args.host, + "--port", + str(args.port), + "--no-reload", + "--log-level", + args.log_level, + ] + BACKEND_LOG.parent.mkdir(parents=True, exist_ok=True) + log_file = BACKEND_LOG.open("a", encoding="utf-8") + proc = subprocess.Popen( + cmd, + cwd=REPO_ROOT, + env=backend_env(), + stdout=log_file, + stderr=subprocess.STDOUT, + start_new_session=os.name != "nt", + ) + BACKEND_PID.write_text(str(proc.pid), encoding="utf-8") + print(f"backend: started pid {proc.pid}") + print(f"log: {BACKEND_LOG}") + return 0 + + +def frontend_env() -> dict[str, str]: + return os.environ.copy() + + +def run_frontend(args: argparse.Namespace) -> int: + if not FRONTEND_DIR.exists(): + print(f"frontend directory not found: {FRONTEND_DIR}", file=sys.stderr) + return 1 + cmd = ["npm", "run", "dev", "--", "--host", args.host, "--port", str(args.port)] + return subprocess.call(cmd, cwd=FRONTEND_DIR, env=frontend_env()) + + +def start_frontend_daemon(args: argparse.Namespace) -> int: + if FRONTEND_PID.exists(): + pid = read_pid(FRONTEND_PID) + if pid is not None and is_process_alive(pid): + print(f"frontend: already running pid {pid}") + return 0 + FRONTEND_PID.unlink(missing_ok=True) + + LOGS_DIR.mkdir(parents=True, exist_ok=True) + log_file = FRONTEND_LOG.open("a", encoding="utf-8") + cmd = [get_python(), str(REPO_ROOT / "main.py"), "frontend", "--host", args.host, "--port", str(args.port)] + proc = subprocess.Popen( + cmd, + cwd=REPO_ROOT, + stdout=log_file, + stderr=subprocess.STDOUT, + start_new_session=os.name != "nt", + ) + FRONTEND_PID.write_text(str(proc.pid), encoding="utf-8") + print(f"frontend: started pid {proc.pid}") + print(f"log: {FRONTEND_LOG}") + return 0 + + +def build_frontend(args: argparse.Namespace) -> int: + if not FRONTEND_DIR.exists(): + print(f"frontend directory not found: {FRONTEND_DIR}", file=sys.stderr) + return 1 + if args.install and not (FRONTEND_DIR / "node_modules").exists(): + result = subprocess.call(["npm", "install"], cwd=FRONTEND_DIR) + if result != 0: + return result + return subprocess.call(["npm", "run", "build"], cwd=FRONTEND_DIR) + + +def deploy_frontend(args: argparse.Namespace) -> int: + dist = FRONTEND_DIR / "dist" + if not dist.exists(): + print(f"build output not found: {dist}", file=sys.stderr) + print("run: python main.py frontend-build", file=sys.stderr) + return 1 + print(f"build output ready: {dist}") + print("copy this directory to the web server root configured by nginx") + return 0 + + +def status(_: argparse.Namespace) -> int: + for name, pid_file, log_file in ( + ("backend", BACKEND_PID, BACKEND_LOG), + ("frontend", FRONTEND_PID, FRONTEND_LOG), + ): + pid = read_pid(pid_file) + running = pid is not None and is_process_alive(pid) + state = "RUNNING" if running else "NOT RUNNING" + print(f"{name}: {state}") + if pid is not None: + print(f" pid: {pid}") + print(f" log: {log_file}") + return 0 + + +def stop(args: argparse.Namespace) -> int: + stopped = False + targets = ("backend", "frontend") if args.target == "all" else (args.target,) + for target in targets: + if target == "backend": + stopped = stop_pid_file(BACKEND_PID, "backend") or stopped + elif target == "frontend": + stopped = stop_pid_file(FRONTEND_PID, "frontend") or stopped + return 0 if stopped or args.target in {"backend", "frontend", "all"} else 1 + + +def add_backend_args(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--host", default="0.0.0.0") + parser.add_argument("--port", type=int, default=BACKEND_PORT) + parser.add_argument("--reload", dest="reload", action="store_true", default=True) + parser.add_argument("--no-reload", dest="reload", action="store_false") + parser.add_argument("--log-level", default="info") + parser.add_argument("--check", action="store_true", help="only verify backend.main:app imports") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CheckIn App project manager") + sub = parser.add_subparsers(dest="command", required=True) + + backend = sub.add_parser("backend", help="run backend in the foreground") + add_backend_args(backend) + backend.set_defaults(func=run_backend) + + backend_daemon = sub.add_parser("backend-daemon", help="start backend in the background") + backend_daemon.add_argument("--host", default="0.0.0.0") + backend_daemon.add_argument("--port", type=int, default=BACKEND_PORT) + backend_daemon.add_argument("--log-level", default="info") + backend_daemon.set_defaults(func=start_backend_daemon) + + frontend = sub.add_parser("frontend", help="run frontend dev server in the foreground") + frontend.add_argument("--host", default="0.0.0.0") + frontend.add_argument("--port", type=int, default=FRONTEND_PORT) + frontend.set_defaults(func=run_frontend) + + frontend_daemon = sub.add_parser("frontend-daemon", help="start frontend dev server in the background") + frontend_daemon.add_argument("--host", default="0.0.0.0") + frontend_daemon.add_argument("--port", type=int, default=FRONTEND_PORT) + frontend_daemon.set_defaults(func=start_frontend_daemon) + + frontend_build = sub.add_parser("frontend-build", help="build current frontend") + frontend_build.add_argument("--install", action="store_true", help="run npm install if node_modules is missing") + frontend_build.set_defaults(func=build_frontend) + + deploy = sub.add_parser("frontend-deploy", help="show frontend deployment output path") + deploy.set_defaults(func=deploy_frontend) + + service_status = sub.add_parser("status", help="show managed process status") + service_status.set_defaults(func=status) + + service_stop = sub.add_parser("stop", help="stop managed daemon processes") + service_stop.add_argument("target", choices=["backend", "frontend", "all"], nargs="?", default="all") + service_stop.set_defaults(func=stop) + + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + return args.func(args) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/manage.bat b/manage.bat deleted file mode 100644 index b3f7804..0000000 --- a/manage.bat +++ /dev/null @@ -1,642 +0,0 @@ -@echo off -REM CheckIn App V2 - Unified Process Manager (Windows) -REM Usage: manage.bat {start|stop|restart|status|log} [backend|frontend|all] - -chcp 65001 >nul 2>&1 - -setlocal EnableDelayedExpansion - -cd /d "%~dp0" - -set APP_DIR=%~dp0 -set VENV_DIR=%APP_DIR%venv -set BACKEND_PID_FILE=%APP_DIR%backend.pid -set FRONTEND_PID_FILE=%APP_DIR%frontend.pid -set BACKEND_LOG_FILE=%APP_DIR%logs\backend.log -set FRONTEND_LOG_FILE=%APP_DIR%logs\frontend.log -set PYTHON_EXE=%VENV_DIR%\Scripts\python.exe - -REM Parse command and target -set COMMAND=%1 -set TARGET=%2 - -REM Default target is 'all' if not specified -if "%TARGET%"=="" set TARGET=all - -if "%COMMAND%"=="" goto usage -if "%COMMAND%"=="start" goto start -if "%COMMAND%"=="stop" goto stop -if "%COMMAND%"=="restart" goto restart -if "%COMMAND%"=="status" goto status -if "%COMMAND%"=="log" goto log -if "%COMMAND%"=="build" goto build -goto usage - -REM ============================================ -REM START COMMAND -REM ============================================ -:start -if "%TARGET%"=="backend" goto start_backend -if "%TARGET%"=="frontend" goto start_frontend -if "%TARGET%"=="all" goto start_all -echo [ERROR] Invalid target: %TARGET% -goto usage - -:start_all -echo ======================================== -echo CheckIn App V2 - Starting All Services -echo ======================================== -echo. -call :start_backend_internal -echo. -call :start_frontend_internal -echo. -echo ======================================== -echo All Services Started! -echo ======================================== -echo. -echo Backend API: http://localhost:8000 -echo API Docs: http://localhost:8000/docs -echo Frontend App: http://localhost:3000 -echo. -goto end - -:start_backend -echo ======================================== -echo CheckIn App V2 - Starting Backend -echo ======================================== -echo. -call :start_backend_internal -goto end - -:start_frontend -echo ======================================== -echo CheckIn App V2 - Starting Frontend -echo ======================================== -echo. -call :start_frontend_internal -goto end - -REM --- Backend Start Logic --- -:start_backend_internal -REM Check if already running -if exist "%BACKEND_PID_FILE%" ( - set /p PID=<"%BACKEND_PID_FILE%" - tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "python.exe">NUL - if "!ERRORLEVEL!"=="0" ( - echo [WARNING] Backend is already running (PID: !PID!) - exit /b 0 - ) else ( - REM Silently clean up stale PID file (don't show message) - del "%BACKEND_PID_FILE%" >nul 2>&1 - ) -) - -REM Check virtual environment -if not exist "%VENV_DIR%" ( - echo [ERROR] Virtual environment does not exist: %VENV_DIR% - echo [INFO] Please run first: python -m venv venv - exit /b 1 -) - -REM Check required directories -if not exist "data" mkdir data -if not exist "logs" mkdir logs -if not exist "sessions" mkdir sessions - -echo [INFO] Starting backend service in background... - -REM Create a VBS script to run Python invisibly (using venv's python.exe directly) -set VBS_FILE=%TEMP%\start_backend.vbs -echo Set WshShell = CreateObject("WScript.Shell") > "%VBS_FILE%" -echo WshShell.Run """%PYTHON_EXE%"" ""%APP_DIR%run_daemon.py""", 0, False >> "%VBS_FILE%" -cscript //nologo "%VBS_FILE%" -del "%VBS_FILE%" >nul 2>&1 - -echo [INFO] Waiting for backend to start... -timeout /t 3 /nobreak >nul - -REM Check if port 8000 is listening -set SERVICE_RUNNING=0 -for /L %%i in (1,1,10) do ( - netstat -ano | findstr ":8000" | findstr "LISTENING" >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - set SERVICE_RUNNING=1 - goto :backend_port_found - ) - timeout /t 1 /nobreak >nul -) - -:backend_port_found -if "%SERVICE_RUNNING%"=="1" ( - REM Find the PID of the process listening on port 8000 - for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000" ^| findstr "LISTENING"') do ( - set LAST_PID=%%a - goto :backend_pid_found - ) - - :backend_pid_found - if defined LAST_PID ( - echo !LAST_PID! > "%BACKEND_PID_FILE%" - echo [OK] Backend started successfully (PID: !LAST_PID!) - echo API Docs: http://localhost:8000/docs - echo Log: %BACKEND_LOG_FILE% - exit /b 0 - ) -) else ( - echo [ERROR] Backend failed to start - port 8000 not listening - echo [INFO] Check log: %BACKEND_LOG_FILE% - echo. - echo [DEBUG] Last 10 lines of log: - if exist "%BACKEND_LOG_FILE%" ( - powershell -Command "$OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content '%BACKEND_LOG_FILE%' -Encoding UTF8 -Tail 10" - ) else ( - echo Log file not found - ) - exit /b 1 -) -exit /b 0 - -REM --- Frontend Start Logic --- -:start_frontend_internal -REM Check if already running -if exist "%FRONTEND_PID_FILE%" ( - set /p PID=<"%FRONTEND_PID_FILE%" - tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "node.exe">NUL - if "!ERRORLEVEL!"=="0" ( - echo [WARNING] Frontend is already running (PID: !PID!) - exit /b 0 - ) else ( - REM Silently clean up stale PID file (don't show message) - del "%FRONTEND_PID_FILE%" >nul 2>&1 - ) -) - -REM Check Node.js -where node >nul 2>nul -if %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Node.js not found - echo [INFO] Please install Node.js from https://nodejs.org/ - exit /b 1 -) - -REM Check frontend directory -if not exist "frontend" ( - echo [ERROR] Frontend directory not found - exit /b 1 -) - -REM Check node_modules -if not exist "frontend\node_modules" ( - echo [INFO] Installing frontend dependencies... - cd frontend - call npm install - cd .. -) - -echo [INFO] Starting frontend service in background... - -REM Create VBS script to run npm dev invisibly -set VBS_FILE=%TEMP%\start_frontend.vbs -echo Set WshShell = CreateObject("WScript.Shell") > "%VBS_FILE%" -echo WshShell.CurrentDirectory = "%APP_DIR%frontend" >> "%VBS_FILE%" -echo Set fso = CreateObject("Scripting.FileSystemObject") >> "%VBS_FILE%" -echo Set logFile = fso.CreateTextFile("%FRONTEND_LOG_FILE%", True) >> "%VBS_FILE%" -echo logFile.WriteLine "Frontend service starting at " ^& Now >> "%VBS_FILE%" -echo logFile.Close >> "%VBS_FILE%" -echo WshShell.Run "cmd /c npm run dev >> ""%FRONTEND_LOG_FILE%"" 2>&1", 0, False >> "%VBS_FILE%" -cscript //nologo "%VBS_FILE%" -del "%VBS_FILE%" >nul 2>&1 - -echo [INFO] Waiting for frontend to start... -timeout /t 3 /nobreak >nul - -REM Check if port 5000 is listening (Vite configured port) -set SERVICE_RUNNING=0 -for /L %%i in (1,1,10) do ( - netstat -ano | findstr ":3000" | findstr "LISTENING" >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - set SERVICE_RUNNING=1 - goto :frontend_port_found - ) - timeout /t 1 /nobreak >nul -) - -:frontend_port_found -if "%SERVICE_RUNNING%"=="1" ( - REM Find the PID of the process listening on port 3000 - for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":3000" ^| findstr "LISTENING"') do ( - set LAST_PID=%%a - goto :frontend_pid_found - ) - - :frontend_pid_found - if defined LAST_PID ( - echo !LAST_PID! > "%FRONTEND_PID_FILE%" - echo [OK] Frontend started successfully (PID: !LAST_PID!) - echo URL: http://localhost:3000 - echo Log: %FRONTEND_LOG_FILE% - exit /b 0 - ) -) else ( - echo [ERROR] Frontend failed to start - port 3000 not listening - echo [INFO] Check log: %FRONTEND_LOG_FILE% - echo. - echo [DEBUG] Last 10 lines of log: - if exist "%FRONTEND_LOG_FILE%" ( - powershell -Command "$OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content '%FRONTEND_LOG_FILE%' -Encoding UTF8 -Tail 10" - ) else ( - echo Log file not found - ) - exit /b 1 -) -exit /b 0 - -REM ============================================ -REM STOP COMMAND -REM ============================================ -:stop -if "%TARGET%"=="backend" goto stop_backend -if "%TARGET%"=="frontend" goto stop_frontend -if "%TARGET%"=="all" goto stop_all -echo [ERROR] Invalid target: %TARGET% -goto usage - -:stop_all -echo ======================================== -echo CheckIn App V2 - Stopping All Services -echo ======================================== -echo. -call :stop_backend_internal -echo. -call :stop_frontend_internal -goto end - -:stop_backend -call :stop_backend_internal -goto end - -:stop_frontend -call :stop_frontend_internal -goto end - -REM --- Backend Stop Logic --- -:stop_backend_internal -echo [INFO] Stopping backend... - -REM First try to kill by port -set BACKEND_KILLED=0 -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000" ^| findstr "LISTENING"') do ( - taskkill /F /PID %%a >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - echo [OK] Backend stopped ^(PID: %%a^) - set BACKEND_KILLED=1 - REM Delete PID file immediately after successful kill - if exist "%BACKEND_PID_FILE%" ( - del "%BACKEND_PID_FILE%" >nul 2>&1 - ) - ) -) - -REM Then try PID file if port method didn't work -if "%BACKEND_KILLED%"=="0" ( - if exist "%BACKEND_PID_FILE%" ( - set /p PID=<"%BACKEND_PID_FILE%" - tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "python.exe">NUL - if "!ERRORLEVEL!"=="0" ( - taskkill /F /T /PID !PID! >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - echo [OK] Backend stopped ^(PID: !PID!^) - set BACKEND_KILLED=1 - REM Delete PID file immediately after successful kill - del "%BACKEND_PID_FILE%" >nul 2>&1 - ) - ) else ( - REM Process doesn't exist, just clean up the stale PID file - del "%BACKEND_PID_FILE%" >nul 2>&1 - ) - ) - - REM Only show warning if nothing was stopped - if "%BACKEND_KILLED%"=="0" ( - echo [WARNING] Backend not running - ) -) - -exit /b 0 - -REM --- Frontend Stop Logic --- -:stop_frontend_internal -echo [INFO] Stopping frontend... - -REM First try to kill by port -set FRONTEND_KILLED=0 -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":3000" ^| findstr "LISTENING"') do ( - taskkill /F /T /PID %%a >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - echo [OK] Frontend stopped ^(PID: %%a^) - set FRONTEND_KILLED=1 - REM Delete PID file immediately after successful kill - if exist "%FRONTEND_PID_FILE%" ( - del "%FRONTEND_PID_FILE%" >nul 2>&1 - ) - ) -) - -REM Also check ports 5001-5010 (Vite fallback ports) -if "%FRONTEND_KILLED%"=="0" ( - for /L %%p in (3001,1,3010) do ( - for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%%p" ^| findstr "LISTENING"') do ( - REM Check if it's a node process - tasklist /FI "PID eq %%a" 2>NUL | find /I /N "node.exe">NUL - if "!ERRORLEVEL!"=="0" ( - taskkill /F /T /PID %%a >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - echo [OK] Frontend stopped ^(PID: %%a, Port: %%p^) - set FRONTEND_KILLED=1 - REM Delete PID file immediately after successful kill - if exist "%FRONTEND_PID_FILE%" ( - del "%FRONTEND_PID_FILE%" >nul 2>&1 - ) - ) - ) - ) - ) -) - -REM Then try PID file if port method didn't work -if "%FRONTEND_KILLED%"=="0" ( - if exist "%FRONTEND_PID_FILE%" ( - set /p PID=<"%FRONTEND_PID_FILE%" - tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "node.exe">NUL - if "!ERRORLEVEL!"=="0" ( - taskkill /F /T /PID !PID! >nul 2>&1 - if "!ERRORLEVEL!"=="0" ( - echo [OK] Frontend stopped ^(PID: !PID!^) - set FRONTEND_KILLED=1 - REM Delete PID file immediately after successful kill - del "%FRONTEND_PID_FILE%" >nul 2>&1 - ) - ) else ( - REM Process doesn't exist, just clean up the stale PID file - del "%FRONTEND_PID_FILE%" >nul 2>&1 - ) - ) - - REM Only show warning if nothing was stopped - if "%FRONTEND_KILLED%"=="0" ( - echo [WARNING] Frontend not running - ) -) - -exit /b 0 - -REM ============================================ -REM RESTART COMMAND -REM ============================================ -:restart -echo [INFO] Restarting %TARGET%... -echo. -call :stop -timeout /t 2 /nobreak >nul - -REM Force cleanup of any remaining PID files before starting -if "%TARGET%"=="backend" ( - del "%BACKEND_PID_FILE%" >nul 2>&1 -) -if "%TARGET%"=="frontend" ( - del "%FRONTEND_PID_FILE%" >nul 2>&1 -) -if "%TARGET%"=="all" ( - del "%BACKEND_PID_FILE%" >nul 2>&1 - del "%FRONTEND_PID_FILE%" >nul 2>&1 -) - -call :start -goto end - -REM ============================================ -REM STATUS COMMAND -REM ============================================ -:status -echo ======================================== -echo CheckIn App V2 - Service Status -echo ======================================== -echo. - -if "%TARGET%"=="backend" goto status_backend -if "%TARGET%"=="frontend" goto status_frontend -if "%TARGET%"=="all" goto status_all -echo [ERROR] Invalid target: %TARGET% -goto usage - -:status_all -call :status_backend_internal -echo. -call :status_frontend_internal -goto end - -:status_backend -call :status_backend_internal -goto end - -:status_frontend -call :status_frontend_internal -goto end - -REM --- Backend Status --- -:status_backend_internal -echo [Backend Service] - -if not exist "%BACKEND_PID_FILE%" ( - echo Status: NOT RUNNING - exit /b 0 -) - -set /p PID=<"%BACKEND_PID_FILE%" -tasklist /FI "PID eq %PID%" 2>NUL | find /I /N "python.exe">NUL -if "%ERRORLEVEL%"=="0" ( - echo Status: RUNNING - echo PID: %PID% - echo URL: http://localhost:8000/docs - echo Log: %BACKEND_LOG_FILE% - netstat -ano | findstr :8000 | findstr LISTENING -) else ( - echo Status: NOT RUNNING - del "%BACKEND_PID_FILE%" >nul 2>&1 -) -exit /b 0 - -REM --- Frontend Status --- -:status_frontend_internal -echo [Frontend Service] - -if not exist "%FRONTEND_PID_FILE%" ( - echo Status: NOT RUNNING - exit /b 0 -) - -set /p PID=<"%FRONTEND_PID_FILE%" -tasklist /FI "PID eq %PID%" 2>NUL | find /I /N "node.exe">NUL -if "%ERRORLEVEL%"=="0" ( - echo Status: RUNNING - echo PID: %PID% - echo URL: http://localhost:3000 - echo Log: %FRONTEND_LOG_FILE% - netstat -ano | findstr :3000 | findstr LISTENING -) else ( - echo Status: NOT RUNNING - del "%FRONTEND_PID_FILE%" >nul 2>&1 -) -exit /b 0 - -REM ============================================ -REM LOG COMMAND -REM ============================================ -:log -if "%TARGET%"=="backend" goto log_backend -if "%TARGET%"=="frontend" goto log_frontend -if "%TARGET%"=="all" ( - echo [ERROR] Cannot tail multiple logs simultaneously - echo [INFO] Use: manage.bat log backend OR manage.bat log frontend - goto usage -) -echo [ERROR] Invalid target: %TARGET% -goto usage - -:log_backend -echo ======================================== -echo Backend Real-time Logs (Press Ctrl+C to exit) -echo ======================================== -echo. - -if not exist "%BACKEND_LOG_FILE%" ( - echo [ERROR] Log file does not exist: %BACKEND_LOG_FILE% - exit /b 1 -) - -powershell -Command "$OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content '%BACKEND_LOG_FILE%' -Encoding UTF8 -Wait -Tail 20" -goto end - -:log_frontend -echo ======================================== -echo Frontend Real-time Logs (Press Ctrl+C to exit) -echo ======================================== -echo. - -if not exist "%FRONTEND_LOG_FILE%" ( - echo [ERROR] Log file does not exist: %FRONTEND_LOG_FILE% - exit /b 1 -) - -powershell -Command "$OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content '%FRONTEND_LOG_FILE%' -Encoding UTF8 -Wait -Tail 20" -goto end - -REM ============================================ -REM BUILD COMMAND -REM ============================================ -:build -echo ======================================== -echo CheckIn App V2 - Building Frontend -echo ======================================== -echo. - -REM Check Node.js -where node >nul 2>nul -if %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Node.js not found - echo [INFO] Please install Node.js from https://nodejs.org/ - exit /b 1 -) - -REM Check frontend directory -if not exist "frontend" ( - echo [ERROR] Frontend directory not found - exit /b 1 -) - -REM Check node_modules -if not exist "frontend\node_modules" ( - echo [INFO] Installing frontend dependencies first... - cd frontend - call npm install - if %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Failed to install dependencies - exit /b 1 - ) - cd .. - echo. -) - -echo [INFO] Building frontend for production... -echo. - -REM Build frontend -cd frontend -call npm run build -set BUILD_EXIT_CODE=%ERRORLEVEL% -cd .. - -if %BUILD_EXIT_CODE% EQU 0 ( - echo. - echo [OK] Frontend built successfully! - - REM Check if dist directory exists - if exist "frontend\dist" ( - echo. - echo Build output: - echo Location: %APP_DIR%frontend\dist - - REM Calculate directory size - for /f "tokens=3" %%a in ('dir /s "frontend\dist" ^| find "bytes"') do set DIST_SIZE=%%a - echo Files: !DIST_SIZE! bytes - - echo. - echo File structure: - dir /B frontend\dist - echo. - echo [INFO] You can now deploy the 'frontend\dist' directory to your web server - ) else ( - echo [WARNING] Build succeeded but dist directory not found - ) -) else ( - echo. - echo [ERROR] Frontend build failed - echo [INFO] Check the output above for error details - exit /b 1 -) -goto end - -REM ============================================ -REM USAGE -REM ============================================ -:usage -echo CheckIn App V2 - Unified Process Manager -echo. -echo Usage: %~nx0 COMMAND [TARGET] -echo. -echo Commands: -echo start [TARGET] - Start service(s) -echo stop [TARGET] - Stop service(s) -echo restart [TARGET] - Restart service(s) -echo status [TARGET] - View service(s) status -echo log TARGET - View real-time logs (backend or frontend only) -echo build - Build frontend for production -echo. -echo Targets: -echo backend - Backend API service (default port: 8000) -echo frontend - Frontend dev server (default port: 5000) -echo all - Both services (default) -echo. -echo Examples: -echo %~nx0 start # Start both services -echo %~nx0 start backend # Start backend only -echo %~nx0 stop all # Stop all services -echo %~nx0 status # View all services status -echo %~nx0 log backend # View backend logs -echo %~nx0 build # Build frontend static files -echo %~nx0 restart frontend # Restart frontend -exit /b 1 - -:end -exit /b 0 diff --git a/manage.sh b/manage.sh deleted file mode 100644 index bb63795..0000000 --- a/manage.sh +++ /dev/null @@ -1,848 +0,0 @@ -#!/bin/bash -# ============================================================================== -# CheckIn App V2 - Unified Service Manager (Linux/macOS) -# ============================================================================== -# Description: Manages backend and frontend services with unified interface -# Usage: ./manage.sh COMMAND [TARGET] -# Commands: start, stop, restart, status, log, build -# Targets: backend, frontend, all (default) -# ============================================================================== - -set -eu -# Enable pipefail if supported (bash 3+) -if set -o | grep -q pipefail; then - set -o pipefail -fi - -# ============================================================================== -# Configuration -# ============================================================================== -readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -readonly SCRIPT_NAME="$(basename "$0")" -readonly APP_DIR="${SCRIPT_DIR}" -readonly VENV_DIR="${APP_DIR}/venv" -readonly PYTHON_BIN="${VENV_DIR}/bin/python" - -readonly BACKEND_PID="${APP_DIR}/backend.pid" -readonly FRONTEND_PID="${APP_DIR}/frontend.pid" -readonly BACKEND_LOG="${APP_DIR}/logs/backend.log" -readonly FRONTEND_LOG="${APP_DIR}/logs/frontend.log" - -readonly BACKEND_PORT=8000 -readonly FRONTEND_PORT=3000 - -# Colors -readonly C_RESET='\033[0m' -readonly C_RED='\033[0;31m' -readonly C_GREEN='\033[0;32m' -readonly C_YELLOW='\033[1;33m' -readonly C_BLUE='\033[0;34m' -readonly C_CYAN='\033[0;36m' - -# ============================================================================== -# Utility Functions -# ============================================================================== -print_header() { - local text="$1" - printf "\n" - printf "${C_CYAN}========================================${C_RESET}\n" - printf "${C_CYAN}%s${C_RESET}\n" "$text" - printf "${C_CYAN}========================================${C_RESET}\n" -} - -log_info() { - printf "${C_GREEN}[INFO]${C_RESET} %s\n" "$1" -} - -log_success() { - printf "${C_GREEN}[OK]${C_RESET} %s\n" "$1" -} - -log_warn() { - printf "${C_YELLOW}[WARNING]${C_RESET} %s\n" "$1" -} - -log_error() { - printf "${C_RED}[ERROR]${C_RESET} %s\n" "$1" -} - -log_debug() { - printf "${C_BLUE}[DEBUG]${C_RESET} %s\n" "$1" -} - -# Check if a process is running by PID -is_process_alive() { - local pid="$1" - kill -0 "$pid" 2>/dev/null -} - -# Get PID from PID file if exists -get_pid_from_file() { - local pid_file="$1" - if [ -f "$pid_file" ]; then - cat "$pid_file" - else - echo "" - fi -} - -# Get PID listening on a port -get_pid_by_port() { - local port="$1" - local pid="" - - # Try lsof first - if command -v lsof >/dev/null 2>&1; then - pid=$(lsof -ti ":$port" 2>/dev/null | head -n1) - if [ -n "$pid" ]; then - echo "$pid" - return 0 - fi - fi - - # Fall back to netstat + ps - if command -v netstat >/dev/null 2>&1; then - local line - line=$(netstat -tlnp 2>/dev/null | grep ":$port " | head -n1) - if [ -n "$line" ]; then - pid=$(echo "$line" | awk '{print $NF}' | cut -d'/' -f1) - if [ -n "$pid" ] && [ "$pid" != "-" ]; then - echo "$pid" - return 0 - fi - fi - fi - - echo "" - return 1 -} - -# Check Node.js version (returns 0 for valid, 1 for invalid) -check_node_version() { - local node_cmd="$1" - local node_version - node_version=$($node_cmd --version 2>/dev/null | sed 's/v//') - local major_version - major_version=$(echo "$node_version" | cut -d. -f1) - - # Vite requires Node.js 20.19+ or 22.12+ - if [ "$major_version" -lt 20 ]; then - return 1 - fi - return 0 -} - -# Detect Node.js binary -find_node() { - local node_cmd="" - - if command -v node &>/dev/null; then - node_cmd="node" - elif [ -x /usr/bin/node ]; then - node_cmd="/usr/bin/node" - elif [ -x /usr/local/bin/node ]; then - node_cmd="/usr/local/bin/node" - else - return 1 - fi - - echo "$node_cmd" - return 0 -} - -# Wait for port to be listening -wait_for_port() { - local port="$1" - local max_wait="${2:-10}" - local count=0 - - while [ $count -lt $max_wait ]; do - local pid - pid=$(get_pid_by_port "$port") - if [ -n "$pid" ]; then - return 0 - fi - sleep 1 - count=$((count + 1)) - done - return 1 -} - -# ============================================================================== -# Backend Management -# ============================================================================== -start_backend() { - log_info "Starting backend service..." - - # Check if already running - local pid - pid=$(get_pid_from_file "$BACKEND_PID") - if [ -n "$pid" ] && is_process_alive "$pid"; then - log_warn "Backend already running (PID: $pid)" - return 0 - fi - - # Clean stale PID file - [ -f "$BACKEND_PID" ] && rm -f "$BACKEND_PID" - - # Verify virtual environment - if [ ! -d "$VENV_DIR" ]; then - log_error "Virtual environment not found: $VENV_DIR" - log_info "Create it with: python3 -m venv venv" - return 1 - fi - - if [ ! -x "$PYTHON_BIN" ]; then - log_error "Python executable not found: $PYTHON_BIN" - return 1 - fi - - # Create required directories - mkdir -p "${APP_DIR}/data" "${APP_DIR}/logs" "${APP_DIR}/sessions" - - # Start backend daemon - log_info "Launching backend daemon..." - nohup "$PYTHON_BIN" "${APP_DIR}/run_daemon.py" >"$BACKEND_LOG" 2>&1 & - local daemon_pid=$! - echo "$daemon_pid" >"$BACKEND_PID" - - # Wait for service to be ready - log_info "Waiting for backend to be ready..." - if wait_for_port "$BACKEND_PORT" 15; then - # Update PID with actual process on port - local actual_pid - actual_pid=$(get_pid_by_port "$BACKEND_PORT") - if [ -n "$actual_pid" ]; then - echo "$actual_pid" >"$BACKEND_PID" - log_success "Backend started (PID: $actual_pid)" - else - log_success "Backend started (PID: $daemon_pid)" - fi - printf " ${C_BLUE}API:${C_RESET} http://localhost:%d\n" "$BACKEND_PORT" - printf " ${C_BLUE}Docs:${C_RESET} http://localhost:%d/docs\n" "$BACKEND_PORT" - printf " ${C_BLUE}Log:${C_RESET} %s\n" "$BACKEND_LOG" - return 0 - else - log_error "Backend failed to start - port $BACKEND_PORT not listening" - log_info "Check logs: tail -f $BACKEND_LOG" - rm -f "$BACKEND_PID" - return 1 - fi -} - -stop_backend() { - log_info "Stopping backend..." - - local stopped=false - - # Try to stop by port first - local pid - pid=$(get_pid_by_port "$BACKEND_PORT") - if [ -n "$pid" ]; then - if kill -TERM "$pid" 2>/dev/null; then - log_success "Backend stopped (PID: $pid)" - stopped=true - fi - fi - - # Try PID file if not stopped yet - if [ "$stopped" = "false" ]; then - pid=$(get_pid_from_file "$BACKEND_PID") - if [ -n "$pid" ] && is_process_alive "$pid"; then - if kill -TERM "$pid" 2>/dev/null; then - log_success "Backend stopped (PID: $pid)" - stopped=true - fi - fi - fi - - # Cleanup PID file - rm -f "$BACKEND_PID" - - if [ "$stopped" = "false" ]; then - log_warn "Backend not running" - fi - - return 0 -} - -status_backend() { - printf "\n${C_CYAN}[Backend Service]${C_RESET}\n" - - local pid - pid=$(get_pid_from_file "$BACKEND_PID") - - if [ -z "$pid" ] || ! is_process_alive "$pid"; then - printf " Status: ${C_RED}NOT RUNNING${C_RESET}\n" - rm -f "$BACKEND_PID" - return 1 - fi - - printf " Status: ${C_GREEN}RUNNING${C_RESET}\n" - printf " PID: %s\n" "$pid" - printf " URL: http://localhost:%d\n" "$BACKEND_PORT" - printf " Docs: http://localhost:%d/docs\n" "$BACKEND_PORT" - printf " Log: %s\n" "$BACKEND_LOG" - - # Show port info if lsof available - if command -v lsof &>/dev/null; then - local port_info - port_info=$(lsof -i ":$BACKEND_PORT" 2>/dev/null | grep LISTEN || echo "N/A") - printf " Port: %s\n" "$port_info" - fi - - return 0 -} - -# ============================================================================== -# Frontend Management -# ============================================================================== -start_frontend() { - log_info "Starting frontend service..." - - # Check if already running - local pid - pid=$(get_pid_from_file "$FRONTEND_PID") - if [ -n "$pid" ] && is_process_alive "$pid"; then - log_warn "Frontend already running (PID: $pid)" - return 0 - fi - - # Clean stale PID file - [ -f "$FRONTEND_PID" ] && rm -f "$FRONTEND_PID" - - # Verify Node.js exists - local node_bin - node_bin=$(find_node) - if [ $? -ne 0 ]; then - log_error "Node.js not found" - log_info "Install from: https://nodejs.org/" - return 1 - fi - - # Check Node.js version - if ! check_node_version "$node_bin"; then - local node_version - node_version=$($node_bin --version 2>/dev/null) - log_error "Node.js version $node_version is too old" - log_error "Vite requires Node.js 20.19+ or 22.12+" - log_info "Upgrade: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -" - log_info "Then: sudo apt-get install -y nodejs" - return 1 - fi - - # Verify frontend directory - if [ ! -d "${APP_DIR}/frontend" ]; then - log_error "Frontend directory not found" - return 1 - fi - - # Install dependencies if needed - if [ ! -d "${APP_DIR}/frontend/node_modules" ]; then - log_info "Installing frontend dependencies..." - (cd "${APP_DIR}/frontend" && npm install) - fi - - # Start frontend dev server - log_info "Launching frontend dev server..." - (cd "${APP_DIR}/frontend" && nohup npm run dev >"$FRONTEND_LOG" 2>&1 & echo $! >&3) 3>"$FRONTEND_PID" - - # Read PID from file - local npm_pid - npm_pid=$(cat "$FRONTEND_PID" 2>/dev/null || echo "unknown") - - # Wait for service to be ready - log_info "Waiting for frontend to be ready..." - if wait_for_port "$FRONTEND_PORT" 15; then - # Update PID with actual process on port - local actual_pid - actual_pid=$(get_pid_by_port "$FRONTEND_PORT") - if [ -n "$actual_pid" ]; then - echo "$actual_pid" >"$FRONTEND_PID" - log_success "Frontend started (PID: $actual_pid)" - else - log_success "Frontend started (PID: $npm_pid)" - fi - printf " ${C_BLUE}URL:${C_RESET} http://localhost:%d\n" "$FRONTEND_PORT" - printf " ${C_BLUE}Log:${C_RESET} %s\n" "$FRONTEND_LOG" - return 0 - else - log_error "Frontend failed to start - port $FRONTEND_PORT not listening" - - # Show last 10 lines of log for debugging - if [ -f "$FRONTEND_LOG" ]; then - echo "" - log_warn "Last 10 lines from log:" - echo "----------------------------------------" - tail -n 10 "$FRONTEND_LOG" - echo "----------------------------------------" - fi - - log_info "Full log: tail -f $FRONTEND_LOG" - rm -f "$FRONTEND_PID" - return 1 - fi -} - -stop_frontend() { - log_info "Stopping frontend..." - - local stopped=false - - # Try common Vite ports (3000-3010) - for port in $(seq 3000 3010); do - local pid - pid=$(get_pid_by_port "$port") - if [ -n "$pid" ]; then - # Verify it's a node process - if ps -p "$pid" -o comm= 2>/dev/null | grep -q node; then - if kill -TERM "$pid" 2>/dev/null; then - log_success "Frontend stopped (PID: $pid, Port: $port)" - stopped=true - fi - fi - fi - done - - # Try PID file if not stopped yet - if [ "$stopped" = "false" ]; then - local pid - pid=$(get_pid_from_file "$FRONTEND_PID") - if [ -n "$pid" ] && is_process_alive "$pid"; then - if kill -TERM "$pid" 2>/dev/null; then - log_success "Frontend stopped (PID: $pid)" - stopped=true - fi - fi - fi - - # Cleanup PID file - rm -f "$FRONTEND_PID" - - if [ "$stopped" = "false" ]; then - log_warn "Frontend not running" - fi - - return 0 -} - -status_frontend() { - printf "\n${C_CYAN}[Frontend Service]${C_RESET}\n" - - local pid - pid=$(get_pid_from_file "$FRONTEND_PID") - - if [ -z "$pid" ] || ! is_process_alive "$pid"; then - printf " Status: ${C_RED}NOT RUNNING${C_RESET}\n" - rm -f "$FRONTEND_PID" - return 1 - fi - - printf " Status: ${C_GREEN}RUNNING${C_RESET}\n" - printf " PID: %s\n" "$pid" - printf " URL: http://localhost:%d\n" "$FRONTEND_PORT" - printf " Log: %s\n" "$FRONTEND_LOG" - - # Show port info if lsof available - if command -v lsof &>/dev/null; then - local port_info - port_info=$(lsof -i ":$FRONTEND_PORT" 2>/dev/null | grep LISTEN || echo "N/A") - printf " Port: %s\n" "$port_info" - fi - - return 0 -} - -# ============================================================================== -# Build Command -# ============================================================================== -build_frontend() { - print_header "CheckIn App V2 - Building Frontend" - - # Verify Node.js exists - local node_bin - node_bin=$(find_node) - if [ $? -ne 0 ]; then - log_error "Node.js not found" - log_info "Install from: https://nodejs.org/" - return 1 - fi - - # Check Node.js version - if ! check_node_version "$node_bin"; then - local node_version - node_version=$($node_bin --version 2>/dev/null) - log_error "Node.js version $node_version is too old" - log_error "Vite requires Node.js 20.19+ or 22.12+" - log_info "Upgrade: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -" - log_info "Then: sudo apt-get install -y nodejs" - return 1 - fi - - # Verify frontend directory - if [ ! -d "${APP_DIR}/frontend" ]; then - log_error "Frontend directory not found" - return 1 - fi - - # Install dependencies if needed - if [ ! -d "${APP_DIR}/frontend/node_modules" ]; then - log_info "Installing dependencies first..." - (cd "${APP_DIR}/frontend" && npm install) || { - log_error "Failed to install dependencies" - return 1 - } - echo - fi - - log_info "Building frontend for production..." - echo - - # Build - (cd "${APP_DIR}/frontend" && npm run build) - local exit_code=$? - - if [ $exit_code -eq 0 ]; then - echo - log_success "Frontend built successfully!" - - # Show build info - if [ -d "${APP_DIR}/frontend/dist" ]; then - local dist_size - dist_size=$(du -sh "${APP_DIR}/frontend/dist" 2>/dev/null | cut -f1 || echo "unknown") - - echo - printf "${C_CYAN}Build Output:${C_RESET}\n" - printf " Location: %s/frontend/dist\n" "$APP_DIR" - printf " Size: %s\n" "$dist_size" - echo - printf "${C_CYAN}File Structure:${C_RESET}\n" - ls -lh "${APP_DIR}/frontend/dist/" 2>/dev/null || echo " (unable to list files)" - echo - log_info "Deploy 'frontend/dist' to your web server" - else - log_warn "Build succeeded but dist directory not found" - fi - return 0 - else - echo - log_error "Frontend build failed" - log_info "Check output above for details" - return 1 - fi -} - -# ============================================================================== -# Deploy Command -# ============================================================================== -deploy_frontend() { - print_header "CheckIn App V2 - Deploying Frontend" - - local deploy_dir="/var/www/checkin-app" - - # Check if dist directory exists - log_info "Checking for build output..." - if [ ! -d "${APP_DIR}/frontend/dist" ]; then - log_error "Build output not found: ${APP_DIR}/frontend/dist" - log_info "Please build first: $SCRIPT_NAME build" - return 1 - fi - - # Check if dist directory is not empty - if [ -z "$(ls -A ${APP_DIR}/frontend/dist 2>/dev/null)" ]; then - log_error "Build output directory is empty: ${APP_DIR}/frontend/dist" - log_info "Please build first: $SCRIPT_NAME build" - return 1 - fi - - log_success "Build output found" - echo - log_info "Deploying to $deploy_dir..." - - # Create deploy directory if it doesn't exist - if [ ! -d "$deploy_dir" ]; then - log_info "Creating deployment directory..." - sudo mkdir -p "$deploy_dir" || { - log_error "Failed to create $deploy_dir" - return 1 - } - fi - - # Clear old files - log_info "Removing old files from $deploy_dir..." - sudo rm -rf "${deploy_dir:?}"/* || { - log_error "Failed to remove old files" - return 1 - } - - # Copy new files - log_info "Copying new files to $deploy_dir..." - sudo cp -r "${APP_DIR}/frontend/dist/"* "$deploy_dir/" || { - log_error "Failed to copy files" - return 1 - } - - # Set ownership and permissions - log_info "Setting ownership and permissions..." - sudo chown -R www-data:www-data "$deploy_dir" || { - log_warn "Failed to set ownership (www-data user may not exist)" - } - sudo chmod -R 755 "$deploy_dir" || { - log_error "Failed to set permissions" - return 1 - } - - # Reload Nginx if available - if command -v nginx &>/dev/null; then - log_info "Reloading Nginx..." - sudo systemctl reload nginx 2>/dev/null || sudo service nginx reload 2>/dev/null || { - log_warn "Failed to reload Nginx - you may need to reload it manually" - } - else - log_warn "Nginx not found - skipping reload" - fi - - echo - log_success "Deployment completed successfully!" - echo - printf "${C_CYAN}Deployment Info:${C_RESET}\n" - printf " Location: %s\n" "$deploy_dir" - printf " Owner: www-data:www-data\n" - printf " Perms: 755\n" - - # Show deployed files - local file_count - file_count=$(find "$deploy_dir" -type f 2>/dev/null | wc -l) - printf " Files: %s\n" "$file_count" - - local total_size - total_size=$(sudo du -sh "$deploy_dir" 2>/dev/null | cut -f1 || echo "unknown") - printf " Size: %s\n" "$total_size" - - echo - log_info "Frontend is now live at your configured Nginx URL" - - return 0 -} - -# ============================================================================== -# Command Handlers -# ============================================================================== -cmd_start() { - local target="${1:-all}" - - case "$target" in - backend) - print_header "CheckIn App V2 - Starting Backend" - start_backend - ;; - frontend) - print_header "CheckIn App V2 - Starting Frontend" - start_frontend - ;; - all) - print_header "CheckIn App V2 - Starting All Services" - echo - start_backend - echo - start_frontend - echo - printf "${C_GREEN}========================================${C_RESET}\n" - printf "${C_GREEN}All Services Started!${C_RESET}\n" - printf "${C_GREEN}========================================${C_RESET}\n" - echo - printf "Backend API: http://localhost:%d\n" "$BACKEND_PORT" - printf "API Docs: http://localhost:%d/docs\n" "$BACKEND_PORT" - printf "Frontend App: http://localhost:%d\n" "$FRONTEND_PORT" - echo - ;; - *) - log_error "Invalid target: $target" - show_usage - return 1 - ;; - esac -} - -cmd_stop() { - local target="${1:-all}" - - case "$target" in - backend) - print_header "CheckIn App V2 - Stopping Backend" - stop_backend - ;; - frontend) - print_header "CheckIn App V2 - Stopping Frontend" - stop_frontend - ;; - all) - print_header "CheckIn App V2 - Stopping All Services" - echo - stop_backend - echo - stop_frontend - ;; - *) - log_error "Invalid target: $target" - show_usage - return 1 - ;; - esac -} - -cmd_restart() { - local target="${1:-all}" - - log_info "Restarting $target..." - echo - cmd_stop "$target" - sleep 2 - cmd_start "$target" -} - -cmd_status() { - local target="${1:-all}" - - print_header "CheckIn App V2 - Service Status" - - case "$target" in - backend) - status_backend || true - ;; - frontend) - status_frontend || true - ;; - all) - status_backend || true - status_frontend || true - ;; - *) - log_error "Invalid target: $target" - show_usage - return 1 - ;; - esac - echo -} - -cmd_log() { - local target="${1:-}" - - if [ -z "$target" ] || [ "$target" = "all" ]; then - log_error "Cannot tail multiple logs simultaneously" - log_info "Use: $SCRIPT_NAME log backend OR $SCRIPT_NAME log frontend" - return 1 - fi - - case "$target" in - backend) - if [ ! -f "$BACKEND_LOG" ]; then - log_error "Log file not found: $BACKEND_LOG" - return 1 - fi - print_header "Backend Real-time Logs (Press Ctrl+C to exit)" - echo - tail -f "$BACKEND_LOG" - ;; - frontend) - if [ ! -f "$FRONTEND_LOG" ]; then - log_error "Log file not found: $FRONTEND_LOG" - return 1 - fi - print_header "Frontend Real-time Logs (Press Ctrl+C to exit)" - echo - tail -f "$FRONTEND_LOG" - ;; - *) - log_error "Invalid target: $target" - log_info "Use: backend or frontend" - return 1 - ;; - esac -} - -# ============================================================================== -# Help and Usage -# ============================================================================== -show_usage() { - echo - printf "${C_CYAN}CheckIn App V2 - Unified Service Manager${C_RESET}\n" - echo - printf "${C_YELLOW}USAGE:${C_RESET}\n" - echo " $SCRIPT_NAME COMMAND [TARGET]" - echo - printf "${C_YELLOW}COMMANDS:${C_RESET}\n" - echo " start [TARGET] - Start service(s)" - echo " stop [TARGET] - Stop service(s)" - echo " restart [TARGET] - Restart service(s)" - echo " status [TARGET] - View service status" - echo " log TARGET - View real-time logs (backend or frontend)" - echo " build - Build frontend for production" - echo " deploy - Build and deploy frontend to /var/www/checkin-app" - echo - printf "${C_YELLOW}TARGETS:${C_RESET}\n" - echo " backend - Backend API service (port $BACKEND_PORT)" - echo " frontend - Frontend dev server (port $FRONTEND_PORT)" - echo " all - Both services (default)" - echo - printf "${C_YELLOW}EXAMPLES:${C_RESET}\n" - echo " $SCRIPT_NAME start # Start both services" - echo " $SCRIPT_NAME start backend # Start backend only" - echo " $SCRIPT_NAME stop all # Stop all services" - echo " $SCRIPT_NAME status # View all service status" - echo " $SCRIPT_NAME log backend # View backend logs" - echo " $SCRIPT_NAME build # Build frontend static files" - echo " $SCRIPT_NAME deploy # Build and deploy to /var/www/checkin-app" - echo " $SCRIPT_NAME restart frontend # Restart frontend" - echo -} - -# ============================================================================== -# Main Entry Point -# ============================================================================== -main() { - local command="${1:-}" - local target="${2:-all}" - - if [ -z "$command" ]; then - show_usage - exit 1 - fi - - case "$command" in - start) - cmd_start "$target" - ;; - stop) - cmd_stop "$target" - ;; - restart) - cmd_restart "$target" - ;; - status) - cmd_status "$target" - ;; - log) - cmd_log "$target" - ;; - build) - build_frontend - ;; - deploy) - deploy_frontend - ;; - help|--help|-h) - show_usage - exit 0 - ;; - *) - log_error "Unknown command: $command" - show_usage - exit 1 - ;; - esac -} - -# Run main function -main "$@" diff --git a/run.py b/run.py deleted file mode 100644 index 8343d53..0000000 --- a/run.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -启动脚本 - 直接运行后端服务 -""" -import sys -import os -from pathlib import Path - -# 添加项目根目录到 Python 路径 -BASE_DIR = Path(__file__).resolve().parent -sys.path.insert(0, str(BASE_DIR)) -os.chdir(BASE_DIR) - -# 导入并运行 -if __name__ == "__main__": - import uvicorn - uvicorn.run( - "backend.main:app", - host="0.0.0.0", - port=8000, - reload=True, - log_level="info", - ) diff --git a/run_daemon.py b/run_daemon.py deleted file mode 100644 index 95258bf..0000000 --- a/run_daemon.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Daemon mode startup script - For background service -后台服务启动脚本 - 用于守护进程模式 -""" -import sys -import os -from pathlib import Path - -# Add project root directory to Python path -BASE_DIR = Path(__file__).resolve().parent -sys.path.insert(0, str(BASE_DIR)) -os.chdir(BASE_DIR) - -# Import and run in production mode -if __name__ == "__main__": - import uvicorn - uvicorn.run( - "backend.main:app", - host="0.0.0.0", - port=8000, - reload=False, # Disable reload for daemon mode - log_level="info", - access_log=True, - )