config token
All checks were successful
Deploy on Master Change / deploy (push) Successful in 39s

This commit is contained in:
2026-07-01 08:04:03 +07:00
parent fd4ab86fff
commit d1411ad1df
8 changed files with 179 additions and 9 deletions

View File

@@ -965,3 +965,20 @@ export const apiExam = {
}>;
},
};
export const apiQldt = {
getToken: async (): Promise<{ token: string }> => {
const res = await staffFetch('/qldt-token');
if (!res.ok) await parseError(res, 'Không tải được token QLĐT');
return res.json();
},
saveToken: async (token: string): Promise<{ ok: boolean; message: string }> => {
const res = await staffFetch('/qldt-token', {
method: 'POST',
body: JSON.stringify({ token }),
});
if (!res.ok) await parseError(res, 'Lưu token thất bại');
return res.json();
},
};

View File

@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useAuth } from '../auth/AuthContext';
import { useGitHubConnection } from '../hooks/useGitHubConnection';
import { GitHubReposPanel } from './GitHubReposPanel';
import { OrganizationSection } from './OrganizationSection';
import { apiQldt } from '../api';
export function ChangePasswordPage({ forced }: { forced?: boolean }) {
const { changePassword, logout } = useAuth();
@@ -85,6 +86,40 @@ export function MyAccountTab() {
const [pwMsg, setPwMsg] = useState('');
const [pwErr, setPwErr] = useState('');
// Cấu hình QLĐT Token (Chỉ cho phuocntb@rikkeiacademy.com)
const isQldtAdmin = staff?.email === 'phuocntb@rikkeiacademy.com';
const [qldtToken, setQldtToken] = useState('');
const [qldtMsg, setQldtMsg] = useState('');
const [qldtErr, setQldtErr] = useState('');
const [loadingQldt, setLoadingQldt] = useState(false);
useEffect(() => {
if (!isQldtAdmin) return;
setLoadingQldt(true);
apiQldt.getToken()
.then((res) => {
setQldtToken(res.token || '');
})
.catch((err) => {
setQldtErr(err.message || 'Lỗi khi tải token QLĐT');
})
.finally(() => {
setLoadingQldt(false);
});
}, [isQldtAdmin]);
const saveQldtToken = async (e: React.FormEvent) => {
e.preventDefault();
setQldtMsg('');
setQldtErr('');
try {
await apiQldt.saveToken(qldtToken);
setQldtMsg('Đã lưu cấu hình QLĐT token');
} catch (err: any) {
setQldtErr(err.message || 'Lỗi khi lưu token QLĐT');
}
};
const submitPw = async (e: React.FormEvent) => {
e.preventDefault();
setPwErr('');
@@ -165,6 +200,45 @@ export function MyAccountTab() {
)}
</div>
{isQldtAdmin && (
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Cấu hình QLĐT Token</h2>
<p className="page-desc" style={{ marginTop: 0 }}>
Cấu hình token đng bộ dữ liệu lớp học, môn học điểm danh từ Cổng đào tạo (QLĐT).
</p>
{loadingQldt ? (
<p style={{ color: 'var(--text-muted)' }}>Đang tải cấu hình...</p>
) : (
<form onSubmit={saveQldtToken} className="login-form">
<label className="login-field">
<span>Token QLĐT</span>
<textarea
style={{
width: '100%',
minHeight: '100px',
fontFamily: 'monospace',
fontSize: '0.875rem',
padding: '0.5rem',
borderRadius: '8px',
border: '1px solid var(--border-color, #ccc)',
backgroundColor: 'var(--bg-input, #fff)',
color: 'var(--text, #333)',
resize: 'vertical'
}}
value={qldtToken}
onChange={(e) => setQldtToken(e.target.value)}
placeholder="Nhập JWT Token từ QLĐT..."
required
/>
</label>
{qldtErr && <div className="login-error">{qldtErr}</div>}
{qldtMsg && <div className="login-success">{qldtMsg}</div>}
<button type="submit" className="btn btn-primary" style={{ marginTop: '0.5rem' }}>Lưu cấu hình</button>
</form>
)}
</div>
)}
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Đi mật khẩu</h2>
<form onSubmit={submitPw} className="login-form" style={{ maxWidth: 400 }}>

View File

@@ -65,6 +65,7 @@ func AutoMigrate(db *gorm.DB) error {
&models.ExamSubmission{},
&models.StaffGitHubAuth{},
&models.StaffGitHubRepo{},
&models.SystemSetting{},
); err != nil {
return err
}

View File

@@ -27,8 +27,9 @@ func atoiDefault(s string, def int) int {
}
// Handler bắt đầu đồng bộ lớp học
func StartClassesSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.ClassesSyncJob, token string) fiber.Handler {
func StartClassesSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.ClassesSyncJob) fiber.Handler {
return func(c *fiber.Ctx) error {
token := GetQldtToken(db)
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing token QLDT_TOKEN"})
}
@@ -48,8 +49,9 @@ func GetClassesSyncStatusHandler(job *syncjobs.ClassesSyncJob) fiber.Handler {
}
// Handler bắt đầu đồng bộ toàn bộ sinh viên
func StartStudentsSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.StudentsSyncJob, token string) fiber.Handler {
func StartStudentsSyncHandler(db *gorm.DB, client *qldt.Client, job *syncjobs.StudentsSyncJob) fiber.Handler {
return func(c *fiber.Ctx) error {
token := GetQldtToken(db)
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing token QLDT_TOKEN"})
}

View File

@@ -27,13 +27,14 @@ var standardShiftTemplate = []struct {
}
// GET /api/classes/:rkId/courses
func GetClassCoursesHandler(db *gorm.DB, qldtClient *qldt.Client, token string) fiber.Handler {
func GetClassCoursesHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
rkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
}
token := GetQldtToken(db)
var liveErr error
if token != "" {
res, err := qldtClient.GetClassCourses(context.Background(), token, rkID)
@@ -430,7 +431,7 @@ func UpdateAttendanceBulkStatusHandler(db *gorm.DB) fiber.Handler {
}
// POST /api/classes/:rkId/attendance/push-qldt
func PushAttendanceToQLDTHandler(db *gorm.DB, qldtClient *qldt.Client, token string) fiber.Handler {
func PushAttendanceToQLDTHandler(db *gorm.DB, qldtClient *qldt.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
classRkID, _ := strconv.ParseInt(c.Params("rkId"), 10, 64)
var req struct {
@@ -446,6 +447,7 @@ func PushAttendanceToQLDTHandler(db *gorm.DB, qldtClient *qldt.Client, token str
if req.Period < 1 {
req.Period = 1
}
token := GetQldtToken(db)
if token == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "QLDT_TOKEN chưa cấu hình"})
}

View File

@@ -0,0 +1,62 @@
package handlers
import (
"os"
"time"
"server/internal/models"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
// GetQldtToken lấy token từ database, nếu không có sẽ lấy từ biến môi trường
func GetQldtToken(db *gorm.DB) string {
var setting models.SystemSetting
if err := db.Where("setting_key = ?", "QLDT_TOKEN").First(&setting).Error; err == nil && setting.SettingValue != "" {
return setting.SettingValue
}
return os.Getenv("QLDT_TOKEN")
}
// GetQldtTokenHandler trả về token hiện tại (chỉ hỗ trợ phuocntb@rikkeiacademy.com)
func GetQldtTokenHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
email, _ := c.Locals("staffEmail").(string)
if email != "phuocntb@rikkeiacademy.com" {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Bạn không có quyền truy cập thông tin này"})
}
token := GetQldtToken(db)
return c.JSON(fiber.Map{"token": token})
}
}
// SaveQldtTokenHandler lưu token mới vào database (chỉ hỗ trợ phuocntb@rikkeiacademy.com)
func SaveQldtTokenHandler(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
email, _ := c.Locals("staffEmail").(string)
if email != "phuocntb@rikkeiacademy.com" {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Bạn không có quyền thực hiện thao tác này"})
}
var body struct {
Token string `json:"token"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Yêu cầu không hợp lệ"})
}
setting := models.SystemSetting{
SettingKey: "QLDT_TOKEN",
SettingValue: body.Token,
UpdatedAt: time.Now(),
}
if err := db.Save(&setting).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Lỗi lưu cấu hình: " + err.Error()})
}
return c.JSON(fiber.Map{"ok": true, "message": "Đã cập nhật QLDT_TOKEN thành công"})
}
}

View File

@@ -178,3 +178,13 @@ type StudentSession struct {
}
func (StudentSession) TableName() string { return "student_sessions" }
// SystemSetting lưu các cấu hình hệ thống động
type SystemSetting struct {
SettingKey string `gorm:"primaryKey;column:setting_key;size:128" json:"settingKey"`
SettingValue string `gorm:"column:setting_value;type:text" json:"settingValue"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (SystemSetting) TableName() string { return "system_settings" }

View File

@@ -128,9 +128,9 @@ func main() {
staff.Get("/students", handlers.ListAllStudentsHandler(gormDB))
// Sync endpoints
staff.Post("/sync/classes/start", handlers.StartClassesSyncHandler(gormDB, qldtClient, classesJob, qldtToken))
staff.Post("/sync/classes/start", handlers.StartClassesSyncHandler(gormDB, qldtClient, classesJob))
staff.Get("/sync/classes/status", handlers.GetClassesSyncStatusHandler(classesJob))
staff.Post("/sync/students/start", handlers.StartStudentsSyncHandler(gormDB, qldtClient, studentsJob, qldtToken))
staff.Post("/sync/students/start", handlers.StartStudentsSyncHandler(gormDB, qldtClient, studentsJob))
staff.Get("/sync/students/status", handlers.GetStudentsSyncStatusHandler(studentsJob))
// Learning Management endpoints
@@ -153,13 +153,13 @@ func main() {
staff.Get("/classes/:rkId/session-logs", handlers.ListClassSessionLogsHandler(gormDB))
staff.Get("/classes/:rkId/online-students", handlers.GetOnlineStudentsHandler(gormDB))
staff.Get("/classes/:rkId/courses", handlers.GetClassCoursesHandler(gormDB, qldtClient, qldtToken))
staff.Get("/classes/:rkId/courses", handlers.GetClassCoursesHandler(gormDB, qldtClient))
staff.Post("/classes/:rkId/schedule/apply-template", handlers.ApplyScheduleTemplateHandler(gormDB))
staff.Get("/classes/:rkId/attendance", handlers.GetClassAttendanceHandler(gormDB))
staff.Get("/classes/:rkId/attendance/shifts", handlers.ListAttendanceShiftsHandler(gormDB))
staff.Put("/classes/:rkId/attendance/status", handlers.UpdateAttendanceStatusHandler(gormDB))
staff.Put("/classes/:rkId/attendance/bulk-status", handlers.UpdateAttendanceBulkStatusHandler(gormDB))
staff.Post("/classes/:rkId/attendance/push-qldt", handlers.PushAttendanceToQLDTHandler(gormDB, qldtClient, qldtToken))
staff.Post("/classes/:rkId/attendance/push-qldt", handlers.PushAttendanceToQLDTHandler(gormDB, qldtClient))
// Chat & quản lý tài khoản
staff.Get("/chat/students", handlers.SearchChatStudentsHandler(gormDB))
@@ -170,6 +170,8 @@ func main() {
staff.Post("/admin/email-domains", handlers.AddEmailDomainHandler(gormDB))
staff.Delete("/admin/email-domains/:id", handlers.DeleteEmailDomainHandler(gormDB))
staff.Get("/admin/staff", handlers.ListStaffHandler(gormDB))
staff.Get("/qldt-token", handlers.GetQldtTokenHandler(gormDB))
staff.Post("/qldt-token", handlers.SaveQldtTokenHandler(gormDB))
// Phòng thi
staff.Get("/exam-rooms", handlers.ListExamRoomsHandler(gormDB))