# Chuyển TeamBoard ver2.0 từ Supabase sang cPanel MySQL

## 1. Có khả thi không?

Có. Bản này đã được chuyển sang mô hình cùng hosting:

- Giao diện HTML/CSS/JavaScript chạy trong `public_html`.
- PHP API xác thực tài khoản bằng PHP Session và chống CSRF.
- MySQL/MariaDB của cPanel lưu tài khoản và dữ liệu dùng chung.
- `localStorage` chỉ là cache dự phòng trên trình duyệt, không còn là nguồn dữ liệu chính.

Hosting cần PHP 8.0 trở lên, extension `PDO MySQL`, MySQL hoặc MariaDB và chứng chỉ HTTPS. Nếu gói hosting chỉ hỗ trợ website tĩnh thì không chạy được backend này.

## 2. Cấu trúc upload

```text
public_html/
├── index.html
├── index.php
├── .htaccess
├── assets/
├── docs/
└── api/
    ├── .htaccess
    ├── index.php
    ├── bootstrap.php
    ├── config.example.php
    ├── config.php          # tự tạo trên hosting, không commit/chia sẻ
    ├── schema.sql
    ├── migrate-password-reset.sql
    ├── forgot-password.php
    ├── reset-password.php
    └── setup-admin.php     # xóa hoặc đổi tên ngay sau khi tạo Admin
```

Bốn bảng được sử dụng:

- `teamboard_users`: tài khoản, mật khẩu đã băm, vai trò truy cập và trạng thái hoạt động.
- `teamboard_app_state`: một bản JSON dùng chung chứa task, KPI, thành viên, phân quyền và thông báo.
- `teamboard_presence`: heartbeat đăng nhập để hiển thị user online (xanh) hoặc offline (xám).
- `teamboard_password_resets`: liên kết đặt lại mật khẩu dạng mã băm, có thời hạn và chỉ dùng một lần.

Nếu database đã được tạo từ bản cũ, import `api/migrate-presence.sql` và `api/migrate-password-reset.sql` một lần trong phpMyAdmin. User gửi heartbeat mỗi 30 giây; sau khoảng 120 giây không còn heartbeat sẽ tự chuyển offline.

## 3. Tạo database trong cPanel

1. Mở **cPanel > MySQL Databases**.
2. Tạo database, ví dụ `teamboard`.
3. Tạo database user với mật khẩu mạnh và riêng biệt.
4. Thêm user vào database, chọn **ALL PRIVILEGES**.
5. Ghi lại tên đầy đủ do cPanel cấp. Tên thường có prefix, ví dụ `tenaccount_teamboard`.
6. Mở **phpMyAdmin**, chọn đúng database vừa tạo.
7. Chọn **Import** và tải file `api/schema.sql` lên.
8. Kiểm tra đã có hai bảng `teamboard_users` và `teamboard_app_state`.

## 4. Cấu hình PHP API

1. Upload nội dung gói hosting vào `public_html` hoặc thư mục domain/subdomain.
2. Trong `api`, copy `config.example.php` thành `config.php`.
3. Sửa `config.php` bằng thông tin thật:

```php
<?php
return [
    'db_host' => 'localhost',
    'db_port' => 3306,
    'db_name' => 'CPANEL_PREFIX_teamboard',
    'db_user' => 'CPANEL_PREFIX_teamboard',
    'db_password' => 'MAT_KHAU_DATABASE_RIENG',
    'session_name' => 'teamboard_cpanel_session',
    'setup_key' => 'CHUOI_NGAU_NHIEN_DAI_IT_NHAT_32_KY_TU',
    'app_state_id' => 'main',
];
```

Không đưa `config.php` vào Git hoặc gửi công khai. File `.htaccess` trong `api` đã chặn truy cập web trực tiếp đến file cấu hình, schema và bootstrap; vẫn cần giữ quyền file phù hợp (thường 0640 hoặc 0644 tùy hosting).

## 5. Tạo Admin lần đầu

1. Mở `https://ten-mien-cua-ban/api/setup-admin.php`.
2. Nhập `setup_key` đã đặt trong `config.php`.
3. Nhập họ tên, username và mật khẩu Admin tối thiểu 10 ký tự.
4. Đăng nhập tại trang chính bằng tài khoản vừa tạo.
5. Xóa hoặc đổi tên `api/setup-admin.php` ngay sau khi hoàn tất.

Trang setup tự khóa khi database đã có một Admin hoạt động, nhưng xóa file vẫn là biện pháp an toàn nên thực hiện.

## 6. Di chuyển dữ liệu hiện tại từ Supabase

Thực hiện theo thứ tự để giữ đường quay lui:

1. Trên bản Supabase cũ, đăng nhập Admin.
2. Vào **Cài đặt > Cơ sở dữ liệu > Xuất backup (.json)**.
3. Giữ nguyên project Supabase, chưa xóa bảng hoặc user.
4. Cài bản cPanel và tạo Admin lần đầu như mục 5.
5. Đăng nhập bản cPanel bằng Admin.
6. Vào **Cài đặt > Cơ sở dữ liệu > Nhập backup** và chọn file JSON vừa xuất.
7. Bấm **Lưu lên máy chủ**.
8. Tải lại trang, kiểm tra số thành viên, task, tháng KPI và thời điểm đồng bộ.
9. Tạo/reset mật khẩu đăng nhập cho từng thành viên trong **Cài đặt > Quản lý thành viên**. Mật khẩu Supabase không thể xuất hoặc chuyển trực tiếp.

Nên thử trên subdomain staging trước, ví dụ `test.tenmien.com`, rồi mới thay bản production.

## 7. Phân quyền

| Cấp tài khoản | Quyền chính |
|---|---|
| Admin | Toàn quyền; quản lý thành viên/vai trò; xem hướng dẫn; kiểm tra, kéo/lưu, backup và reset database |
| Manager | Quản lý vai trò, hạng mục và thành viên; không xem/thao tác database; không cấp/xóa Admin |
| Planner | Quản lý vai trò, hạng mục và thành viên; không xem/thao tác database; không cấp/xóa Admin |
| Thành viên | Chỉ dùng các màn hình và thao tác nghiệp vụ được phân quyền |

Khi thêm thành viên, nhập mật khẩu tạm tối thiểu 8 ký tự. User nên đổi mật khẩu sau lần đăng nhập đầu tiên.

## 8. Kiểm tra sau upload

- Mở `https://tenmien.com/api/index.php?action=health`; kết quả hợp lệ có `"ok":true`.
- Trang đăng nhập chỉ có một nút **Đăng nhập** dưới ô mật khẩu.
- Đăng nhập Admin, mở **Cài đặt > Cơ sở dữ liệu**, bấm **Kiểm tra kết nối**.
- Thêm một user thử, đăng xuất rồi đăng nhập user đó.
- Tạo một task, tải lại trang và kiểm tra task vẫn còn.
- Mở ở trình duyệt thứ hai để xác nhận dữ liệu dùng chung được đồng bộ.

## 9. Backup, bảo mật và vận hành

- Luôn bật HTTPS; không vận hành bằng HTTP công khai.
- Backup MySQL trong cPanel hằng ngày và định kỳ xuất backup JSON trong TeamBoard.
- Dùng mật khẩu database khác mật khẩu cPanel và tài khoản TeamBoard.
- Không lưu mật khẩu thật vào `config.example.php`.
- Cập nhật PHP lên nhánh còn được hosting hỗ trợ bảo mật.
- Nếu đổi domain hoặc thư mục, giữ `apiBaseUrl: './api/index.php'` để frontend gọi API cùng origin.
- Khi upload bản mới, giữ lại `api/config.php` trên server và backup database trước khi ghi đè file.

## 10. Quay lại Supabase khi cần

Trong `assets/js/config.js`, đổi:

```js
dataMode: 'supabase',
authMode: 'supabase',
```

Sau đó điền lại `supabaseUrl` và `supabaseAnonKey`, chạy schema Supabase cũ và build/upload lại. Không dùng đồng thời Supabase và cPanel làm nguồn ghi chính để tránh hai bản dữ liệu bị lệch.

## 11. Quên mật khẩu cho mọi thành viên

Cột `teamboard_users.password_hash` chỉ chứa mật khẩu đã băm. Không nhập mật khẩu thường trực tiếp bằng phpMyAdmin.

1. Import `api/migrate-password-reset.sql` nếu database được tạo từ bản cũ.
2. Bảo đảm ít nhất một tài khoản Admin có email hợp lệ.
3. Cấu hình `app_url`, `password_reset_admin_email`, Gmail SMTP và Google App Password trong `api/config.php`.
4. Thành viên chọn **Quên mật khẩu** và nhập username/email.
5. Hệ thống gửi liên kết dùng một lần tới email Admin.
6. Admin xác minh người yêu cầu rồi chuyển liên kết cho thành viên.
7. Thành viên tự đặt mật khẩu mới. Liên kết mặc định hết hạn sau 60 phút.

Xem thêm `docs/PASSWORD_RESET_GUIDE.md`.
