feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage
Major Features: - ✅ Multi-tenant architecture (tenant isolation) - ✅ Employee CRUD with lifecycle management (onboarding/offboarding) - ✅ Department tree structure with email domain management - ✅ Company info management (single-record editing) - ✅ System functions CRUD (permission management) - ✅ Email account management (multi-account per employee) - ✅ Keycloak SSO integration (auth.lab.taipei) - ✅ Redis session storage (10.1.0.254:6379) - Solves Cookie 4KB limitation - Cross-system session sharing - Sliding expiration (8 hours) - Automatic token refresh Technical Stack: Backend: - FastAPI + SQLAlchemy - PostgreSQL 16 (10.1.0.20:5433) - Keycloak Admin API integration - Docker Mailserver integration (SSH) - Alembic migrations Frontend: - Next.js 14 (App Router) - NextAuth 4 with Keycloak Provider - Redis session storage (ioredis) - Tailwind CSS Infrastructure: - Redis 7 (10.1.0.254:6379) - Session + Cache - Keycloak 26.1.0 (auth.lab.taipei) - Docker Mailserver (10.1.0.254) Architecture Highlights: - Session管理由 Keycloak + Redis 統一控制 - 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session - Token 自動刷新,異質服務整合 - 未來可無縫遷移到雲端 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
29
scripts/COPY-AND-RUN.txt
Normal file
29
scripts/COPY-AND-RUN.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
========================================
|
||||
請在 Ubuntu Server 上執行以下命令
|
||||
========================================
|
||||
|
||||
1. 打開終端機/SSH 連接到 Ubuntu Server
|
||||
2. 複製以下 3 個命令 (全選後複製)
|
||||
3. 貼到終端機並按 Enter
|
||||
|
||||
----------------------------------------
|
||||
命令開始 (請複製以下 3 行)
|
||||
----------------------------------------
|
||||
|
||||
docker stop postgres && docker rm postgres && docker run -d --name postgres --restart unless-stopped -e POSTGRES_PASSWORD="DC1qaz2wsx" -e TZ=Asia/Taipei -p 0.0.0.0:5432:5432 -v postgres-data:/var/lib/postgresql/data postgres:16
|
||||
|
||||
sleep 8 && docker exec postgres psql -U hr_user -d hr_portal -c "SELECT 'Database OK: ' || COUNT(*)::text || ' tables found' FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';"
|
||||
|
||||
docker ps | grep postgres && docker port postgres
|
||||
|
||||
----------------------------------------
|
||||
命令結束
|
||||
----------------------------------------
|
||||
|
||||
完成後,你應該會看到:
|
||||
- Database OK: 9 tables found
|
||||
- postgres 容器運行中
|
||||
- 0.0.0.0:5432->5432/tcp
|
||||
|
||||
然後回到這裡告訴我「完成」,我會測試連接!
|
||||
========================================
|
||||
252
scripts/CREATE-HR-POSTGRES.txt
Normal file
252
scripts/CREATE-HR-POSTGRES.txt
Normal file
@@ -0,0 +1,252 @@
|
||||
========================================
|
||||
建立 HR Portal 獨立 PostgreSQL 容器
|
||||
微服務架構 - 第三個資料庫容器
|
||||
========================================
|
||||
|
||||
請在 Ubuntu Server 上執行以下命令:
|
||||
|
||||
1. 登入: ssh ubuntu@10.1.0.254
|
||||
2. 複製下面所有命令並執行
|
||||
|
||||
========================================
|
||||
命令開始 (複製以下全部)
|
||||
========================================
|
||||
|
||||
# 建立 hr-postgres 容器
|
||||
docker run -d \
|
||||
--name hr-postgres \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="DC1qaz2wsx" \
|
||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:5432:5432 \
|
||||
-v hr-postgres-data:/var/lib/postgresql/data \
|
||||
--health-cmd="pg_isready -U postgres" \
|
||||
--health-interval=10s \
|
||||
--health-timeout=5s \
|
||||
--health-retries=5 \
|
||||
postgres:16-alpine
|
||||
|
||||
# 等待啟動
|
||||
echo "Waiting for PostgreSQL to start..."
|
||||
sleep 10
|
||||
|
||||
# 建立 hr_user 和 hr_portal 資料庫
|
||||
docker exec -i hr-postgres psql -U postgres <<EOF
|
||||
CREATE USER hr_user WITH PASSWORD 'DC1qaz2wsx';
|
||||
ALTER USER hr_user WITH SUPERUSER;
|
||||
CREATE DATABASE hr_portal OWNER hr_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE hr_portal TO hr_user;
|
||||
\l
|
||||
EOF
|
||||
|
||||
# 初始化 Schema
|
||||
docker exec -i hr-postgres psql -U hr_user -d hr_portal <<'SCHEMA'
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
CREATE TABLE business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER,
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門');
|
||||
|
||||
CREATE TABLE divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER,
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
first_name VARCHAR(50),
|
||||
last_name VARCHAR(50),
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
position VARCHAR(100),
|
||||
job_level VARCHAR(50),
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_employees_username ON employees(username);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
|
||||
CREATE TABLE email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(100) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 5120,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(255),
|
||||
quota_gb INTEGER DEFAULT 50,
|
||||
webdav_url VARCHAR(255),
|
||||
smb_path VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
access_level VARCHAR(50),
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(50) DEFAULT 'planning',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
role VARCHAR(100),
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
CREATE TABLE audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
table_name VARCHAR(100),
|
||||
record_id INTEGER,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS \$\$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
\$\$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT e.id, e.employee_id, e.username, e.first_name, e.last_name, e.chinese_name, e.email, e.phone, e.mobile,
|
||||
bu.name as business_unit, bu.code as business_unit_code, d.name as division, d.code as division_code,
|
||||
e.position, e.job_level, e.hire_date, e.status, e.created_at, e.updated_at
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT bu.name as business_unit, d.name as division,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON d.id = e.division_id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT p.project_code, p.project_name, p.status, e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count, p.start_date, p.end_date
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name, p.start_date, p.end_date;
|
||||
SCHEMA
|
||||
|
||||
# 驗證設定
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Verifying setup..."
|
||||
echo "=========================================="
|
||||
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "SELECT 'Tables: ' || COUNT(*)::text FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';"
|
||||
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "\dt"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Microservice Architecture Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
docker ps | grep -E "keycloak-db|gitea-db|hr-postgres"
|
||||
echo ""
|
||||
echo "Connection String:"
|
||||
echo "postgresql://hr_user:DC1qaz2wsx@10.1.0.254:5432/hr_portal"
|
||||
echo ""
|
||||
|
||||
========================================
|
||||
命令結束
|
||||
========================================
|
||||
|
||||
完成後會看到:
|
||||
✓ Tables: 9
|
||||
✓ 三個獨立的 PostgreSQL 容器運行中
|
||||
✓ Connection String
|
||||
|
||||
然後回來告訴我「完成」! 🚀
|
||||
========================================
|
||||
63
scripts/EXECUTE-FIX-POSTGRES.txt
Normal file
63
scripts/EXECUTE-FIX-POSTGRES.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
# ============================================
|
||||
# 修復 PostgreSQL 端口綁定
|
||||
# ============================================
|
||||
#
|
||||
# 複製以下命令到 Ubuntu Server 執行
|
||||
# SSH 登入: ssh ubuntu@10.1.0.254
|
||||
#
|
||||
# ============================================
|
||||
|
||||
# 步驟 1: 檢查現有配置
|
||||
echo "Current PostgreSQL configuration:"
|
||||
docker ps | grep postgres
|
||||
docker port postgres
|
||||
|
||||
# 步驟 2: 獲取 postgres 密碼 (如果不知道,設定一個新的)
|
||||
# 例如: POSTGRES_PASSWORD="yourpassword"
|
||||
POSTGRES_PASSWORD="DC1qaz2wsx"
|
||||
|
||||
# 步驟 3: 檢查資料卷
|
||||
VOLUME_NAME=$(docker inspect postgres --format '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}' 2>/dev/null || echo "postgres-data")
|
||||
echo "Using volume: $VOLUME_NAME"
|
||||
|
||||
# 步驟 4: 停止並移除舊容器 (資料不會遺失,在 volume 中)
|
||||
docker stop postgres
|
||||
docker rm postgres
|
||||
|
||||
# 步驟 5: 重新啟動,綁定到所有網路介面 (0.0.0.0:5432)
|
||||
docker run -d \
|
||||
--name postgres \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
|
||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --locale=en_US.UTF-8" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:5432:5432 \
|
||||
-v ${VOLUME_NAME}:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
# 步驟 6: 等待啟動
|
||||
echo "Waiting for PostgreSQL to start..."
|
||||
sleep 5
|
||||
|
||||
# 步驟 7: 驗證
|
||||
echo ""
|
||||
echo "Verification:"
|
||||
docker ps | grep postgres
|
||||
docker port postgres
|
||||
docker exec postgres pg_isready -U postgres
|
||||
|
||||
# 步驟 8: 檢查資料庫
|
||||
echo ""
|
||||
echo "Databases:"
|
||||
docker exec postgres psql -U postgres -lqt | cut -d \| -f 1 | grep -v "^$"
|
||||
|
||||
echo ""
|
||||
echo "Testing hr_portal database:"
|
||||
docker exec postgres psql -U hr_user -d hr_portal -c "\dt"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " PostgreSQL is now accessible!"
|
||||
echo "=========================================="
|
||||
echo "Connection: postgresql://hr_user:DC1qaz2wsx@10.1.0.254:5432/hr_portal"
|
||||
echo ""
|
||||
210
scripts/EXECUTE-THIS.txt
Normal file
210
scripts/EXECUTE-THIS.txt
Normal file
@@ -0,0 +1,210 @@
|
||||
# ============================================
|
||||
# 複製以下所有內容到 Ubuntu Server 執行
|
||||
# ============================================
|
||||
#
|
||||
# 步驟:
|
||||
# 1. SSH 登入: ssh ubuntu@10.1.0.254
|
||||
# 2. 執行: bash
|
||||
# 3. 複製貼上以下全部內容並按 Enter
|
||||
# 4. 等待完成
|
||||
#
|
||||
# ============================================
|
||||
|
||||
docker exec -i postgres psql -U postgres <<'EOF'
|
||||
DROP USER IF EXISTS hr_user CASCADE;
|
||||
CREATE USER hr_user WITH PASSWORD 'DC1qaz2wsx';
|
||||
ALTER USER hr_user WITH SUPERUSER;
|
||||
|
||||
DROP DATABASE IF EXISTS hr_portal;
|
||||
CREATE DATABASE hr_portal OWNER hr_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE hr_portal TO hr_user;
|
||||
EOF
|
||||
|
||||
docker exec -i postgres psql -U hr_user -d hr_portal <<'EOF'
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
CREATE TABLE business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER,
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門');
|
||||
|
||||
CREATE TABLE divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER,
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
first_name VARCHAR(50),
|
||||
last_name VARCHAR(50),
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
position VARCHAR(100),
|
||||
job_level VARCHAR(50),
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_employees_username ON employees(username);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
|
||||
CREATE TABLE email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(100) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 5120,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(255),
|
||||
quota_gb INTEGER DEFAULT 50,
|
||||
webdav_url VARCHAR(255),
|
||||
smb_path VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
access_level VARCHAR(50),
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(50) DEFAULT 'planning',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
role VARCHAR(100),
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
CREATE TABLE audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
table_name VARCHAR(100),
|
||||
record_id INTEGER,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT e.id, e.employee_id, e.username, e.first_name, e.last_name, e.chinese_name, e.email, e.phone, e.mobile,
|
||||
bu.name as business_unit, bu.code as business_unit_code, d.name as division, d.code as division_code,
|
||||
e.position, e.job_level, e.hire_date, e.status, e.created_at, e.updated_at
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT bu.name as business_unit, d.name as division,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON d.id = e.division_id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT p.project_code, p.project_name, p.status, e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count, p.start_date, p.end_date
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name, p.start_date, p.end_date;
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✓ Database Setup Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Verification:"
|
||||
docker exec -i postgres psql -U hr_user -d hr_portal -c "\dt"
|
||||
echo ""
|
||||
echo "Connection String:"
|
||||
echo "postgresql://hr_user:DC1qaz2wsx@10.1.0.254:5432/hr_portal"
|
||||
echo ""
|
||||
70
scripts/INSERT-TEST-DATA.txt
Normal file
70
scripts/INSERT-TEST-DATA.txt
Normal file
@@ -0,0 +1,70 @@
|
||||
========================================
|
||||
插入測試資料到 hr_portal 資料庫
|
||||
========================================
|
||||
|
||||
在 Ubuntu Server 上複製並執行以下命令:
|
||||
|
||||
========================================
|
||||
命令開始 (複製以下全部)
|
||||
========================================
|
||||
|
||||
docker exec -i hr-postgres psql -U hr_user -d hr_portal <<'SQL'
|
||||
-- 插入測試員工
|
||||
INSERT INTO employees (employee_id, username, first_name, last_name, chinese_name, email, mobile, business_unit_id, position, job_level, hire_date, status) VALUES
|
||||
('E0001', 'porsche.chen', 'Porsche', 'Chen', '陳博駿', 'porsche.chen@porscheworld.tw', '0912-345-678', 4, 'CTO / 技術長', 'C-Level', '2020-01-01', 'active'),
|
||||
('E1001', 'alice.wang', 'Alice', 'Wang', '王小華', 'alice.wang@lab.taipei', '0922-111-222', 3, 'Technical Director', 'Director', '2020-06-01', 'active'),
|
||||
('E1002', 'bob.chen', 'Bob', 'Chen', '陳大明', 'bob.chen@lab.taipei', '0933-222-333', 3, 'Senior Software Engineer', 'Senior', '2021-03-15', 'active'),
|
||||
('E2001', 'charlie.lin', 'Charlie', 'Lin', '林小風', 'charlie.lin@ease.taipei', '0944-333-444', 1, 'Wind Energy Consultant', 'Senior', '2021-08-01', 'active'),
|
||||
('E3001', 'diana.wu', 'Diana', 'Wu', '吳小綠', 'diana.wu@ease.taipei', '0955-444-555', 2, 'Carbon Credit Specialist', 'Staff', '2022-01-10', 'active');
|
||||
|
||||
-- 插入郵件帳號
|
||||
INSERT INTO email_accounts (employee_id, email_address, mailbox_quota_mb, is_active)
|
||||
SELECT e.id, e.email, CASE e.job_level WHEN 'C-Level' THEN 20480 WHEN 'Director' THEN 10240 ELSE 5120 END, true FROM employees e;
|
||||
|
||||
-- 插入網路硬碟
|
||||
INSERT INTO network_drives (employee_id, drive_name, drive_path, quota_gb, webdav_url, smb_path, is_active)
|
||||
SELECT e.id, e.username || '_personal', '/nas/users/' || e.username, CASE e.job_level WHEN 'C-Level' THEN 500 WHEN 'Director' THEN 200 WHEN 'Senior' THEN 80 ELSE 50 END, 'https://nas.porscheworld.tw/webdav/' || e.username, '//10.1.0.30/homes/' || e.username, true FROM employees e;
|
||||
|
||||
-- 插入系統權限
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'HR Portal', CASE WHEN e.job_level IN ('C-Level', 'Director') THEN 'admin' ELSE 'user' END, true, 'Initial access' FROM employees e;
|
||||
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'Gitea', 'user', true, 'Git access' FROM employees e WHERE e.business_unit_id = 3;
|
||||
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
VALUES ((SELECT id FROM employees WHERE username = 'porsche.chen'), 'Portainer', 'admin', true, 'Docker management');
|
||||
|
||||
-- 插入專案
|
||||
INSERT INTO projects (project_code, project_name, description, project_manager_id, start_date, status) VALUES
|
||||
('WIND-2024-001', '離岸風電技術評估', '台中港離岸風電場技術可行性評估', (SELECT id FROM employees WHERE username = 'charlie.lin'), '2024-01-15', 'in_progress'),
|
||||
('CARBON-2024-001', '企業碳盤查輔導', '協助製造業進行 ISO 14064-1 碳盤查', (SELECT id FROM employees WHERE username = 'diana.wu'), '2024-02-01', 'in_progress'),
|
||||
('AI-2024-001', 'HR Portal 系統開發', '開發整合 Keycloak 的人資管理系統', (SELECT id FROM employees WHERE username = 'alice.wang'), '2024-01-01', 'in_progress');
|
||||
|
||||
-- 插入專案成員
|
||||
INSERT INTO project_members (project_id, employee_id, role) VALUES
|
||||
((SELECT id FROM projects WHERE project_code = 'WIND-2024-001'), (SELECT id FROM employees WHERE username = 'charlie.lin'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'CARBON-2024-001'), (SELECT id FROM employees WHERE username = 'diana.wu'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'alice.wang'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'bob.chen'), 'Lead Developer'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'porsche.chen'), 'Technical Advisor');
|
||||
|
||||
-- 驗證
|
||||
SELECT '=== 完成! ===' as status, (SELECT COUNT(*) FROM employees) as employees, (SELECT COUNT(*) FROM email_accounts) as emails, (SELECT COUNT(*) FROM network_drives) as drives, (SELECT COUNT(*) FROM system_permissions WHERE is_active = true) as permissions, (SELECT COUNT(*) FROM projects) as projects;
|
||||
|
||||
SELECT employee_id, username, chinese_name, position, job_level FROM employees ORDER BY employee_id;
|
||||
SQL
|
||||
|
||||
========================================
|
||||
命令結束
|
||||
========================================
|
||||
|
||||
執行完成後會看到:
|
||||
- 5 個員工
|
||||
- 5 個郵件帳號
|
||||
- 5 個網路硬碟
|
||||
- 8 個系統權限
|
||||
- 3 個專案
|
||||
|
||||
然後回來告訴我「完成」!
|
||||
========================================
|
||||
81
scripts/ONE-TIME-SETUP.txt
Normal file
81
scripts/ONE-TIME-SETUP.txt
Normal file
@@ -0,0 +1,81 @@
|
||||
========================================
|
||||
HR Portal 一鍵完成腳本
|
||||
在 Ubuntu Server (10.1.0.254) 執行
|
||||
========================================
|
||||
|
||||
用 PuTTY 登入: porsche@10.1.0.254
|
||||
複製以下所有命令並執行:
|
||||
|
||||
========================================
|
||||
開始執行
|
||||
========================================
|
||||
|
||||
echo "=========================================="
|
||||
echo " HR Portal 資料庫初始化"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 插入測試資料
|
||||
docker exec -i hr-postgres psql -U hr_user -d hr_portal <<'SQLEND'
|
||||
INSERT INTO employees (employee_id, username, first_name, last_name, chinese_name, email, mobile, business_unit_id, position, job_level, hire_date, status) VALUES
|
||||
('E0001', 'porsche.chen', 'Porsche', 'Chen', '陳博駿', 'porsche.chen@porscheworld.tw', '0912-345-678', 4, 'CTO / 技術長', 'C-Level', '2020-01-01', 'active'),
|
||||
('E1001', 'alice.wang', 'Alice', 'Wang', '王小華', 'alice.wang@lab.taipei', '0922-111-222', 3, 'Technical Director', 'Director', '2020-06-01', 'active'),
|
||||
('E1002', 'bob.chen', 'Bob', 'Chen', '陳大明', 'bob.chen@lab.taipei', '0933-222-333', 3, 'Senior Software Engineer', 'Senior', '2021-03-15', 'active'),
|
||||
('E2001', 'charlie.lin', 'Charlie', 'Lin', '林小風', 'charlie.lin@ease.taipei', '0944-333-444', 1, 'Wind Energy Consultant', 'Senior', '2021-08-01', 'active'),
|
||||
('E3001', 'diana.wu', 'Diana', 'Wu', '吳小綠', 'diana.wu@ease.taipei', '0955-444-555', 2, 'Carbon Credit Specialist', 'Staff', '2022-01-10', 'active');
|
||||
|
||||
INSERT INTO email_accounts (employee_id, email_address, mailbox_quota_mb, is_active)
|
||||
SELECT e.id, e.email, CASE e.job_level WHEN 'C-Level' THEN 20480 WHEN 'Director' THEN 10240 ELSE 5120 END, true FROM employees e;
|
||||
|
||||
INSERT INTO network_drives (employee_id, drive_name, drive_path, quota_gb, webdav_url, smb_path, is_active)
|
||||
SELECT e.id, e.username || '_personal', '/nas/users/' || e.username, CASE e.job_level WHEN 'C-Level' THEN 500 WHEN 'Director' THEN 200 WHEN 'Senior' THEN 80 ELSE 50 END, 'https://nas.porscheworld.tw/webdav/' || e.username, '//10.1.0.30/homes/' || e.username, true FROM employees e;
|
||||
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'HR Portal', CASE WHEN e.job_level IN ('C-Level', 'Director') THEN 'admin' ELSE 'user' END, true, 'Initial access' FROM employees e;
|
||||
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'Gitea', 'user', true, 'Git access' FROM employees e WHERE e.business_unit_id = 3;
|
||||
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
VALUES ((SELECT id FROM employees WHERE username = 'porsche.chen'), 'Portainer', 'admin', true, 'Docker management');
|
||||
|
||||
INSERT INTO projects (project_code, project_name, description, project_manager_id, start_date, status) VALUES
|
||||
('WIND-2024-001', '離岸風電技術評估', '台中港離岸風電場技術可行性評估', (SELECT id FROM employees WHERE username = 'charlie.lin'), '2024-01-15', 'in_progress'),
|
||||
('CARBON-2024-001', '企業碳盤查輔導', '協助製造業進行 ISO 14064-1 碳盤查', (SELECT id FROM employees WHERE username = 'diana.wu'), '2024-02-01', 'in_progress'),
|
||||
('AI-2024-001', 'HR Portal 系統開發', '開發整合 Keycloak 的人資管理系統', (SELECT id FROM employees WHERE username = 'alice.wang'), '2024-01-01', 'in_progress');
|
||||
|
||||
INSERT INTO project_members (project_id, employee_id, role) VALUES
|
||||
((SELECT id FROM projects WHERE project_code = 'WIND-2024-001'), (SELECT id FROM employees WHERE username = 'charlie.lin'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'CARBON-2024-001'), (SELECT id FROM employees WHERE username = 'diana.wu'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'alice.wang'), 'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'bob.chen'), 'Lead Developer'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'porsche.chen'), 'Technical Advisor');
|
||||
SQLEND
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 驗證結果"
|
||||
echo "=========================================="
|
||||
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "SELECT COUNT(*) as employees FROM employees;"
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "SELECT COUNT(*) as email_accounts FROM email_accounts;"
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "SELECT COUNT(*) as network_drives FROM network_drives;"
|
||||
docker exec hr-postgres psql -U hr_user -d hr_portal -c "SELECT COUNT(*) as projects FROM projects;"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 完成!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "測試資料已插入:"
|
||||
echo " - 5 個員工"
|
||||
echo " - 5 個郵件帳號"
|
||||
echo " - 5 個網路硬碟"
|
||||
echo " - 3 個專案"
|
||||
echo ""
|
||||
echo "請回到 Windows 告訴 Claude '完成'"
|
||||
echo ""
|
||||
|
||||
========================================
|
||||
執行結束
|
||||
========================================
|
||||
11
scripts/QUICK-INSERT.txt
Normal file
11
scripts/QUICK-INSERT.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
========================================
|
||||
快速插入測試資料 (一行命令)
|
||||
========================================
|
||||
|
||||
複製以下完整命令,在 Ubuntu Server 執行:
|
||||
|
||||
docker exec -i hr-postgres psql -U hr_user -d hr_portal <<'ENDOFSQL'
|
||||
INSERT INTO employees (employee_id, username, first_name, last_name, chinese_name, email, mobile, business_unit_id, position, job_level, hire_date, status) VALUES ('E0001', 'porsche.chen', 'Porsche', 'Chen', '陳博駿', 'porsche.chen@porscheworld.tw', '0912-345-678', 4, 'CTO / 技術長', 'C-Level', '2020-01-01', 'active'), ('E1001', 'alice.wang', 'Alice', 'Wang', '王小華', 'alice.wang@lab.taipei', '0922-111-222', 3, 'Technical Director', 'Director', '2020-06-01', 'active'), ('E1002', 'bob.chen', 'Bob', 'Chen', '陳大明', 'bob.chen@lab.taipei', '0933-222-333', 3, 'Senior Software Engineer', 'Senior', '2021-03-15', 'active'), ('E2001', 'charlie.lin', 'Charlie', 'Lin', '林小風', 'charlie.lin@ease.taipei', '0944-333-444', 1, 'Wind Energy Consultant', 'Senior', '2021-08-01', 'active'), ('E3001', 'diana.wu', 'Diana', 'Wu', '吳小綠', 'diana.wu@ease.taipei', '0955-444-555', 2, 'Carbon Credit Specialist', 'Staff', '2022-01-10', 'active'); INSERT INTO email_accounts (employee_id, email_address, mailbox_quota_mb, is_active) SELECT e.id, e.email, CASE e.job_level WHEN 'C-Level' THEN 20480 WHEN 'Director' THEN 10240 ELSE 5120 END, true FROM employees e; INSERT INTO network_drives (employee_id, drive_name, drive_path, quota_gb, webdav_url, smb_path, is_active) SELECT e.id, e.username || '_personal', '/nas/users/' || e.username, CASE e.job_level WHEN 'C-Level' THEN 500 WHEN 'Director' THEN 200 WHEN 'Senior' THEN 80 ELSE 50 END, 'https://nas.porscheworld.tw/webdav/' || e.username, '//10.1.0.30/homes/' || e.username, true FROM employees e; INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes) SELECT e.id, 'HR Portal', CASE WHEN e.job_level IN ('C-Level', 'Director') THEN 'admin' ELSE 'user' END, true, 'Initial access' FROM employees e; INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes) SELECT e.id, 'Gitea', 'user', true, 'Git access' FROM employees e WHERE e.business_unit_id = 3; INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes) VALUES ((SELECT id FROM employees WHERE username = 'porsche.chen'), 'Portainer', 'admin', true, 'Docker management'); INSERT INTO projects (project_code, project_name, description, project_manager_id, start_date, status) VALUES ('WIND-2024-001', '離岸風電技術評估', '台中港離岸風電場技術可行性評估', (SELECT id FROM employees WHERE username = 'charlie.lin'), '2024-01-15', 'in_progress'), ('CARBON-2024-001', '企業碳盤查輔導', '協助製造業進行 ISO 14064-1 碳盤查', (SELECT id FROM employees WHERE username = 'diana.wu'), '2024-02-01', 'in_progress'), ('AI-2024-001', 'HR Portal 系統開發', '開發整合 Keycloak 的人資管理系統', (SELECT id FROM employees WHERE username = 'alice.wang'), '2024-01-01', 'in_progress'); INSERT INTO project_members (project_id, employee_id, role) VALUES ((SELECT id FROM projects WHERE project_code = 'WIND-2024-001'), (SELECT id FROM employees WHERE username = 'charlie.lin'), 'Project Manager'), ((SELECT id FROM projects WHERE project_code = 'CARBON-2024-001'), (SELECT id FROM employees WHERE username = 'diana.wu'), 'Project Manager'), ((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'alice.wang'), 'Project Manager'), ((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'bob.chen'), 'Lead Developer'), ((SELECT id FROM projects WHERE project_code = 'AI-2024-001'), (SELECT id FROM employees WHERE username = 'porsche.chen'), 'Technical Advisor'); SELECT '插入完成!' as status; SELECT COUNT(*) || ' 個員工' FROM employees; SELECT COUNT(*) || ' 個郵件帳號' FROM email_accounts; SELECT COUNT(*) || ' 個網路硬碟' FROM network_drives; SELECT COUNT(*) || ' 個系統權限' FROM system_permissions WHERE is_active = true; SELECT COUNT(*) || ' 個專案' FROM projects; SELECT '--- 員工列表 ---' as info; SELECT employee_id, username, chinese_name, position FROM employees ORDER BY employee_id;
|
||||
ENDOFSQL
|
||||
|
||||
========================================
|
||||
212
scripts/SETUP-INSTRUCTIONS.md
Normal file
212
scripts/SETUP-INSTRUCTIONS.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# HR Portal 資料庫設定指南
|
||||
|
||||
## 🎯 目標
|
||||
在 Ubuntu Server (10.1.0.254) 上建立 HR Portal 資料庫
|
||||
|
||||
## 📋 方案選擇
|
||||
|
||||
### ⭐ 方案 A: 一鍵執行 (推薦)
|
||||
|
||||
**步驟 1: 登入 Ubuntu Server**
|
||||
```bash
|
||||
ssh ubuntu@10.1.0.254
|
||||
```
|
||||
|
||||
**步驟 2: 下載並執行安裝腳本**
|
||||
```bash
|
||||
# 建立工作目錄
|
||||
mkdir -p ~/hr-portal-setup && cd ~/hr-portal-setup
|
||||
|
||||
# 執行設定腳本 (會自動從 Windows 複製檔案)
|
||||
bash <(cat <<'SETUP_SCRIPT'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo " HR Portal Database Setup"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 配置
|
||||
DB_NAME="hr_portal"
|
||||
DB_USER="hr_user"
|
||||
POSTGRES_CONTAINER="postgres"
|
||||
|
||||
# 提示輸入密碼
|
||||
read -sp "Enter password for 'hr_user': " DB_PASSWORD
|
||||
echo ""
|
||||
|
||||
# 檢查 PostgreSQL
|
||||
echo "[1/5] Checking PostgreSQL container..."
|
||||
docker ps | grep postgres
|
||||
|
||||
# 建立用戶
|
||||
echo ""
|
||||
echo "[2/5] Creating database user..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
ALTER USER $DB_USER WITH SUPERUSER;
|
||||
SQL
|
||||
|
||||
# 建立資料庫
|
||||
echo ""
|
||||
echo "[3/5] Creating database..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
SQL
|
||||
|
||||
# 下載 Schema
|
||||
echo ""
|
||||
echo "[4/5] Downloading schema..."
|
||||
wget -q -O init-db.sql "https://raw.githubusercontent.com/.../init-db.sql" 2>/dev/null || \
|
||||
curl -s -o init-db.sql "https://..." 2>/dev/null || \
|
||||
echo "Please upload init-db.sql manually"
|
||||
|
||||
# 初始化 Schema
|
||||
echo ""
|
||||
echo "[5/5] Initializing schema..."
|
||||
if [ -f init-db.sql ]; then
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME < init-db.sql
|
||||
echo "✓ Schema initialized"
|
||||
else
|
||||
echo "⚠ Please run: docker exec -i postgres psql -U hr_user -d hr_portal < init-db.sql"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Setup Complete!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Connection String:"
|
||||
echo "postgresql://$DB_USER:$DB_PASSWORD@10.1.0.254:5432/$DB_NAME"
|
||||
echo ""
|
||||
|
||||
SETUP_SCRIPT
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📝 方案 B: 手動逐步執行
|
||||
|
||||
登入 Ubuntu Server 後執行:
|
||||
|
||||
#### 1. 檢查 PostgreSQL 容器
|
||||
```bash
|
||||
docker ps -a | grep postgres
|
||||
```
|
||||
|
||||
#### 2. 建立資料庫用戶
|
||||
```bash
|
||||
docker exec -i postgres psql -U postgres <<EOF
|
||||
CREATE USER hr_user WITH PASSWORD 'your_password_here';
|
||||
ALTER USER hr_user WITH SUPERUSER;
|
||||
EOF
|
||||
```
|
||||
|
||||
#### 3. 建立資料庫
|
||||
```bash
|
||||
docker exec -i postgres psql -U postgres <<EOF
|
||||
CREATE DATABASE hr_portal OWNER hr_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE hr_portal TO hr_user;
|
||||
EOF
|
||||
```
|
||||
|
||||
#### 4. 上傳 SQL 檔案到 Ubuntu Server
|
||||
|
||||
在 **Windows PowerShell** 執行:
|
||||
```powershell
|
||||
scp W:\DevOps-Workspace\hr-portal\scripts\init-db.sql ubuntu@10.1.0.254:/tmp/
|
||||
```
|
||||
|
||||
#### 5. 初始化 Schema
|
||||
|
||||
回到 **Ubuntu Server** 執行:
|
||||
```bash
|
||||
docker exec -i postgres psql -U hr_user -d hr_portal < /tmp/init-db.sql
|
||||
```
|
||||
|
||||
#### 6. 驗證設定
|
||||
```bash
|
||||
docker exec -i postgres psql -U hr_user -d hr_portal -c "\dt"
|
||||
```
|
||||
|
||||
應該看到 9 個資料表:
|
||||
- audit_logs
|
||||
- business_units
|
||||
- divisions
|
||||
- email_accounts
|
||||
- employees
|
||||
- network_drives
|
||||
- project_members
|
||||
- projects
|
||||
- system_permissions
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成後
|
||||
|
||||
### 更新後端配置
|
||||
|
||||
編輯 `W:\DevOps-Workspace\hr-portal\backend\.env`:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://hr_user:your_password@10.1.0.254:5432/hr_portal
|
||||
```
|
||||
|
||||
### 插入測試資料 (可選)
|
||||
|
||||
```bash
|
||||
# 上傳測試資料
|
||||
scp W:\DevOps-Workspace\hr-portal\scripts\insert-test-data.sql ubuntu@10.1.0.254:/tmp/
|
||||
|
||||
# 執行插入
|
||||
ssh ubuntu@10.1.0.254 "docker exec -i postgres psql -U hr_user -d hr_portal < /tmp/insert-test-data.sql"
|
||||
```
|
||||
|
||||
### 驗證資料
|
||||
```bash
|
||||
ssh ubuntu@10.1.0.254 "docker exec -i postgres psql -U hr_user -d hr_portal -c 'SELECT * FROM employees;'"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 常見問題
|
||||
|
||||
### Q: 資料庫已存在怎麼辦?
|
||||
```bash
|
||||
# 刪除舊資料庫
|
||||
docker exec postgres psql -U postgres -c "DROP DATABASE hr_portal;"
|
||||
# 重新建立
|
||||
docker exec postgres psql -U postgres -c "CREATE DATABASE hr_portal OWNER hr_user;"
|
||||
```
|
||||
|
||||
### Q: 用戶已存在?
|
||||
```bash
|
||||
# 刪除用戶
|
||||
docker exec postgres psql -U postgres -c "DROP USER hr_user;"
|
||||
# 重新建立
|
||||
docker exec postgres psql -U postgres -c "CREATE USER hr_user WITH PASSWORD 'new_password';"
|
||||
```
|
||||
|
||||
### Q: 無法連接 PostgreSQL?
|
||||
```bash
|
||||
# 檢查容器狀態
|
||||
docker ps | grep postgres
|
||||
|
||||
# 檢查端口
|
||||
sudo netstat -tlnp | grep 5432
|
||||
|
||||
# 重啟容器
|
||||
docker restart postgres
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 需要協助?
|
||||
|
||||
如遇到問題,請提供以下資訊:
|
||||
1. 執行的命令
|
||||
2. 錯誤訊息
|
||||
3. Docker 容器狀態 (`docker ps`)
|
||||
43
scripts/SETUP-SSH-KEY-EASY.txt
Normal file
43
scripts/SETUP-SSH-KEY-EASY.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
========================================
|
||||
SSH 免密碼登入設定 (超簡單版)
|
||||
========================================
|
||||
|
||||
只需要 3 步驟,1 分鐘完成!
|
||||
|
||||
========================================
|
||||
步驟 1: 在 Windows PowerShell 執行
|
||||
========================================
|
||||
|
||||
# 檢查是否已有 SSH 金鑰
|
||||
if (Test-Path "$env:USERPROFILE\.ssh\id_rsa.pub") {
|
||||
Write-Host "SSH 金鑰已存在!" -ForegroundColor Green
|
||||
Get-Content "$env:USERPROFILE\.ssh\id_rsa.pub"
|
||||
} else {
|
||||
Write-Host "正在生成 SSH 金鑰..." -ForegroundColor Yellow
|
||||
ssh-keygen -t rsa -b 4096 -f "$env:USERPROFILE\.ssh\id_rsa" -N ""
|
||||
Write-Host "金鑰生成完成!" -ForegroundColor Green
|
||||
Get-Content "$env:USERPROFILE\.ssh\id_rsa.pub"
|
||||
}
|
||||
|
||||
========================================
|
||||
步驟 2: 上傳公鑰到 Ubuntu Server
|
||||
========================================
|
||||
|
||||
# 執行這一行命令 (需要輸入密碼最後一次)
|
||||
type $env:USERPROFILE\.ssh\id_rsa.pub | ssh ubuntu@10.1.0.254 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && echo '========== SSH 金鑰設定完成! =========='"
|
||||
|
||||
========================================
|
||||
步驟 3: 測試免密碼登入
|
||||
========================================
|
||||
|
||||
# 測試 (應該不用輸入密碼)
|
||||
ssh ubuntu@10.1.0.254 "echo '免密碼登入成功! 🎉'"
|
||||
|
||||
========================================
|
||||
完成!
|
||||
========================================
|
||||
|
||||
之後所有操作都可以全自動執行了!
|
||||
|
||||
回來告訴我「設定完成」,我就能自動幫你執行所有任務! 🚀
|
||||
========================================
|
||||
251
scripts/auto-setup.sh
Normal file
251
scripts/auto-setup.sh
Normal file
@@ -0,0 +1,251 @@
|
||||
#!/bin/bash
|
||||
# Auto setup with password
|
||||
set -e
|
||||
|
||||
DB_NAME="hr_portal"
|
||||
DB_USER="hr_user"
|
||||
DB_PASSWORD="DC1qaz2wsx"
|
||||
POSTGRES_CONTAINER="postgres"
|
||||
|
||||
echo "========================================"
|
||||
echo " HR Portal Database Auto Setup"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Check PostgreSQL
|
||||
echo "[1/5] Checking PostgreSQL..."
|
||||
docker ps | grep postgres
|
||||
|
||||
# Create user
|
||||
echo ""
|
||||
echo "[2/5] Creating user..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
DROP USER IF EXISTS $DB_USER CASCADE;
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
ALTER USER $DB_USER WITH SUPERUSER;
|
||||
SQL
|
||||
|
||||
# Create database
|
||||
echo ""
|
||||
echo "[3/5] Creating database..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
DROP DATABASE IF EXISTS $DB_NAME;
|
||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
SQL
|
||||
|
||||
# Create schema
|
||||
echo ""
|
||||
echo "[4/5] Creating schema..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME <<'SCHEMA'
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER,
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER,
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
first_name VARCHAR(50),
|
||||
last_name VARCHAR(50),
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
position VARCHAR(100),
|
||||
job_level VARCHAR(50),
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_employees_username ON employees(username);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(100) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 5120,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(255),
|
||||
quota_gb INTEGER DEFAULT 50,
|
||||
webdav_url VARCHAR(255),
|
||||
smb_path VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
access_level VARCHAR(50),
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(50) DEFAULT 'planning',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
role VARCHAR(100),
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
table_name VARCHAR(100),
|
||||
record_id INTEGER,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS \$\$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
\$\$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT
|
||||
e.id, e.employee_id, e.username, e.first_name, e.last_name, e.chinese_name,
|
||||
e.email, e.phone, e.mobile,
|
||||
bu.name as business_unit, bu.code as business_unit_code,
|
||||
d.name as division, d.code as division_code,
|
||||
e.position, e.job_level, e.hire_date, e.status, e.created_at, e.updated_at
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT
|
||||
bu.name as business_unit, d.name as division,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON d.id = e.division_id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT
|
||||
p.project_code, p.project_name, p.status,
|
||||
e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count,
|
||||
p.start_date, p.end_date
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name, p.start_date, p.end_date;
|
||||
SCHEMA
|
||||
|
||||
# Verify
|
||||
echo ""
|
||||
echo "[5/5] Verifying..."
|
||||
TABLE_COUNT=$(docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';")
|
||||
echo "Tables created: $TABLE_COUNT / 9"
|
||||
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -c "\dt"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Setup Complete!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Connection String:"
|
||||
echo "postgresql://$DB_USER:$DB_PASSWORD@10.1.0.254:5432/$DB_NAME"
|
||||
echo ""
|
||||
102
scripts/check-postgres.ps1
Normal file
102
scripts/check-postgres.ps1
Normal file
@@ -0,0 +1,102 @@
|
||||
# PostgreSQL Connection Check Script
|
||||
# This script checks if PostgreSQL is accessible and ready for HR Portal setup
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " PostgreSQL Connection Check" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Configuration
|
||||
$POSTGRES_HOST = "10.1.0.254"
|
||||
$POSTGRES_PORT = 5432
|
||||
|
||||
# Step 1: Test Network Connectivity
|
||||
Write-Host "[1/4] Testing network connectivity..." -ForegroundColor Yellow
|
||||
try {
|
||||
$tcpResult = Test-NetConnection -ComputerName $POSTGRES_HOST -Port $POSTGRES_PORT -WarningAction SilentlyContinue
|
||||
|
||||
if ($tcpResult.TcpTestSucceeded) {
|
||||
Write-Host " [OK] Port $POSTGRES_PORT is accessible on $POSTGRES_HOST" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Cannot reach port $POSTGRES_PORT on $POSTGRES_HOST" -ForegroundColor Red
|
||||
Write-Host " Please check if PostgreSQL container is running" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] Network test failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 2: Check Docker Containers
|
||||
Write-Host "[2/4] Checking Docker containers on remote host..." -ForegroundColor Yellow
|
||||
try {
|
||||
$containers = ssh ubuntu@$POSTGRES_HOST "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' | grep postgres"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] PostgreSQL containers found:" -ForegroundColor Green
|
||||
Write-Host " $containers" -ForegroundColor White
|
||||
} else {
|
||||
Write-Host " [WARNING] No PostgreSQL containers found or SSH failed" -ForegroundColor Yellow
|
||||
Write-Host " This is not critical if you can connect to database" -ForegroundColor Gray
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [WARNING] Could not check containers: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 3: Test Database Connection
|
||||
Write-Host "[3/4] Testing database connection..." -ForegroundColor Yellow
|
||||
Write-Host " Please enter postgres password when prompted" -ForegroundColor Gray
|
||||
|
||||
try {
|
||||
# Test connection to postgres default database
|
||||
$testQuery = "SELECT version();"
|
||||
$result = ssh ubuntu@$POSTGRES_HOST "docker exec -i postgres psql -U postgres -c `"$testQuery`"" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Successfully connected to PostgreSQL" -ForegroundColor Green
|
||||
Write-Host " Version info:" -ForegroundColor Gray
|
||||
Write-Host " $result" -ForegroundColor White
|
||||
} else {
|
||||
Write-Host " [FAIL] Could not connect to PostgreSQL" -ForegroundColor Red
|
||||
Write-Host " Error: $result" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] Connection test failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 4: Check if hr_portal database exists
|
||||
Write-Host "[4/4] Checking for existing hr_portal database..." -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
$dbCheck = ssh ubuntu@$POSTGRES_HOST "docker exec -i postgres psql -U postgres -lqt | cut -d '|' -f 1 | grep -w hr_portal" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [INFO] hr_portal database already exists" -ForegroundColor Yellow
|
||||
Write-Host " You may want to drop it before running setup-db-simple.ps1" -ForegroundColor Gray
|
||||
Write-Host " Command: docker exec postgres psql -U postgres -c 'DROP DATABASE hr_portal;'" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host " [OK] hr_portal database does not exist (ready for setup)" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [INFO] Could not check database: $($_.Exception.Message)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Check completed!" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Run setup-db-simple.ps1 to create hr_portal database" -ForegroundColor White
|
||||
Write-Host " 2. Execute test data insertion if needed" -ForegroundColor White
|
||||
Write-Host ""
|
||||
68
scripts/check-resources.sh
Normal file
68
scripts/check-resources.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# 檢查系統資源使用狀況
|
||||
#
|
||||
|
||||
echo "=========================================="
|
||||
echo " System Resource Check"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# CPU 資訊
|
||||
echo "[1/5] CPU Information:"
|
||||
lscpu | grep -E "^CPU\(s\)|^Model name|^Thread"
|
||||
echo ""
|
||||
|
||||
# 記憶體使用
|
||||
echo "[2/5] Memory Usage:"
|
||||
free -h
|
||||
echo ""
|
||||
|
||||
# 磁碟使用
|
||||
echo "[3/5] Disk Usage:"
|
||||
df -h | grep -E "^Filesystem|/$|/data"
|
||||
echo ""
|
||||
|
||||
# Docker 資源使用
|
||||
echo "[4/5] Docker Resource Usage:"
|
||||
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
|
||||
echo ""
|
||||
|
||||
# PostgreSQL 容器資源
|
||||
echo "[5/5] PostgreSQL Containers:"
|
||||
docker ps --filter "ancestor=postgres:16-alpine" --format "table {{.Names}}\t{{.Status}}\t{{.Size}}"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo " Resource Summary"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 計算 PostgreSQL 容器數量和總記憶體
|
||||
PG_COUNT=$(docker ps | grep postgres | wc -l)
|
||||
echo "Current PostgreSQL containers: $PG_COUNT"
|
||||
echo ""
|
||||
|
||||
# 預估資源需求
|
||||
echo "Estimated resource requirements:"
|
||||
echo ""
|
||||
echo "Option A: Shared PostgreSQL (1 container)"
|
||||
echo " ├── CPU: ~2-5%"
|
||||
echo " ├── Memory: ~100-200 MB"
|
||||
echo " └── Disk: ~500 MB"
|
||||
echo ""
|
||||
echo "Option B: Microservices (3 containers)"
|
||||
echo " ├── CPU: ~6-15%"
|
||||
echo " ├── Memory: ~300-600 MB"
|
||||
echo " └── Disk: ~1.5 GB"
|
||||
echo ""
|
||||
|
||||
# 系統規格回顧
|
||||
echo "Your Ubuntu Server specs:"
|
||||
echo " ├── CPU: 12th Gen Intel Core i5-12400F"
|
||||
echo " ├── RAM: 32 GB"
|
||||
echo " └── Storage: SSD 216GB + HDD 931GB"
|
||||
echo ""
|
||||
echo "Recommendation: You have MORE than enough resources!"
|
||||
echo " Both options are viable."
|
||||
echo ""
|
||||
396
scripts/complete-setup.sh
Normal file
396
scripts/complete-setup.sh
Normal file
@@ -0,0 +1,396 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# HR Portal Complete Database Setup
|
||||
# 完整的一鍵設定腳本 - 包含所有 SQL
|
||||
#
|
||||
# 使用方法:
|
||||
# 1. 複製此腳本內容
|
||||
# 2. SSH 登入 Ubuntu Server: ssh ubuntu@10.1.0.254
|
||||
# 3. 建立檔案: nano ~/setup-hr-portal.sh
|
||||
# 4. 貼上內容並儲存 (Ctrl+X, Y, Enter)
|
||||
# 5. 執行: bash ~/setup-hr-portal.sh
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# 顏色定義
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${CYAN}========================================"
|
||||
echo " HR Portal Database Setup"
|
||||
echo "========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# 配置
|
||||
DB_NAME="hr_portal"
|
||||
DB_USER="hr_user"
|
||||
POSTGRES_CONTAINER="postgres"
|
||||
|
||||
# 提示輸入密碼
|
||||
echo -e "${YELLOW}Please enter database password:${NC}"
|
||||
read -sp "Password for 'hr_user': " DB_PASSWORD
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
if [ -z "$DB_PASSWORD" ]; then
|
||||
echo -e "${RED}Password cannot be empty!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ====================================
|
||||
# Step 1: 檢查 PostgreSQL
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[1/6] Checking PostgreSQL container...${NC}"
|
||||
|
||||
if docker ps | grep -q "$POSTGRES_CONTAINER"; then
|
||||
echo -e "${GREEN}✓ PostgreSQL container is running${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ PostgreSQL container not found${NC}"
|
||||
echo "Available containers:"
|
||||
docker ps -a | grep -i postgres || echo "No postgres containers"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ====================================
|
||||
# Step 2: 建立資料庫用戶
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[2/6] Creating database user...${NC}"
|
||||
|
||||
# 檢查用戶是否存在
|
||||
USER_EXISTS=$(docker exec -i $POSTGRES_CONTAINER psql -U postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" 2>&1 || echo "0")
|
||||
|
||||
if echo "$USER_EXISTS" | grep -q "1"; then
|
||||
echo -e "${YELLOW}⚠ User '$DB_USER' already exists${NC}"
|
||||
read -p "Drop and recreate? (yes/no): " RECREATE_USER
|
||||
if [ "$RECREATE_USER" = "yes" ]; then
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres -c "DROP USER IF EXISTS $DB_USER CASCADE;"
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
ALTER USER $DB_USER WITH SUPERUSER;
|
||||
SQL
|
||||
echo -e "${GREEN}✓ User recreated${NC}"
|
||||
fi
|
||||
else
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
ALTER USER $DB_USER WITH SUPERUSER;
|
||||
SQL
|
||||
echo -e "${GREEN}✓ User '$DB_USER' created${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ====================================
|
||||
# Step 3: 建立資料庫
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[3/6] Creating database...${NC}"
|
||||
|
||||
# 檢查資料庫是否存在
|
||||
DB_EXISTS=$(docker exec -i $POSTGRES_CONTAINER psql -U postgres -lqt | cut -d \| -f 1 | grep -w "$DB_NAME" | wc -l)
|
||||
|
||||
if [ "$DB_EXISTS" -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠ Database '$DB_NAME' already exists${NC}"
|
||||
read -p "Drop and recreate? (yes/no): " RECREATE_DB
|
||||
if [ "$RECREATE_DB" = "yes" ]; then
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres -c "DROP DATABASE IF EXISTS $DB_NAME;"
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
SQL
|
||||
echo -e "${GREEN}✓ Database recreated${NC}"
|
||||
fi
|
||||
else
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<SQL
|
||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
SQL
|
||||
echo -e "${GREEN}✓ Database '$DB_NAME' created${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ====================================
|
||||
# Step 4: 建立 Schema (內嵌 SQL)
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[4/6] Creating database schema...${NC}"
|
||||
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME <<'SCHEMA_SQL'
|
||||
-- Set timezone
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
-- Business Units
|
||||
CREATE TABLE IF NOT EXISTS business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER,
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- Divisions
|
||||
CREATE TABLE IF NOT EXISTS divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER,
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Employees
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
first_name VARCHAR(50),
|
||||
last_name VARCHAR(50),
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
position VARCHAR(100),
|
||||
job_level VARCHAR(50),
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_employees_username ON employees(username);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
|
||||
-- Email Accounts
|
||||
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(100) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 5120,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Network Drives
|
||||
CREATE TABLE IF NOT EXISTS network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(255),
|
||||
quota_gb INTEGER DEFAULT 50,
|
||||
webdav_url VARCHAR(255),
|
||||
smb_path VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- System Permissions
|
||||
CREATE TABLE IF NOT EXISTS system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
access_level VARCHAR(50),
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
-- Projects
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(50) DEFAULT 'planning',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Project Members
|
||||
CREATE TABLE IF NOT EXISTS project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
role VARCHAR(100),
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
-- Audit Logs
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
table_name VARCHAR(100),
|
||||
record_id INTEGER,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
-- Triggers
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Views
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT
|
||||
e.id,
|
||||
e.employee_id,
|
||||
e.username,
|
||||
e.first_name,
|
||||
e.last_name,
|
||||
e.chinese_name,
|
||||
e.email,
|
||||
e.phone,
|
||||
e.mobile,
|
||||
bu.name as business_unit,
|
||||
bu.code as business_unit_code,
|
||||
d.name as division,
|
||||
d.code as division_code,
|
||||
e.position,
|
||||
e.job_level,
|
||||
e.hire_date,
|
||||
e.status,
|
||||
e.created_at,
|
||||
e.updated_at
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT
|
||||
bu.name as business_unit,
|
||||
d.name as division,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON d.id = e.division_id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
p.status,
|
||||
e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count,
|
||||
p.start_date,
|
||||
p.end_date
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name, p.start_date, p.end_date;
|
||||
|
||||
SCHEMA_SQL
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Schema created successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Failed to create schema${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ====================================
|
||||
# Step 5: 驗證設定
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[5/6] Verifying setup...${NC}"
|
||||
|
||||
TABLE_COUNT=$(docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';")
|
||||
|
||||
echo " Tables created: $TABLE_COUNT / 9"
|
||||
|
||||
if [ "$TABLE_COUNT" -ge 9 ]; then
|
||||
echo -e "${GREEN}✓ All tables created${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Warning: Expected 9 tables, found $TABLE_COUNT${NC}"
|
||||
fi
|
||||
|
||||
# 列出資料表
|
||||
echo ""
|
||||
echo "Tables in database:"
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -c "\dt" | head -20
|
||||
echo ""
|
||||
|
||||
# ====================================
|
||||
# Step 6: 輸出連接字串
|
||||
# ====================================
|
||||
echo -e "${YELLOW}[6/6] Setup complete!${NC}"
|
||||
echo ""
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${GREEN} Database Ready!${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Connection String:${NC}"
|
||||
echo -e "${CYAN}postgresql://$DB_USER:$DB_PASSWORD@10.1.0.254:5432/$DB_NAME${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Update your backend/.env file:${NC}"
|
||||
echo "DATABASE_URL=postgresql://$DB_USER:$DB_PASSWORD@10.1.0.254:5432/$DB_NAME"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo " 1. Copy the connection string above"
|
||||
echo " 2. Update W:\DevOps-Workspace\hr-portal\backend\.env"
|
||||
echo " 3. (Optional) Insert test data using insert-test-data.sql"
|
||||
echo " 4. Start the backend service"
|
||||
echo ""
|
||||
123
scripts/fix-postgres-port.sh
Normal file
123
scripts/fix-postgres-port.sh
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# 修復 PostgreSQL 端口綁定
|
||||
# 讓 PostgreSQL 可以從外部訪問
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo " Fix PostgreSQL Port Binding"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 1. 檢查現有 PostgreSQL 容器
|
||||
echo "[1/5] Checking existing PostgreSQL container..."
|
||||
docker ps -a | grep postgres
|
||||
|
||||
echo ""
|
||||
echo "Current port bindings:"
|
||||
docker port postgres 2>/dev/null || echo "No port bindings found"
|
||||
|
||||
echo ""
|
||||
read -p "Continue to reconfigure PostgreSQL? (yes/no): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "yes" ]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 2. 備份現有配置
|
||||
echo "[2/5] Getting container information..."
|
||||
|
||||
# Get current postgres password
|
||||
POSTGRES_PASSWORD=$(docker exec postgres env | grep POSTGRES_PASSWORD | cut -d= -f2)
|
||||
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||
read -sp "Enter postgres superuser password: " POSTGRES_PASSWORD
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check for data volume
|
||||
VOLUME_NAME=$(docker inspect postgres --format '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}')
|
||||
if [ -z "$VOLUME_NAME" ]; then
|
||||
VOLUME_NAME="postgres-data"
|
||||
echo " Using default volume name: $VOLUME_NAME"
|
||||
else
|
||||
echo " Found existing volume: $VOLUME_NAME"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 3. 停止並移除舊容器
|
||||
echo "[3/5] Stopping and removing old container..."
|
||||
docker stop postgres
|
||||
docker rm postgres
|
||||
|
||||
echo " ✓ Old container removed"
|
||||
echo ""
|
||||
|
||||
# 4. 重新啟動容器,綁定到所有網路介面
|
||||
echo "[4/5] Starting new PostgreSQL container..."
|
||||
|
||||
docker run -d \
|
||||
--name postgres \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
|
||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --locale=en_US.UTF-8" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:5432:5432 \
|
||||
-v ${VOLUME_NAME}:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
echo " ✓ New container started"
|
||||
echo ""
|
||||
|
||||
# 5. 等待 PostgreSQL 啟動
|
||||
echo "[5/5] Waiting for PostgreSQL to be ready..."
|
||||
sleep 5
|
||||
|
||||
for i in {1..30}; do
|
||||
if docker exec postgres pg_isready -U postgres >/dev/null 2>&1; then
|
||||
echo " ✓ PostgreSQL is ready!"
|
||||
break
|
||||
fi
|
||||
echo " Waiting... ($i/30)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 驗證配置
|
||||
echo "Verifying configuration..."
|
||||
echo ""
|
||||
echo "Port bindings:"
|
||||
docker port postgres
|
||||
|
||||
echo ""
|
||||
echo "Testing local connection:"
|
||||
docker exec postgres psql -U postgres -c "SELECT version();" | head -3
|
||||
|
||||
echo ""
|
||||
echo "Database list:"
|
||||
docker exec postgres psql -U postgres -lqt | cut -d \| -f 1 | grep -v "^$" | grep -v "template"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✓ Configuration Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "PostgreSQL is now accessible from external hosts"
|
||||
echo ""
|
||||
echo "Connection details:"
|
||||
echo " Host: 10.1.0.254"
|
||||
echo " Port: 5432"
|
||||
echo " Databases: postgres, hr_portal (if created)"
|
||||
echo ""
|
||||
echo "Test from Windows:"
|
||||
echo " psql -h 10.1.0.254 -U hr_user -d hr_portal"
|
||||
echo ""
|
||||
echo "Or update backend/.env:"
|
||||
echo " DATABASE_URL=postgresql://hr_user:DC1qaz2wsx@10.1.0.254:5432/hr_portal"
|
||||
echo ""
|
||||
60
scripts/fix-postgres-remote.sh
Normal file
60
scripts/fix-postgres-remote.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# Fix PostgreSQL port binding - Remote execution script
|
||||
|
||||
POSTGRES_PASSWORD="DC1qaz2wsx"
|
||||
|
||||
echo "=========================================="
|
||||
echo " Reconfiguring PostgreSQL Container"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
echo "[1/4] Stopping old container..."
|
||||
docker stop postgres
|
||||
docker rm postgres
|
||||
|
||||
echo ""
|
||||
echo "[2/4] Starting new container with external access..."
|
||||
docker run -d \
|
||||
--name postgres \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
|
||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --locale=en_US.UTF-8" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:5432:5432 \
|
||||
-v postgres-data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
echo ""
|
||||
echo "[3/4] Waiting for PostgreSQL to be ready..."
|
||||
sleep 5
|
||||
|
||||
for i in {1..20}; do
|
||||
if docker exec postgres pg_isready -U postgres >/dev/null 2>&1; then
|
||||
echo " PostgreSQL is ready!"
|
||||
break
|
||||
fi
|
||||
echo " Waiting... ($i/20)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[4/4] Verifying configuration..."
|
||||
echo ""
|
||||
echo "Container status:"
|
||||
docker ps | grep postgres
|
||||
|
||||
echo ""
|
||||
echo "Port binding:"
|
||||
docker port postgres
|
||||
|
||||
echo ""
|
||||
echo "Database check:"
|
||||
docker exec postgres psql -U hr_user -d hr_portal -c "SELECT COUNT(*) as table_count FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Configuration Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "PostgreSQL is now accessible from 10.1.0.254:5432"
|
||||
echo ""
|
||||
378
scripts/init-db.sql
Normal file
378
scripts/init-db.sql
Normal file
@@ -0,0 +1,378 @@
|
||||
-- ====================================
|
||||
-- HR Portal Database Schema
|
||||
-- ====================================
|
||||
-- 創建時間: 2026-02-08
|
||||
-- 資料庫: PostgreSQL 16
|
||||
-- ====================================
|
||||
|
||||
-- 設定時區
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
-- ====================================
|
||||
-- 1. 事業部表 (Business Units)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER, -- 將在建立 employees 表後設定外鍵
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 初始資料
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- ====================================
|
||||
-- 2. 部門表 (Divisions)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER, -- 將在建立 employees 表後設定外鍵
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 初始資料 - 玄鐵風能授權服務
|
||||
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
||||
(1, 'wind-licensing', '技術授權部', 'Technical Licensing', 'wind-licensing@ease.taipei'),
|
||||
(1, 'wind-assessment', '風場評估部', 'Wind Assessment', 'wind-assessment@ease.taipei'),
|
||||
(1, 'wind-service', '客戶服務部', 'Customer Service', 'wind-service@ease.taipei')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- 國際碳權申請服務
|
||||
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
||||
(2, 'carbon-apply', '碳權申請部', 'Carbon Application', 'carbon-apply@ease.taipei'),
|
||||
(2, 'carbon-audit', '碳盤查部', 'Carbon Audit', 'carbon-audit@ease.taipei'),
|
||||
(2, 'carbon-trade', '碳交易部', 'Carbon Trading', 'carbon-trade@ease.taipei')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- 智能研發服務
|
||||
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
||||
(3, 'software-dev', '軟體研發部', 'Software Development', 'software@lab.taipei'),
|
||||
(3, 'hardware-dev', '硬體研發部', 'Hardware Development', 'hardware@lab.taipei'),
|
||||
(3, 'product-mgmt', '產品管理部', 'Product Management', 'product@lab.taipei')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- 管理部門
|
||||
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
||||
(4, 'hr', '人力資源部', 'Human Resources', 'hr@porscheworld.tw'),
|
||||
(4, 'finance', '財務部', 'Finance', 'finance@porscheworld.tw'),
|
||||
(4, 'admin', '行政部', 'Administration', 'admin@porscheworld.tw'),
|
||||
(4, 'it', '資訊部', 'Information Technology', 'it@porscheworld.tw')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- ====================================
|
||||
-- 3. 員工表 (Employees)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
|
||||
-- 基本資料
|
||||
first_name VARCHAR(50) NOT NULL,
|
||||
last_name VARCHAR(50) NOT NULL,
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
|
||||
-- 任職資訊
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
team VARCHAR(100),
|
||||
position VARCHAR(100),
|
||||
job_title VARCHAR(100),
|
||||
job_level VARCHAR(20), -- C-Level, VP, Director, Manager, Senior, Staff, etc.
|
||||
employment_type VARCHAR(20) DEFAULT 'full-time', -- full-time, part-time, contractor, intern
|
||||
|
||||
-- 日期
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
|
||||
-- 狀態
|
||||
status VARCHAR(20) DEFAULT 'active', -- active, inactive, suspended, terminated
|
||||
|
||||
-- 審計
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(100),
|
||||
updated_by VARCHAR(100)
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
CREATE INDEX idx_employees_business_unit ON employees(business_unit_id);
|
||||
CREATE INDEX idx_employees_division ON employees(division_id);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
|
||||
-- 新增外鍵到 business_units 和 divisions
|
||||
ALTER TABLE business_units ADD CONSTRAINT fk_business_unit_manager
|
||||
FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE divisions ADD CONSTRAINT fk_division_manager
|
||||
FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL;
|
||||
|
||||
-- ====================================
|
||||
-- 4. 郵件帳號表 (Email Accounts)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(255) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 1024,
|
||||
mailbox_used_mb INTEGER DEFAULT 0,
|
||||
|
||||
-- 郵件設定
|
||||
forward_to VARCHAR(255),
|
||||
auto_reply BOOLEAN DEFAULT FALSE,
|
||||
auto_reply_message TEXT,
|
||||
|
||||
-- 狀態
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
-- 審計
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_email_accounts_employee ON email_accounts(employee_id);
|
||||
CREATE INDEX idx_email_accounts_email ON email_accounts(email_address);
|
||||
|
||||
-- ====================================
|
||||
-- 5. 網路硬碟表 (Network Drives)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
|
||||
-- 硬碟資訊
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(500) NOT NULL,
|
||||
quota_gb INTEGER DEFAULT 10,
|
||||
used_gb DECIMAL(10,2) DEFAULT 0,
|
||||
|
||||
-- 存取設定
|
||||
webdav_url VARCHAR(500),
|
||||
smb_path VARCHAR(500),
|
||||
|
||||
-- 權限
|
||||
can_share BOOLEAN DEFAULT FALSE,
|
||||
|
||||
-- 狀態
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
-- 審計
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_network_drives_employee ON network_drives(employee_id);
|
||||
|
||||
-- ====================================
|
||||
-- 6. 系統權限表 (System Permissions)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
system_url VARCHAR(500),
|
||||
access_level VARCHAR(50), -- admin, user, readonly
|
||||
|
||||
-- 狀態
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
granted_by VARCHAR(100),
|
||||
revoked_at TIMESTAMP,
|
||||
revoked_by VARCHAR(100),
|
||||
|
||||
UNIQUE(employee_id, system_name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_system_permissions_employee ON system_permissions(employee_id);
|
||||
CREATE INDEX idx_system_permissions_system ON system_permissions(system_name);
|
||||
|
||||
-- ====================================
|
||||
-- 7. 專案表 (Projects)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
client_name VARCHAR(200),
|
||||
|
||||
-- 專案狀態
|
||||
status VARCHAR(20) DEFAULT 'planning', -- planning, active, on-hold, completed, cancelled
|
||||
|
||||
-- 專案經理
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
|
||||
-- 時間
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
actual_end_date DATE,
|
||||
|
||||
-- 預算
|
||||
budget_amount DECIMAL(15,2),
|
||||
budget_currency VARCHAR(10) DEFAULT 'TWD',
|
||||
|
||||
-- 專案空間
|
||||
nas_path VARCHAR(500),
|
||||
git_repo VARCHAR(200),
|
||||
|
||||
-- 審計
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_projects_business_unit ON projects(business_unit_id);
|
||||
CREATE INDEX idx_projects_status ON projects(status);
|
||||
CREATE INDEX idx_projects_manager ON projects(project_manager_id);
|
||||
|
||||
-- ====================================
|
||||
-- 8. 專案成員表 (Project Members)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
|
||||
role VARCHAR(50), -- project-manager, developer, consultant, support
|
||||
allocation_percentage INTEGER DEFAULT 100, -- 0-100
|
||||
|
||||
joined_date DATE DEFAULT CURRENT_DATE,
|
||||
left_date DATE,
|
||||
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_project_members_project ON project_members(project_id);
|
||||
CREATE INDEX idx_project_members_employee ON project_members(employee_id);
|
||||
|
||||
-- ====================================
|
||||
-- 9. 審計日誌表 (Audit Logs)
|
||||
-- ====================================
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
|
||||
action VARCHAR(50) NOT NULL, -- create, update, delete, login, logout
|
||||
resource_type VARCHAR(50), -- employee, email, drive, project
|
||||
resource_id INTEGER,
|
||||
|
||||
old_value JSONB,
|
||||
new_value JSONB,
|
||||
|
||||
ip_address VARCHAR(50),
|
||||
user_agent TEXT,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at DESC);
|
||||
|
||||
-- ====================================
|
||||
-- 10. 更新時間觸發器函數
|
||||
-- ====================================
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- 為需要的表新增觸發器
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ====================================
|
||||
-- 11. 建立視圖 (Views)
|
||||
-- ====================================
|
||||
|
||||
-- 員工完整資訊視圖
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT
|
||||
e.*,
|
||||
bu.name as business_unit_name,
|
||||
bu.name_en as business_unit_name_en,
|
||||
d.name as division_name,
|
||||
d.name_en as division_name_en
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
-- 專案人力統計視圖
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT
|
||||
p.id,
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
p.status,
|
||||
COUNT(pm.id) as member_count,
|
||||
SUM(pm.allocation_percentage) as total_allocation
|
||||
FROM projects p
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.left_date IS NULL
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status;
|
||||
|
||||
-- 部門人力統計視圖
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT
|
||||
bu.name as business_unit_name,
|
||||
d.name as division_name,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON e.division_id = d.id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
-- ====================================
|
||||
-- 完成
|
||||
-- ====================================
|
||||
-- 授予權限 (依實際用戶名調整)
|
||||
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO hr_user;
|
||||
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO hr_user;
|
||||
|
||||
SELECT 'HR Portal Database Schema 初始化完成!' as message;
|
||||
275
scripts/insert-test-data-fixed.sql
Normal file
275
scripts/insert-test-data-fixed.sql
Normal file
@@ -0,0 +1,275 @@
|
||||
-- ====================================
|
||||
-- HR Portal - 測試資料 (修正版)
|
||||
-- ====================================
|
||||
|
||||
-- 插入測試員工
|
||||
INSERT INTO employees (
|
||||
employee_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name,
|
||||
chinese_name,
|
||||
email,
|
||||
mobile,
|
||||
business_unit_id,
|
||||
position,
|
||||
job_level,
|
||||
hire_date,
|
||||
status
|
||||
) VALUES
|
||||
-- 管理層
|
||||
(
|
||||
'E0001',
|
||||
'porsche.chen',
|
||||
'Porsche',
|
||||
'Chen',
|
||||
'陳博駿',
|
||||
'porsche.chen@porscheworld.tw',
|
||||
'0912-345-678',
|
||||
4, -- 管理部門
|
||||
'CTO / 技術長',
|
||||
'C-Level',
|
||||
'2020-01-01',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 智能研發服務事業部
|
||||
(
|
||||
'E1001',
|
||||
'alice.wang',
|
||||
'Alice',
|
||||
'Wang',
|
||||
'王小華',
|
||||
'alice.wang@lab.taipei',
|
||||
'0922-111-222',
|
||||
3, -- 智能研發
|
||||
'Technical Director',
|
||||
'Director',
|
||||
'2020-06-01',
|
||||
'active'
|
||||
),
|
||||
|
||||
(
|
||||
'E1002',
|
||||
'bob.chen',
|
||||
'Bob',
|
||||
'Chen',
|
||||
'陳大明',
|
||||
'bob.chen@lab.taipei',
|
||||
'0933-222-333',
|
||||
3, -- 智能研發
|
||||
'Senior Software Engineer',
|
||||
'Senior',
|
||||
'2021-03-15',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 玄鐵風能授權服務事業部
|
||||
(
|
||||
'E2001',
|
||||
'charlie.lin',
|
||||
'Charlie',
|
||||
'Lin',
|
||||
'林小風',
|
||||
'charlie.lin@ease.taipei',
|
||||
'0944-333-444',
|
||||
1, -- 玄鐵風能
|
||||
'Wind Energy Consultant',
|
||||
'Senior',
|
||||
'2021-08-01',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 國際碳權申請服務事業部
|
||||
(
|
||||
'E3001',
|
||||
'diana.wu',
|
||||
'Diana',
|
||||
'Wu',
|
||||
'吳小綠',
|
||||
'diana.wu@ease.taipei',
|
||||
'0955-444-555',
|
||||
2, -- 國際碳權
|
||||
'Carbon Credit Specialist',
|
||||
'Staff',
|
||||
'2022-01-10',
|
||||
'active'
|
||||
);
|
||||
|
||||
-- 插入郵件帳號
|
||||
INSERT INTO email_accounts (employee_id, email_address, mailbox_quota_mb, is_active)
|
||||
SELECT
|
||||
e.id,
|
||||
e.email,
|
||||
CASE e.job_level
|
||||
WHEN 'C-Level' THEN 20480
|
||||
WHEN 'Director' THEN 10240
|
||||
WHEN 'Manager' THEN 10240
|
||||
WHEN 'Senior' THEN 5120
|
||||
ELSE 5120
|
||||
END as quota,
|
||||
true
|
||||
FROM employees e;
|
||||
|
||||
-- 插入網路硬碟
|
||||
INSERT INTO network_drives (
|
||||
employee_id,
|
||||
drive_name,
|
||||
drive_path,
|
||||
quota_gb,
|
||||
webdav_url,
|
||||
smb_path,
|
||||
is_active
|
||||
)
|
||||
SELECT
|
||||
e.id,
|
||||
e.username || '_personal',
|
||||
'/nas/users/' || e.username,
|
||||
CASE e.job_level
|
||||
WHEN 'C-Level' THEN 500
|
||||
WHEN 'Director' THEN 200
|
||||
WHEN 'Manager' THEN 100
|
||||
WHEN 'Senior' THEN 80
|
||||
ELSE 50
|
||||
END as quota,
|
||||
'https://nas.porscheworld.tw/webdav/' || e.username,
|
||||
'//10.1.0.30/homes/' || e.username,
|
||||
true
|
||||
FROM employees e;
|
||||
|
||||
-- 插入系統權限
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'HR Portal',
|
||||
CASE
|
||||
WHEN e.job_level IN ('C-Level', 'Director') THEN 'admin'
|
||||
WHEN e.job_level = 'Manager' THEN 'manager'
|
||||
ELSE 'user'
|
||||
END,
|
||||
true,
|
||||
'Initial access granted'
|
||||
FROM employees e;
|
||||
|
||||
-- 額外權限: Gitea
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
SELECT e.id, 'Gitea', 'user', true, 'Git repository access'
|
||||
FROM employees e
|
||||
WHERE e.business_unit_id = 3; -- 智能研發事業部
|
||||
|
||||
-- 額外權限: Portainer (給 IT 人員)
|
||||
INSERT INTO system_permissions (employee_id, system_name, access_level, is_active, notes)
|
||||
VALUES
|
||||
((SELECT id FROM employees WHERE username = 'porsche.chen'), 'Portainer', 'admin', true, 'Docker management');
|
||||
|
||||
-- 插入測試專案
|
||||
INSERT INTO projects (project_code, project_name, description, project_manager_id, start_date, status)
|
||||
VALUES
|
||||
(
|
||||
'WIND-2024-001',
|
||||
'離岸風電技術評估',
|
||||
'台中港離岸風電場技術可行性評估',
|
||||
(SELECT id FROM employees WHERE username = 'charlie.lin'),
|
||||
'2024-01-15',
|
||||
'in_progress'
|
||||
),
|
||||
(
|
||||
'CARBON-2024-001',
|
||||
'企業碳盤查輔導',
|
||||
'協助製造業進行 ISO 14064-1 碳盤查',
|
||||
(SELECT id FROM employees WHERE username = 'diana.wu'),
|
||||
'2024-02-01',
|
||||
'in_progress'
|
||||
),
|
||||
(
|
||||
'AI-2024-001',
|
||||
'HR Portal 系統開發',
|
||||
'開發整合 Keycloak 的人資管理系統',
|
||||
(SELECT id FROM employees WHERE username = 'alice.wang'),
|
||||
'2024-01-01',
|
||||
'in_progress'
|
||||
);
|
||||
|
||||
-- 插入專案成員
|
||||
INSERT INTO project_members (project_id, employee_id, role)
|
||||
VALUES
|
||||
-- WIND-2024-001 團隊
|
||||
((SELECT id FROM projects WHERE project_code = 'WIND-2024-001'),
|
||||
(SELECT id FROM employees WHERE username = 'charlie.lin'),
|
||||
'Project Manager'),
|
||||
|
||||
-- CARBON-2024-001 團隊
|
||||
((SELECT id FROM projects WHERE project_code = 'CARBON-2024-001'),
|
||||
(SELECT id FROM employees WHERE username = 'diana.wu'),
|
||||
'Project Manager'),
|
||||
|
||||
-- AI-2024-001 團隊
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'),
|
||||
(SELECT id FROM employees WHERE username = 'alice.wang'),
|
||||
'Project Manager'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'),
|
||||
(SELECT id FROM employees WHERE username = 'bob.chen'),
|
||||
'Lead Developer'),
|
||||
((SELECT id FROM projects WHERE project_code = 'AI-2024-001'),
|
||||
(SELECT id FROM employees WHERE username = 'porsche.chen'),
|
||||
'Technical Advisor');
|
||||
|
||||
-- 驗證結果
|
||||
SELECT '=== 員工列表 ===' as info;
|
||||
SELECT
|
||||
e.employee_id,
|
||||
e.username,
|
||||
e.chinese_name,
|
||||
bu.name as business_unit,
|
||||
e.position,
|
||||
e.job_level
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
ORDER BY e.employee_id;
|
||||
|
||||
SELECT '=== 郵件帳號 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
ea.email_address,
|
||||
ea.mailbox_quota_mb || ' MB' as quota
|
||||
FROM email_accounts ea
|
||||
JOIN employees e ON ea.employee_id = e.id
|
||||
ORDER BY e.employee_id;
|
||||
|
||||
SELECT '=== 網路硬碟 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
nd.drive_name,
|
||||
nd.quota_gb || ' GB' as quota
|
||||
FROM network_drives nd
|
||||
JOIN employees e ON nd.employee_id = e.id
|
||||
ORDER BY e.employee_id;
|
||||
|
||||
SELECT '=== 系統權限 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
sp.system_name,
|
||||
sp.access_level
|
||||
FROM system_permissions sp
|
||||
JOIN employees e ON sp.employee_id = e.id
|
||||
WHERE sp.is_active = true
|
||||
ORDER BY e.employee_id, sp.system_name;
|
||||
|
||||
SELECT '=== 專案列表 ===' as info;
|
||||
SELECT
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
e.chinese_name as manager,
|
||||
COUNT(pm.id) as members,
|
||||
p.status
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, e.chinese_name, p.status
|
||||
ORDER BY p.project_code;
|
||||
|
||||
SELECT '=== 完成! ===' as info;
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM employees) as employees,
|
||||
(SELECT COUNT(*) FROM email_accounts) as email_accounts,
|
||||
(SELECT COUNT(*) FROM network_drives) as network_drives,
|
||||
(SELECT COUNT(*) FROM system_permissions WHERE is_active = true) as permissions,
|
||||
(SELECT COUNT(*) FROM projects) as projects;
|
||||
393
scripts/insert-test-data.sql
Normal file
393
scripts/insert-test-data.sql
Normal file
@@ -0,0 +1,393 @@
|
||||
-- ====================================
|
||||
-- HR Portal - 測試資料
|
||||
-- ====================================
|
||||
-- 用途: 插入測試資料以驗證系統功能
|
||||
-- ====================================
|
||||
|
||||
-- 檢查事業部資料
|
||||
SELECT '=== 事業部資料 ===' as info;
|
||||
SELECT id, code, name, name_en FROM business_units ORDER BY id;
|
||||
|
||||
-- 檢查部門資料
|
||||
SELECT '=== 部門資料 ===' as info;
|
||||
SELECT id, business_unit_id, code, name, name_en FROM divisions ORDER BY id;
|
||||
|
||||
-- 插入測試員工
|
||||
INSERT INTO employees (
|
||||
employee_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name,
|
||||
chinese_name,
|
||||
email,
|
||||
mobile,
|
||||
business_unit_id,
|
||||
division_id,
|
||||
position,
|
||||
job_title,
|
||||
job_level,
|
||||
employment_type,
|
||||
hire_date,
|
||||
status
|
||||
) VALUES
|
||||
-- 管理層
|
||||
(
|
||||
'E0001',
|
||||
'porsche.chen',
|
||||
'Porsche',
|
||||
'Chen',
|
||||
'陳博駿',
|
||||
'porsche.chen@porscheworld.tw',
|
||||
'0912-345-678',
|
||||
4, -- 管理部門
|
||||
10, -- 資訊部
|
||||
'CTO',
|
||||
'技術長',
|
||||
'C-Level',
|
||||
'full-time',
|
||||
'2020-01-01',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 智能研發服務 - 軟體工程師
|
||||
(
|
||||
'E1001',
|
||||
'alice.wang',
|
||||
'Alice',
|
||||
'Wang',
|
||||
'王小華',
|
||||
'alice.wang@lab.taipei',
|
||||
'0923-456-789',
|
||||
3, -- 智能研發服務
|
||||
7, -- 軟體研發部
|
||||
'Frontend Developer',
|
||||
'前端工程師',
|
||||
'Engineer',
|
||||
'full-time',
|
||||
'2024-03-01',
|
||||
'active'
|
||||
),
|
||||
(
|
||||
'E1002',
|
||||
'bob.chen',
|
||||
'Bob',
|
||||
'Chen',
|
||||
'陳大明',
|
||||
'bob.chen@lab.taipei',
|
||||
'0934-567-890',
|
||||
3, -- 智能研發服務
|
||||
7, -- 軟體研發部
|
||||
'Backend Developer',
|
||||
'後端工程師',
|
||||
'Senior',
|
||||
'full-time',
|
||||
'2023-06-15',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 玄鐵風能授權服務
|
||||
(
|
||||
'E2001',
|
||||
'charlie.lin',
|
||||
'Charlie',
|
||||
'Lin',
|
||||
'林志明',
|
||||
'charlie.lin@ease.taipei',
|
||||
'0945-678-901',
|
||||
1, -- 玄鐵風能
|
||||
1, -- 技術授權部
|
||||
'Sales Manager',
|
||||
'業務經理',
|
||||
'Manager',
|
||||
'full-time',
|
||||
'2022-08-20',
|
||||
'active'
|
||||
),
|
||||
|
||||
-- 國際碳權申請服務
|
||||
(
|
||||
'E3001',
|
||||
'diana.wu',
|
||||
'Diana',
|
||||
'Wu',
|
||||
'吳雅婷',
|
||||
'diana.wu@ease.taipei',
|
||||
'0956-789-012',
|
||||
2, -- 國際碳權
|
||||
4, -- 碳權申請部
|
||||
'Carbon Credit Consultant',
|
||||
'碳權顧問',
|
||||
'Senior',
|
||||
'full-time',
|
||||
'2023-02-10',
|
||||
'active'
|
||||
)
|
||||
|
||||
ON CONFLICT (employee_id) DO NOTHING;
|
||||
|
||||
-- 插入測試郵件帳號
|
||||
INSERT INTO email_accounts (
|
||||
employee_id,
|
||||
email_address,
|
||||
mailbox_quota_mb,
|
||||
is_active
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
CASE
|
||||
WHEN job_level = 'C-Level' THEN 5120 -- 5GB
|
||||
WHEN job_level = 'Manager' THEN 2048 -- 2GB
|
||||
ELSE 1024 -- 1GB
|
||||
END,
|
||||
TRUE
|
||||
FROM employees
|
||||
WHERE email IS NOT NULL
|
||||
ON CONFLICT (email_address) DO NOTHING;
|
||||
|
||||
-- 插入測試網路硬碟
|
||||
INSERT INTO network_drives (
|
||||
employee_id,
|
||||
drive_name,
|
||||
drive_path,
|
||||
quota_gb,
|
||||
webdav_url,
|
||||
smb_path,
|
||||
is_active
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
username || '_personal',
|
||||
'/volume1/homes/' || username,
|
||||
CASE
|
||||
WHEN job_level = 'C-Level' THEN 50
|
||||
WHEN job_level = 'VP' THEN 30
|
||||
WHEN job_level = 'Director' THEN 30
|
||||
WHEN job_level = 'Manager' THEN 20
|
||||
WHEN job_level = 'Senior' THEN 15
|
||||
ELSE 10
|
||||
END,
|
||||
'https://nas.porscheworld.tw/webdav/' || username,
|
||||
'\\10.1.0.30\homes\' || username,
|
||||
TRUE
|
||||
FROM employees
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 插入測試系統權限
|
||||
INSERT INTO system_permissions (
|
||||
employee_id,
|
||||
system_name,
|
||||
system_url,
|
||||
access_level,
|
||||
is_active,
|
||||
granted_by
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
system,
|
||||
url,
|
||||
access_level,
|
||||
TRUE,
|
||||
'system'
|
||||
FROM employees
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
('HR Portal', 'https://hr.porscheworld.tw', 'user'),
|
||||
('Webmail', 'https://webmail.porscheworld.tw', 'user')
|
||||
) AS systems(system, url, access_level)
|
||||
WHERE status = 'active'
|
||||
ON CONFLICT (employee_id, system_name) DO NOTHING;
|
||||
|
||||
-- 為研發人員新增額外權限
|
||||
INSERT INTO system_permissions (
|
||||
employee_id,
|
||||
system_name,
|
||||
system_url,
|
||||
access_level,
|
||||
is_active,
|
||||
granted_by
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
system,
|
||||
url,
|
||||
access_level,
|
||||
TRUE,
|
||||
'system'
|
||||
FROM employees
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
('Gitea', 'https://git.lab.taipei', 'developer'),
|
||||
('Portainer', 'https://portainer.porscheworld.tw', 'user')
|
||||
) AS dev_systems(system, url, access_level)
|
||||
WHERE division_id IN (7, 8, 9) -- 研發部門
|
||||
AND status = 'active'
|
||||
ON CONFLICT (employee_id, system_name) DO NOTHING;
|
||||
|
||||
-- 插入測試專案
|
||||
INSERT INTO projects (
|
||||
business_unit_id,
|
||||
project_code,
|
||||
project_name,
|
||||
description,
|
||||
client_name,
|
||||
status,
|
||||
project_manager_id,
|
||||
start_date,
|
||||
end_date,
|
||||
budget_amount,
|
||||
budget_currency
|
||||
) VALUES
|
||||
(
|
||||
3, -- 智能研發
|
||||
'PRJ-2024-001',
|
||||
'HR Portal 開發專案',
|
||||
'企業人資管理系統開發',
|
||||
'內部專案',
|
||||
'active',
|
||||
(SELECT id FROM employees WHERE username = 'porsche.chen'),
|
||||
'2024-01-15',
|
||||
'2024-06-30',
|
||||
1000000.00,
|
||||
'TWD'
|
||||
),
|
||||
(
|
||||
1, -- 玄鐵風能
|
||||
'PRJ-2024-002',
|
||||
'台中風場評估案',
|
||||
'台中離岸風場技術評估與授權',
|
||||
'台灣風電公司',
|
||||
'active',
|
||||
(SELECT id FROM employees WHERE username = 'charlie.lin'),
|
||||
'2024-02-01',
|
||||
'2024-08-31',
|
||||
5000000.00,
|
||||
'TWD'
|
||||
),
|
||||
(
|
||||
2, -- 國際碳權
|
||||
'PRJ-2024-003',
|
||||
'某企業碳盤查專案',
|
||||
'ISO 14064-1 組織型溫室氣體盤查',
|
||||
'科技公司 A',
|
||||
'planning',
|
||||
(SELECT id FROM employees WHERE username = 'diana.wu'),
|
||||
'2024-03-01',
|
||||
'2024-12-31',
|
||||
800000.00,
|
||||
'TWD'
|
||||
)
|
||||
ON CONFLICT (project_code) DO NOTHING;
|
||||
|
||||
-- 插入專案成員
|
||||
INSERT INTO project_members (
|
||||
project_id,
|
||||
employee_id,
|
||||
role,
|
||||
allocation_percentage
|
||||
)
|
||||
SELECT
|
||||
p.id,
|
||||
e.id,
|
||||
member.role,
|
||||
member.allocation
|
||||
FROM projects p
|
||||
CROSS JOIN LATERAL (
|
||||
VALUES
|
||||
((SELECT id FROM employees WHERE username = 'porsche.chen'), 'project-manager', 50),
|
||||
((SELECT id FROM employees WHERE username = 'alice.wang'), 'developer', 80),
|
||||
((SELECT id FROM employees WHERE username = 'bob.chen'), 'developer', 100)
|
||||
) AS member(employee_id, role, allocation)
|
||||
JOIN employees e ON e.id = member.employee_id
|
||||
WHERE p.project_code = 'PRJ-2024-001'
|
||||
ON CONFLICT (project_id, employee_id) DO NOTHING;
|
||||
|
||||
-- ====================================
|
||||
-- 查詢驗證資料
|
||||
-- ====================================
|
||||
|
||||
SELECT '=== 員工資料 ===' as info;
|
||||
SELECT
|
||||
employee_id,
|
||||
username,
|
||||
chinese_name,
|
||||
email,
|
||||
position,
|
||||
job_level,
|
||||
status
|
||||
FROM employees
|
||||
ORDER BY employee_id;
|
||||
|
||||
SELECT '=== 郵件帳號 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
ea.email_address,
|
||||
ea.mailbox_quota_mb || 'MB' as quota,
|
||||
ea.is_active
|
||||
FROM email_accounts ea
|
||||
JOIN employees e ON ea.employee_id = e.id
|
||||
ORDER BY e.employee_id;
|
||||
|
||||
SELECT '=== 網路硬碟 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
nd.drive_name,
|
||||
nd.quota_gb || 'GB' as quota,
|
||||
nd.is_active
|
||||
FROM network_drives nd
|
||||
JOIN employees e ON nd.employee_id = e.id
|
||||
ORDER BY e.employee_id;
|
||||
|
||||
SELECT '=== 系統權限 ===' as info;
|
||||
SELECT
|
||||
e.username,
|
||||
sp.system_name,
|
||||
sp.access_level,
|
||||
sp.is_active
|
||||
FROM system_permissions sp
|
||||
JOIN employees e ON sp.employee_id = e.id
|
||||
ORDER BY e.username, sp.system_name;
|
||||
|
||||
SELECT '=== 專案資訊 ===' as info;
|
||||
SELECT
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
p.status,
|
||||
e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name
|
||||
ORDER BY p.project_code;
|
||||
|
||||
-- 統計資訊
|
||||
SELECT '=== 統計資訊 ===' as info;
|
||||
SELECT
|
||||
'員工總數' as metric,
|
||||
COUNT(*) as count
|
||||
FROM employees
|
||||
UNION ALL
|
||||
SELECT
|
||||
'活躍員工',
|
||||
COUNT(*)
|
||||
FROM employees
|
||||
WHERE status = 'active'
|
||||
UNION ALL
|
||||
SELECT
|
||||
'郵件帳號',
|
||||
COUNT(*)
|
||||
FROM email_accounts
|
||||
UNION ALL
|
||||
SELECT
|
||||
'網路硬碟',
|
||||
COUNT(*)
|
||||
FROM network_drives
|
||||
UNION ALL
|
||||
SELECT
|
||||
'活躍專案',
|
||||
COUNT(*)
|
||||
FROM projects
|
||||
WHERE status = 'active';
|
||||
|
||||
SELECT '=== 資料插入完成 ===' as info;
|
||||
108
scripts/run-auto-setup.ps1
Normal file
108
scripts/run-auto-setup.ps1
Normal file
@@ -0,0 +1,108 @@
|
||||
# Auto run database setup with password
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " HR Portal Database Auto Setup" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$REMOTE_HOST = "10.1.0.254"
|
||||
$REMOTE_USER = "ubuntu"
|
||||
$PASSWORD = "DC1qaz2wsx"
|
||||
$LOCAL_SCRIPT = "W:\DevOps-Workspace\hr-portal\scripts\auto-setup.sh"
|
||||
|
||||
# Step 1: Upload script using plink (PuTTY)
|
||||
Write-Host "[1/3] Uploading setup script..." -ForegroundColor Yellow
|
||||
|
||||
# Create expect-like script for SSH
|
||||
$uploadCommand = @"
|
||||
`$password = '$PASSWORD'
|
||||
`$process = Start-Process plink -ArgumentList "-ssh $REMOTE_USER@$REMOTE_HOST -pw `$password 'cat > /tmp/auto-setup.sh'" -NoNewWindow -Wait -RedirectStandardInput '$LOCAL_SCRIPT' -PassThru
|
||||
"@
|
||||
|
||||
# Fallback: Try with cmd echo
|
||||
Write-Host " Trying direct SSH connection..." -ForegroundColor Gray
|
||||
|
||||
# Create a temporary script with password
|
||||
$tempScript = @"
|
||||
#!/usr/bin/expect -f
|
||||
set timeout 60
|
||||
spawn scp $LOCAL_SCRIPT ${REMOTE_USER}@${REMOTE_HOST}:/tmp/auto-setup.sh
|
||||
expect "password:"
|
||||
send "${PASSWORD}\r"
|
||||
expect eof
|
||||
"@
|
||||
|
||||
# Write temp script
|
||||
$tempScriptPath = "$env:TEMP\upload-script.exp"
|
||||
$tempScript | Out-File -FilePath $tempScriptPath -Encoding ASCII
|
||||
|
||||
# Try using WSL if available
|
||||
$wslAvailable = Get-Command wsl -ErrorAction SilentlyContinue
|
||||
|
||||
if ($wslAvailable) {
|
||||
Write-Host " Using WSL for transfer..." -ForegroundColor Gray
|
||||
|
||||
# Copy file to WSL
|
||||
$wslTempPath = "/tmp/auto-setup.sh"
|
||||
wsl cp "$LOCAL_SCRIPT" $wslTempPath
|
||||
|
||||
# Use sshpass in WSL
|
||||
$result = wsl bash -c "sshpass -p '$PASSWORD' scp /tmp/auto-setup.sh ${REMOTE_USER}@${REMOTE_HOST}:/tmp/"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Script uploaded" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Upload failed" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
# Manual method
|
||||
Write-Host ""
|
||||
Write-Host " WSL not available. Please manually upload the script:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Open WinSCP or FileZilla" -ForegroundColor White
|
||||
Write-Host " 2. Connect to: ubuntu@10.1.0.254 (password: DC1qaz2wsx)" -ForegroundColor White
|
||||
Write-Host " 3. Upload: W:\DevOps-Workspace\hr-portal\scripts\auto-setup.sh" -ForegroundColor White
|
||||
Write-Host " 4. To: /tmp/auto-setup.sh" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Read-Host "Press Enter after upload completes"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 2: Make executable and run
|
||||
Write-Host "[2/3] Executing setup on remote server..." -ForegroundColor Yellow
|
||||
|
||||
if ($wslAvailable) {
|
||||
$executeCommands = @"
|
||||
chmod +x /tmp/auto-setup.sh
|
||||
bash /tmp/auto-setup.sh
|
||||
"@
|
||||
|
||||
$result = wsl bash -c "sshpass -p '$PASSWORD' ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} '$executeCommands'"
|
||||
|
||||
Write-Host $result
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host ""
|
||||
Write-Host " [OK] Setup completed!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [WARNING] Check output above for any errors" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host " Please run these commands on Ubuntu Server:" -ForegroundColor Yellow
|
||||
Write-Host " chmod +x /tmp/auto-setup.sh" -ForegroundColor White
|
||||
Write-Host " bash /tmp/auto-setup.sh" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[3/3] Database connection info:" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " CONNECTION STRING:" -ForegroundColor Cyan
|
||||
Write-Host " postgresql://hr_user:DC1qaz2wsx@10.1.0.254:5432/hr_portal" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Next: Update backend/.env file" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
109
scripts/run-db-setup.ps1
Normal file
109
scripts/run-db-setup.ps1
Normal file
@@ -0,0 +1,109 @@
|
||||
# Run Database Setup on Ubuntu Server
|
||||
# This script uploads files to Ubuntu Server and executes the setup
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " HR Portal Database Setup Launcher" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$REMOTE_HOST = "ubuntu@10.1.0.254"
|
||||
$REMOTE_DIR = "/tmp/hr-portal-setup"
|
||||
$LOCAL_SCRIPT_DIR = "W:\DevOps-Workspace\hr-portal\scripts"
|
||||
|
||||
# Step 1: Create remote directory
|
||||
Write-Host "[1/4] Preparing remote directory..." -ForegroundColor Yellow
|
||||
|
||||
ssh $REMOTE_HOST "mkdir -p $REMOTE_DIR && chmod 755 $REMOTE_DIR"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Remote directory ready: $REMOTE_DIR" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Could not create remote directory" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Please ensure SSH key-based authentication is configured." -ForegroundColor Yellow
|
||||
Write-Host "Run: type `$env:USERPROFILE\.ssh\id_rsa.pub | ssh ubuntu@10.1.0.254 'cat >> ~/.ssh/authorized_keys'" -ForegroundColor Gray
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 2: Upload files
|
||||
Write-Host "[2/4] Uploading setup files..." -ForegroundColor Yellow
|
||||
|
||||
$filesToUpload = @(
|
||||
"setup-hr-portal-db.sh",
|
||||
"init-db.sql",
|
||||
"insert-test-data.sql"
|
||||
)
|
||||
|
||||
foreach ($file in $filesToUpload) {
|
||||
$localPath = Join-Path $LOCAL_SCRIPT_DIR $file
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
Write-Host " Uploading $file..." -ForegroundColor Gray
|
||||
scp $localPath "${REMOTE_HOST}:${REMOTE_DIR}/"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " [FAIL] Failed to upload $file" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host " [WARNING] File not found: $file" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host " [OK] Files uploaded successfully" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Step 3: Make script executable
|
||||
Write-Host "[3/4] Setting permissions..." -ForegroundColor Yellow
|
||||
|
||||
ssh $REMOTE_HOST "chmod +x $REMOTE_DIR/setup-hr-portal-db.sh"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Script is executable" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Could not set permissions" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 4: Execute setup script
|
||||
Write-Host "[4/4] Executing database setup on remote server..." -ForegroundColor Yellow
|
||||
Write-Host " You will be prompted for database passwords" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
ssh -t $REMOTE_HOST "cd $REMOTE_DIR && bash setup-hr-portal-db.sh"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Database Setup Completed!" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Green
|
||||
Write-Host " 1. The connection string has been displayed above" -ForegroundColor White
|
||||
Write-Host " 2. Update W:\DevOps-Workspace\hr-portal\backend\.env" -ForegroundColor White
|
||||
Write-Host " 3. Run insert-test-data.sql for sample data (optional)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "[ERROR] Setup script failed" -ForegroundColor Red
|
||||
Write-Host "Check the error messages above for details" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Cleanup prompt
|
||||
Write-Host ""
|
||||
$cleanup = Read-Host "Clean up remote setup files? (y/N)"
|
||||
|
||||
if ($cleanup -eq "y" -or $cleanup -eq "Y") {
|
||||
Write-Host "Cleaning up..." -ForegroundColor Gray
|
||||
ssh $REMOTE_HOST "rm -rf $REMOTE_DIR"
|
||||
Write-Host " [OK] Cleanup complete" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
103
scripts/run-fix-postgres.ps1
Normal file
103
scripts/run-fix-postgres.ps1
Normal file
@@ -0,0 +1,103 @@
|
||||
# Execute PostgreSQL fix remotely via SSH
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Fixing PostgreSQL Port Configuration" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$REMOTE_HOST = "10.1.0.254"
|
||||
$REMOTE_USER = "ubuntu"
|
||||
$PASSWORD = "DC1qaz2wsx"
|
||||
|
||||
# Create the command as a single string
|
||||
$commands = @"
|
||||
echo '[1/4] Stopping old PostgreSQL container...'
|
||||
docker stop postgres 2>/dev/null
|
||||
docker rm postgres 2>/dev/null
|
||||
|
||||
echo ''
|
||||
echo '[2/4] Starting new container with external access...'
|
||||
docker run -d \
|
||||
--name postgres \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="$PASSWORD" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:5432:5432 \
|
||||
-v postgres-data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
echo ''
|
||||
echo '[3/4] Waiting for PostgreSQL...'
|
||||
sleep 8
|
||||
|
||||
echo ''
|
||||
echo '[4/4] Verifying...'
|
||||
docker ps | grep postgres
|
||||
docker port postgres
|
||||
docker exec postgres psql -U hr_user -d hr_portal -c 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='\''public'\'' AND table_type='\''BASE TABLE'\'';' 2>/dev/null || echo 'Database check will be done after connection test'
|
||||
|
||||
echo ''
|
||||
echo '=========================================='
|
||||
echo 'PostgreSQL reconfiguration complete!'
|
||||
echo '=========================================='
|
||||
"@
|
||||
|
||||
Write-Host "Connecting to Ubuntu Server..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Try using plink if available
|
||||
$plinkPath = Get-Command plink -ErrorAction SilentlyContinue
|
||||
|
||||
if ($plinkPath) {
|
||||
Write-Host "Using plink for connection..." -ForegroundColor Gray
|
||||
$commands | plink -ssh -batch -pw $PASSWORD "${REMOTE_USER}@${REMOTE_HOST}"
|
||||
} else {
|
||||
# Fallback to regular ssh
|
||||
Write-Host "Using ssh for connection..." -ForegroundColor Gray
|
||||
Write-Host "You may need to enter password: $PASSWORD" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Save commands to temp file
|
||||
$tempFile = "$env:TEMP\fix-postgres-commands.sh"
|
||||
$commands | Out-File -FilePath $tempFile -Encoding UTF8
|
||||
|
||||
# Try to execute via ssh with heredoc
|
||||
$sshCommand = "bash -s"
|
||||
Get-Content $tempFile | ssh "${REMOTE_USER}@${REMOTE_HOST}" $sshCommand
|
||||
}
|
||||
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
Write-Host ""
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host " Success!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "PostgreSQL is now accessible from:" -ForegroundColor White
|
||||
Write-Host " Host: 10.1.0.254" -ForegroundColor Cyan
|
||||
Write-Host " Port: 5432" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Testing connection from Windows..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Test connection
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
Write-Host "Running database connection test..." -ForegroundColor Yellow
|
||||
cd "W:\DevOps-Workspace\hr-portal\backend"
|
||||
python test_db_connection.py
|
||||
|
||||
} else {
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host " Error occurred" -ForegroundColor Red
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Exit code: $exitCode" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Please check the output above for errors." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
102
scripts/setup-database.ps1
Normal file
102
scripts/setup-database.ps1
Normal file
@@ -0,0 +1,102 @@
|
||||
# ====================================
|
||||
# HR Portal - 資料庫設定腳本 (PowerShell)
|
||||
# ====================================
|
||||
|
||||
Write-Host "=== HR Portal 資料庫設定 ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 設定變數
|
||||
$DB_HOST = "10.1.0.254"
|
||||
$DB_NAME = "hr_portal"
|
||||
$DB_USER = "hr_user"
|
||||
$DB_PASSWORD = "hr_password_change_me" # 請修改為強密碼
|
||||
|
||||
Write-Host "連接到 PostgreSQL..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# 檢查 psql 是否可用
|
||||
$psqlAvailable = Get-Command psql -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $psqlAvailable) {
|
||||
Write-Host "錯誤: 找不到 psql 命令" -ForegroundColor Red
|
||||
Write-Host "請確認 PostgreSQL 客戶端已安裝" -ForegroundColor Red
|
||||
Write-Host "或使用 Docker 方式執行" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
# 1. 創建資料庫用戶
|
||||
Write-Host "1. 創建資料庫用戶: $DB_USER" -ForegroundColor Green
|
||||
|
||||
$createUserSQL = @"
|
||||
DO `$`$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$DB_USER') THEN
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
RAISE NOTICE 'User $DB_USER created';
|
||||
ELSE
|
||||
RAISE NOTICE 'User $DB_USER already exists';
|
||||
END IF;
|
||||
END
|
||||
`$`$;
|
||||
"@
|
||||
|
||||
$createUserSQL | psql -h $DB_HOST -U postgres -w
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 2. 創建資料庫
|
||||
Write-Host "2. 創建資料庫: $DB_NAME" -ForegroundColor Green
|
||||
|
||||
$createDbSQL = @"
|
||||
SELECT 'CREATE DATABASE $DB_NAME OWNER $DB_USER'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$DB_NAME')\gexec
|
||||
"@
|
||||
|
||||
$createDbSQL | psql -h $DB_HOST -U postgres -w
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 3. 授予權限
|
||||
Write-Host "3. 授予權限" -ForegroundColor Green
|
||||
|
||||
$grantSQL = @"
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $DB_USER;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DB_USER;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER;
|
||||
"@
|
||||
|
||||
$grantSQL | psql -h $DB_HOST -U postgres -d $DB_NAME -w
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 4. 執行 Schema 初始化
|
||||
Write-Host "4. 執行 Schema 初始化" -ForegroundColor Green
|
||||
|
||||
$schemaFile = Join-Path $PSScriptRoot "init-db.sql"
|
||||
|
||||
if (Test-Path $schemaFile) {
|
||||
Get-Content $schemaFile | psql -h $DB_HOST -U $DB_USER -d $DB_NAME
|
||||
} else {
|
||||
Write-Host "警告: 找不到 init-db.sql" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✅ 資料庫設定完成!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "資料庫連接資訊:" -ForegroundColor Cyan
|
||||
Write-Host " Host: $DB_HOST"
|
||||
Write-Host " Database: $DB_NAME"
|
||||
Write-Host " User: $DB_USER"
|
||||
Write-Host " Password: $DB_PASSWORD"
|
||||
Write-Host ""
|
||||
Write-Host "DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:5432/${DB_NAME}" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "請將上述 DATABASE_URL 複製到 backend/.env 檔案中" -ForegroundColor Cyan
|
||||
|
||||
} catch {
|
||||
Write-Host "錯誤: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
73
scripts/setup-database.sh
Normal file
73
scripts/setup-database.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# ====================================
|
||||
# HR Portal - 資料庫設定腳本
|
||||
# ====================================
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== HR Portal 資料庫設定 ==="
|
||||
echo ""
|
||||
|
||||
# 設定變數
|
||||
DB_HOST="10.1.0.254"
|
||||
DB_NAME="hr_portal"
|
||||
DB_USER="hr_user"
|
||||
DB_PASSWORD="hr_password_change_me" # 請修改為強密碼
|
||||
|
||||
echo "連接到 PostgreSQL..."
|
||||
echo ""
|
||||
|
||||
# 1. 創建資料庫用戶 (如果不存在)
|
||||
echo "1. 創建資料庫用戶: $DB_USER"
|
||||
ssh ubuntu@$DB_HOST "docker exec -i postgres psql -U postgres" <<EOF
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$DB_USER') THEN
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
RAISE NOTICE 'User $DB_USER created';
|
||||
ELSE
|
||||
RAISE NOTICE 'User $DB_USER already exists';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# 2. 創建資料庫 (如果不存在)
|
||||
echo "2. 創建資料庫: $DB_NAME"
|
||||
ssh ubuntu@$DB_HOST "docker exec -i postgres psql -U postgres" <<EOF
|
||||
SELECT 'CREATE DATABASE $DB_NAME OWNER $DB_USER'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$DB_NAME')\gexec
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# 3. 授予權限
|
||||
echo "3. 授予權限"
|
||||
ssh ubuntu@$DB_HOST "docker exec -i postgres psql -U postgres -d $DB_NAME" <<EOF
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $DB_USER;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DB_USER;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER;
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# 4. 執行 Schema 初始化
|
||||
echo "4. 執行 Schema 初始化"
|
||||
ssh ubuntu@$DB_HOST "docker exec -i postgres psql -U $DB_USER -d $DB_NAME" < init-db.sql
|
||||
|
||||
echo ""
|
||||
echo "✅ 資料庫設定完成!"
|
||||
echo ""
|
||||
echo "資料庫連接資訊:"
|
||||
echo " Host: $DB_HOST"
|
||||
echo " Database: $DB_NAME"
|
||||
echo " User: $DB_USER"
|
||||
echo " Password: $DB_PASSWORD"
|
||||
echo ""
|
||||
echo "DATABASE_URL: postgresql://$DB_USER:$DB_PASSWORD@$DB_HOST:5432/$DB_NAME"
|
||||
echo ""
|
||||
echo "請將上述 DATABASE_URL 複製到 backend/.env 檔案中"
|
||||
185
scripts/setup-db-simple.ps1
Normal file
185
scripts/setup-db-simple.ps1
Normal file
@@ -0,0 +1,185 @@
|
||||
# ====================================
|
||||
# HR Portal - 簡化版資料庫設定
|
||||
# ====================================
|
||||
|
||||
param(
|
||||
[string]$DBHost = "10.1.0.254",
|
||||
[string]$DBName = "hr_portal",
|
||||
[string]$DBUser = "hr_user",
|
||||
[string]$DBPassword = "",
|
||||
[string]$PostgresPassword = "",
|
||||
[string]$ContainerName = "postgres"
|
||||
)
|
||||
|
||||
Write-Host "=== HR Portal 資料庫設定 ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 檢查必要參數
|
||||
if (-not $DBPassword) {
|
||||
$DBPassword = Read-Host "請輸入 hr_user 的密碼" -AsSecureString
|
||||
$DBPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($DBPassword)
|
||||
)
|
||||
}
|
||||
|
||||
if (-not $PostgresPassword) {
|
||||
$PostgresPassword = Read-Host "請輸入 postgres 超級用戶密碼" -AsSecureString
|
||||
$PostgresPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($PostgresPassword)
|
||||
)
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "設定資訊:" -ForegroundColor Yellow
|
||||
Write-Host " 資料庫主機: $DBHost"
|
||||
Write-Host " 資料庫名稱: $DBName"
|
||||
Write-Host " 資料庫用戶: $DBUser"
|
||||
Write-Host " 容器名稱: $ContainerName"
|
||||
Write-Host ""
|
||||
|
||||
$continue = Read-Host "是否繼續? (y/N)"
|
||||
if ($continue -ne 'y' -and $continue -ne 'Y') {
|
||||
Write-Host "已取消" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# === 步驟 1: 創建用戶 ===
|
||||
Write-Host "步驟 1/4: 創建資料庫用戶..." -ForegroundColor Green
|
||||
|
||||
$createUserSQL = @"
|
||||
DO `$`$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$DBUser') THEN
|
||||
CREATE USER $DBUser WITH PASSWORD '$DBPassword';
|
||||
RAISE NOTICE 'User $DBUser created';
|
||||
ELSE
|
||||
RAISE NOTICE 'User $DBUser already exists';
|
||||
END IF;
|
||||
END
|
||||
`$`$;
|
||||
"@
|
||||
|
||||
$createUserCmd = "echo `"$createUserSQL`" | docker exec -i $ContainerName psql -U postgres"
|
||||
|
||||
try {
|
||||
ssh ubuntu@$DBHost $createUserCmd
|
||||
Write-Host "✓ 用戶創建完成" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "✗ 用戶創建失敗: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# === 步驟 2: 創建資料庫 ===
|
||||
Write-Host "步驟 2/4: 創建資料庫..." -ForegroundColor Green
|
||||
|
||||
$createDbCmd = "docker exec $ContainerName psql -U postgres -c `"CREATE DATABASE $DBName OWNER $DBUser;`" 2>&1 || echo 'Database may already exist'"
|
||||
|
||||
try {
|
||||
ssh ubuntu@$DBHost $createDbCmd
|
||||
Write-Host "✓ 資料庫創建完成" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "⚠ 資料庫可能已存在" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# === 步驟 3: 授予權限 ===
|
||||
Write-Host "步驟 3/4: 授予權限..." -ForegroundColor Green
|
||||
|
||||
$grantSQL = @"
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DBName TO $DBUser;
|
||||
GRANT ALL PRIVILEGES ON SCHEMA public TO $DBUser;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DBUser;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DBUser;
|
||||
"@
|
||||
|
||||
$grantCmd = "echo `"$grantSQL`" | docker exec -i $ContainerName psql -U postgres -d $DBName"
|
||||
|
||||
try {
|
||||
ssh ubuntu@$DBHost $grantCmd
|
||||
Write-Host "✓ 權限授予完成" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "✗ 權限授予失敗: $_" -ForegroundColor Red
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# === 步驟 4: 執行 Schema ===
|
||||
Write-Host "步驟 4/4: 執行資料庫 Schema..." -ForegroundColor Green
|
||||
|
||||
$schemaFile = Join-Path $PSScriptRoot "init-db.sql"
|
||||
|
||||
if (Test-Path $schemaFile) {
|
||||
Write-Host " 讀取 Schema 檔案..." -ForegroundColor Gray
|
||||
|
||||
# 上傳 SQL 檔案到遠端
|
||||
scp $schemaFile ubuntu@${DBHost}:/tmp/hr-portal-schema.sql
|
||||
|
||||
# 執行 SQL
|
||||
$execSchemaCmd = "docker exec -i $ContainerName psql -U $DBUser -d $DBName < /tmp/hr-portal-schema.sql"
|
||||
|
||||
try {
|
||||
ssh ubuntu@$DBHost $execSchemaCmd
|
||||
Write-Host "✓ Schema 執行完成" -ForegroundColor Green
|
||||
|
||||
# 清理臨時檔案
|
||||
ssh ubuntu@$DBHost "rm /tmp/hr-portal-schema.sql"
|
||||
} catch {
|
||||
Write-Host "✗ Schema 執行失敗: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "✗ 找不到 Schema 檔案: $schemaFile" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# === 驗證 ===
|
||||
Write-Host "驗證資料庫設定..." -ForegroundColor Yellow
|
||||
|
||||
$verifyCmd = "docker exec $ContainerName psql -U $DBUser -d $DBName -c '\dt' 2>&1"
|
||||
|
||||
try {
|
||||
$tables = ssh ubuntu@$DBHost $verifyCmd
|
||||
|
||||
if ($tables -match "business_units|employees|email_accounts") {
|
||||
Write-Host "✓ 資料表已成功建立" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "找到的資料表:" -ForegroundColor Cyan
|
||||
Write-Host $tables -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "⚠ 資料表可能未正確建立" -ForegroundColor Yellow
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠ 無法驗證資料表" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== 設定完成! ===" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 顯示連接字串
|
||||
Write-Host "資料庫連接資訊:" -ForegroundColor Cyan
|
||||
Write-Host " Host: $DBHost"
|
||||
Write-Host " Port: 5432"
|
||||
Write-Host " Database: $DBName"
|
||||
Write-Host " User: $DBUser"
|
||||
Write-Host " Password: $DBPassword"
|
||||
Write-Host ""
|
||||
|
||||
$databaseUrl = "postgresql://${DBUser}:${DBPassword}@${DBHost}:5432/${DBName}"
|
||||
Write-Host "DATABASE_URL:" -ForegroundColor Yellow
|
||||
Write-Host " $databaseUrl" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "下一步:" -ForegroundColor Green
|
||||
Write-Host " 1. 複製上面的 DATABASE_URL"
|
||||
Write-Host " 2. 編輯 backend\.env"
|
||||
Write-Host " 3. 設定 DATABASE_URL 環境變數"
|
||||
Write-Host " 4. 啟動後端: uvicorn app.main:app --reload"
|
||||
Write-Host ""
|
||||
172
scripts/setup-hr-portal-db.sh
Normal file
172
scripts/setup-hr-portal-db.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# HR Portal Database Setup Script
|
||||
# Run this script on Ubuntu Server (10.1.0.254)
|
||||
#
|
||||
# Usage: bash setup-hr-portal-db.sh
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo " HR Portal Database Setup"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
DB_NAME="hr_portal"
|
||||
DB_USER="hr_user"
|
||||
DB_PASSWORD=""
|
||||
POSTGRES_CONTAINER="postgres"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Step 1: Check PostgreSQL container
|
||||
echo -e "${YELLOW}[1/6] Checking PostgreSQL container...${NC}"
|
||||
|
||||
if docker ps | grep -q "$POSTGRES_CONTAINER"; then
|
||||
echo -e "${GREEN} ✓ PostgreSQL container is running${NC}"
|
||||
docker ps --filter "name=$POSTGRES_CONTAINER" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
else
|
||||
echo -e "${RED} ✗ PostgreSQL container not found or not running${NC}"
|
||||
echo ""
|
||||
echo "Available containers:"
|
||||
docker ps -a | grep -i postgres || echo "No PostgreSQL containers found"
|
||||
echo ""
|
||||
read -p "Enter PostgreSQL container name: " POSTGRES_CONTAINER
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 2: Prompt for passwords
|
||||
echo -e "${YELLOW}[2/6] Setting up credentials...${NC}"
|
||||
|
||||
read -sp "Enter password for database user 'hr_user': " DB_PASSWORD
|
||||
echo ""
|
||||
|
||||
if [ -z "$DB_PASSWORD" ]; then
|
||||
echo -e "${RED} ✗ Password cannot be empty${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN} ✓ Credentials configured${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 3: Create database user
|
||||
echo -e "${YELLOW}[3/6] Creating database user '$DB_USER'...${NC}"
|
||||
|
||||
# Check if user exists
|
||||
USER_EXISTS=$(docker exec -i $POSTGRES_CONTAINER psql -U postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" 2>&1)
|
||||
|
||||
if echo "$USER_EXISTS" | grep -q "1"; then
|
||||
echo -e "${YELLOW} ⚠ User '$DB_USER' already exists, skipping...${NC}"
|
||||
else
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<EOF
|
||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
|
||||
ALTER USER $DB_USER WITH SUPERUSER;
|
||||
EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN} ✓ User '$DB_USER' created successfully${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Failed to create user${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Create database
|
||||
echo -e "${YELLOW}[4/6] Creating database '$DB_NAME'...${NC}"
|
||||
|
||||
# Check if database exists
|
||||
DB_EXISTS=$(docker exec -i $POSTGRES_CONTAINER psql -U postgres -lqt | cut -d \| -f 1 | grep -w "$DB_NAME" | wc -l)
|
||||
|
||||
if [ "$DB_EXISTS" -gt 0 ]; then
|
||||
echo -e "${YELLOW} ⚠ Database '$DB_NAME' already exists${NC}"
|
||||
read -p " Do you want to drop and recreate it? (yes/no): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" = "yes" ]; then
|
||||
echo " Dropping existing database..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres -c "DROP DATABASE $DB_NAME;"
|
||||
echo " Creating new database..."
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;"
|
||||
echo -e "${GREEN} ✓ Database recreated${NC}"
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ Skipping database creation${NC}"
|
||||
fi
|
||||
else
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U postgres <<EOF
|
||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
||||
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
|
||||
EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN} ✓ Database '$DB_NAME' created successfully${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Failed to create database${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 5: Initialize schema
|
||||
echo -e "${YELLOW}[5/6] Initializing database schema...${NC}"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/init-db.sql" ]; then
|
||||
echo " Executing init-db.sql..."
|
||||
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME < "$SCRIPT_DIR/init-db.sql"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN} ✓ Schema initialized successfully${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Failed to initialize schema${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED} ✗ init-db.sql not found in $SCRIPT_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 6: Verify setup
|
||||
echo -e "${YELLOW}[6/6] Verifying database setup...${NC}"
|
||||
|
||||
# Count tables
|
||||
TABLE_COUNT=$(docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';")
|
||||
|
||||
echo " Tables created: $TABLE_COUNT"
|
||||
|
||||
if [ "$TABLE_COUNT" -ge 9 ]; then
|
||||
echo -e "${GREEN} ✓ Database setup verified${NC}"
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ Expected 9 tables, found $TABLE_COUNT${NC}"
|
||||
fi
|
||||
|
||||
# List tables
|
||||
echo ""
|
||||
echo " Tables in database:"
|
||||
docker exec -i $POSTGRES_CONTAINER psql -U $DB_USER -d $DB_NAME -c "\dt"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================"
|
||||
echo " Setup Completed Successfully!"
|
||||
echo "========================================${NC}"
|
||||
echo ""
|
||||
echo "Database Connection String:"
|
||||
echo -e "${CYAN}postgresql://$DB_USER:$DB_PASSWORD@10.1.0.254:5432/$DB_NAME${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Update backend/.env with the connection string"
|
||||
echo " 2. Run insert-test-data.sql to add sample data"
|
||||
echo " 3. Test backend database connection"
|
||||
echo ""
|
||||
296
scripts/setup-hr-postgres.sh
Normal file
296
scripts/setup-hr-postgres.sh
Normal file
@@ -0,0 +1,296 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# 建立 HR Portal 專用的 PostgreSQL 容器
|
||||
# 遵循微服務架構原則: Database per Service
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo " Setup HR Portal PostgreSQL"
|
||||
echo " Microservice Architecture"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
DB_PASSWORD="DC1qaz2wsx"
|
||||
CONTAINER_NAME="hr-postgres"
|
||||
PORT="5432"
|
||||
|
||||
# Step 1: 建立容器
|
||||
echo "[1/5] Creating hr-postgres container..."
|
||||
|
||||
docker run -d \
|
||||
--name ${CONTAINER_NAME} \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_PASSWORD="${DB_PASSWORD}" \
|
||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8" \
|
||||
-e TZ=Asia/Taipei \
|
||||
-p 0.0.0.0:${PORT}:5432 \
|
||||
-v hr-postgres-data:/var/lib/postgresql/data \
|
||||
--health-cmd="pg_isready -U postgres" \
|
||||
--health-interval=10s \
|
||||
--health-timeout=5s \
|
||||
--health-retries=5 \
|
||||
postgres:16-alpine
|
||||
|
||||
echo " ✓ Container created"
|
||||
echo ""
|
||||
|
||||
# Step 2: 等待啟動
|
||||
echo "[2/5] Waiting for PostgreSQL to be ready..."
|
||||
|
||||
for i in {1..30}; do
|
||||
if docker exec ${CONTAINER_NAME} pg_isready -U postgres >/dev/null 2>&1; then
|
||||
echo " ✓ PostgreSQL is ready!"
|
||||
break
|
||||
fi
|
||||
echo " Waiting... ($i/30)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 3: 建立用戶和資料庫
|
||||
echo "[3/5] Creating hr_user and hr_portal database..."
|
||||
|
||||
docker exec -i ${CONTAINER_NAME} psql -U postgres <<EOF
|
||||
-- 建立用戶
|
||||
CREATE USER hr_user WITH PASSWORD '${DB_PASSWORD}';
|
||||
ALTER USER hr_user WITH SUPERUSER;
|
||||
|
||||
-- 建立資料庫
|
||||
CREATE DATABASE hr_portal OWNER hr_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE hr_portal TO hr_user;
|
||||
|
||||
-- 顯示結果
|
||||
\l
|
||||
EOF
|
||||
|
||||
echo " ✓ Database created"
|
||||
echo ""
|
||||
|
||||
# Step 4: 初始化 Schema
|
||||
echo "[4/5] Initializing database schema..."
|
||||
|
||||
docker exec -i ${CONTAINER_NAME} psql -U hr_user -d hr_portal <<'SCHEMA'
|
||||
SET timezone = 'Asia/Taipei';
|
||||
|
||||
-- Business Units
|
||||
CREATE TABLE business_units (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
description TEXT,
|
||||
manager_id INTEGER,
|
||||
email_domain VARCHAR(50),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
||||
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
||||
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
||||
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
||||
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門');
|
||||
|
||||
-- Divisions
|
||||
CREATE TABLE divisions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
name_en VARCHAR(100),
|
||||
manager_id INTEGER,
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Employees
|
||||
CREATE TABLE employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
||||
keycloak_user_id UUID UNIQUE,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
first_name VARCHAR(50),
|
||||
last_name VARCHAR(50),
|
||||
chinese_name VARCHAR(50),
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
business_unit_id INTEGER REFERENCES business_units(id),
|
||||
division_id INTEGER REFERENCES divisions(id),
|
||||
position VARCHAR(100),
|
||||
job_level VARCHAR(50),
|
||||
hire_date DATE,
|
||||
termination_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_employees_username ON employees(username);
|
||||
CREATE INDEX idx_employees_email ON employees(email);
|
||||
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
||||
CREATE INDEX idx_employees_status ON employees(status);
|
||||
|
||||
-- Email Accounts
|
||||
CREATE TABLE email_accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
email_address VARCHAR(100) UNIQUE NOT NULL,
|
||||
mailbox_quota_mb INTEGER DEFAULT 5120,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Network Drives
|
||||
CREATE TABLE network_drives (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
drive_name VARCHAR(100) NOT NULL,
|
||||
drive_path VARCHAR(255),
|
||||
quota_gb INTEGER DEFAULT 50,
|
||||
webdav_url VARCHAR(255),
|
||||
smb_path VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- System Permissions
|
||||
CREATE TABLE system_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
system_name VARCHAR(100) NOT NULL,
|
||||
access_level VARCHAR(50),
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
-- Projects
|
||||
CREATE TABLE projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
project_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
project_manager_id INTEGER REFERENCES employees(id),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(50) DEFAULT 'planning',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Project Members
|
||||
CREATE TABLE project_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
||||
role VARCHAR(100),
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
UNIQUE(project_id, employee_id)
|
||||
);
|
||||
|
||||
-- Audit Logs
|
||||
CREATE TABLE audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
table_name VARCHAR(100),
|
||||
record_id INTEGER,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
-- Triggers
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Views
|
||||
CREATE OR REPLACE VIEW v_employees_full AS
|
||||
SELECT e.id, e.employee_id, e.username, e.first_name, e.last_name, e.chinese_name, e.email, e.phone, e.mobile,
|
||||
bu.name as business_unit, bu.code as business_unit_code, d.name as division, d.code as division_code,
|
||||
e.position, e.job_level, e.hire_date, e.status, e.created_at, e.updated_at
|
||||
FROM employees e
|
||||
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
||||
LEFT JOIN divisions d ON e.division_id = d.id;
|
||||
|
||||
CREATE OR REPLACE VIEW v_division_headcount AS
|
||||
SELECT bu.name as business_unit, d.name as division,
|
||||
COUNT(e.id) as employee_count,
|
||||
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
||||
FROM divisions d
|
||||
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
||||
LEFT JOIN employees e ON d.id = e.division_id
|
||||
GROUP BY bu.name, d.name;
|
||||
|
||||
CREATE OR REPLACE VIEW v_project_stats AS
|
||||
SELECT p.project_code, p.project_name, p.status, e.chinese_name as project_manager,
|
||||
COUNT(pm.id) as member_count, p.start_date, p.end_date
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.project_manager_id = e.id
|
||||
LEFT JOIN project_members pm ON p.id = pm.project_id
|
||||
GROUP BY p.id, p.project_code, p.project_name, p.status, e.chinese_name, p.start_date, p.end_date;
|
||||
SCHEMA
|
||||
|
||||
echo " ✓ Schema initialized"
|
||||
echo ""
|
||||
|
||||
# Step 5: 驗證
|
||||
echo "[5/5] Verifying setup..."
|
||||
|
||||
TABLE_COUNT=$(docker exec -i ${CONTAINER_NAME} psql -U hr_user -d hr_portal -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';")
|
||||
|
||||
echo " Tables created: ${TABLE_COUNT} / 9"
|
||||
|
||||
if [ "${TABLE_COUNT}" -eq 9 ]; then
|
||||
echo " ✓ All tables verified"
|
||||
else
|
||||
echo " ⚠ Expected 9 tables, found ${TABLE_COUNT}"
|
||||
fi
|
||||
|
||||
docker exec -i ${CONTAINER_NAME} psql -U hr_user -d hr_portal -c "\dt" | head -15
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Setup Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Microservice Architecture:"
|
||||
echo " ├── keycloak-db (Keycloak)"
|
||||
echo " ├── gitea-db (Gitea)"
|
||||
echo " └── hr-postgres (HR Portal) ← NEW"
|
||||
echo ""
|
||||
echo "Connection String:"
|
||||
echo " postgresql://hr_user:${DB_PASSWORD}@10.1.0.254:${PORT}/hr_portal"
|
||||
echo ""
|
||||
echo "Container Status:"
|
||||
docker ps | grep -E "keycloak-db|gitea-db|hr-postgres"
|
||||
echo ""
|
||||
119
scripts/setup-ssh-key.ps1
Normal file
119
scripts/setup-ssh-key.ps1
Normal file
@@ -0,0 +1,119 @@
|
||||
# Setup SSH Key-based Authentication
|
||||
# This script configures passwordless SSH login to Ubuntu Server
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " SSH Key Setup for Ubuntu Server" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$SSH_DIR = "$env:USERPROFILE\.ssh"
|
||||
$SSH_KEY = "$SSH_DIR\id_rsa"
|
||||
$SSH_PUB = "$SSH_KEY.pub"
|
||||
$REMOTE_HOST = "ubuntu@10.1.0.254"
|
||||
|
||||
# Step 1: Check if SSH directory exists
|
||||
Write-Host "[1/4] Checking SSH directory..." -ForegroundColor Yellow
|
||||
|
||||
if (-not (Test-Path $SSH_DIR)) {
|
||||
Write-Host " Creating .ssh directory..." -ForegroundColor Gray
|
||||
New-Item -ItemType Directory -Path $SSH_DIR -Force | Out-Null
|
||||
Write-Host " [OK] Directory created" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [OK] Directory exists" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 2: Generate SSH key if not exists
|
||||
Write-Host "[2/4] Checking for SSH key..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $SSH_PUB) {
|
||||
Write-Host " [OK] SSH key already exists" -ForegroundColor Green
|
||||
Write-Host " Key location: $SSH_KEY" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Public key content:" -ForegroundColor Gray
|
||||
Get-Content $SSH_PUB | Write-Host -ForegroundColor White
|
||||
} else {
|
||||
Write-Host " Generating new SSH key pair..." -ForegroundColor Gray
|
||||
Write-Host " (Press Enter when prompted for passphrase to skip)" -ForegroundColor Yellow
|
||||
|
||||
ssh-keygen -t rsa -b 4096 -f $SSH_KEY -C "windows-to-ubuntu"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] SSH key generated successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Failed to generate SSH key" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 3: Copy public key to remote server
|
||||
Write-Host "[3/4] Copying public key to Ubuntu Server..." -ForegroundColor Yellow
|
||||
Write-Host " You will need to enter the Ubuntu password ONE TIME" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
try {
|
||||
# Read public key content
|
||||
$pubKeyContent = Get-Content $SSH_PUB -Raw
|
||||
|
||||
# Create command to add key to authorized_keys
|
||||
$remoteCommand = @"
|
||||
mkdir -p ~/.ssh && \
|
||||
chmod 700 ~/.ssh && \
|
||||
echo '$pubKeyContent' >> ~/.ssh/authorized_keys && \
|
||||
chmod 600 ~/.ssh/authorized_keys && \
|
||||
echo 'SSH key added successfully'
|
||||
"@
|
||||
|
||||
# Execute remote command
|
||||
Write-Host " Connecting to $REMOTE_HOST..." -ForegroundColor Gray
|
||||
ssh $REMOTE_HOST $remoteCommand
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Public key copied successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Failed to copy public key" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 4: Test passwordless login
|
||||
Write-Host "[4/4] Testing passwordless SSH login..." -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
$testResult = ssh -o BatchMode=yes -o ConnectTimeout=5 $REMOTE_HOST "echo 'Success'"
|
||||
|
||||
if ($LASTEXITCODE -eq 0 -and $testResult -eq "Success") {
|
||||
Write-Host " [OK] Passwordless login works!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] Passwordless login test failed" -ForegroundColor Red
|
||||
Write-Host " You may need to restart SSH service on Ubuntu Server" -ForegroundColor Yellow
|
||||
Write-Host " Command: sudo systemctl restart ssh" -ForegroundColor Gray
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] Test failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Setup Completed!" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "You can now run commands without password:" -ForegroundColor Green
|
||||
Write-Host " ssh $REMOTE_HOST 'docker ps'" -ForegroundColor White
|
||||
Write-Host " ssh $REMOTE_HOST 'docker exec postgres psql -U postgres -l'" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Next step:" -ForegroundColor Yellow
|
||||
Write-Host " Run check-postgres.ps1 to verify PostgreSQL connection" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Reference in New Issue
Block a user