package qldt import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) type CourseItem struct { ID int64 `json:"id"` Name string `json:"name"` CourseCode string `json:"courseCode"` } type CoursesResponse struct { Data []CourseItem `json:"data"` Message string `json:"message"` StatusCode int `json:"statusCode"` } type AttendanceStudent struct { StudentID int64 `json:"student_id"` Status int `json:"status"` } type AttendancePayload struct { UsersID int `json:"users_id"` CoursesID int `json:"courses_id"` Period int `json:"period"` Students []AttendanceStudent `json:"students"` ClassID int64 `json:"class_id"` Date string `json:"date"` Type string `json:"type"` } func ParseUserIDFromToken(token string) (int, error) { parts := strings.Split(token, ".") if len(parts) < 2 { return 0, fmt.Errorf("invalid jwt") } payload, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return 0, err } var claims struct { ID int `json:"id"` } if err := json.Unmarshal(payload, &claims); err != nil { return 0, err } if claims.ID == 0 { return 0, fmt.Errorf("token missing user id") } return claims.ID, nil } func (c *Client) GetClassCourses(ctx context.Context, token string, classID int64) (*CoursesResponse, error) { u := fmt.Sprintf("%s/courses/class/%d", c.baseURL, classID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return nil, err } c.setAuthHeaders(req, token) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("qldt courses http %d: %s", resp.StatusCode, string(body)) } var out CoursesResponse if err := json.Unmarshal(body, &out); err != nil { return nil, err } if out.StatusCode == 401 || strings.Contains(strings.ToLower(out.Message), "expired") { return nil, fmt.Errorf("qldt token invalid: %s", out.Message) } return &out, nil } func (c *Client) SaveAttendance(ctx context.Context, token string, payload *AttendancePayload) ([]byte, int, error) { jsonData, err := json.Marshal(payload) if err != nil { return nil, 0, err } u := c.baseURL + "/attendance" req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(jsonData)) if err != nil { return nil, 0, err } req.Header.Set("Content-Type", "application/json") c.setAuthHeaders(req, token) resp, err := c.http.Do(req) if err != nil { return nil, 0, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode < 200 || resp.StatusCode >= 300 { return body, resp.StatusCode, fmt.Errorf("qldt attendance post %d: %s", resp.StatusCode, string(body)) } return body, resp.StatusCode, nil } func (c *Client) UpdateAttendanceDetailStatus(ctx context.Context, token string, detailID int64, status string) error { payload := map[string]string{"status": status} jsonData, _ := json.Marshal(payload) u := fmt.Sprintf("%s/attendance-detail/status/%d", c.baseURL, detailID) req, err := http.NewRequestWithContext(ctx, http.MethodPatch, u, bytes.NewBuffer(jsonData)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") c.setAuthHeaders(req, token) resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("qldt patch detail %d: %s", resp.StatusCode, string(body)) } return nil } type PortalAttendanceDetail struct { ID int64 `json:"id"` Status string `json:"status"` Student struct { ID int64 `json:"id"` } `json:"student"` } type PortalAttendanceSession struct { ID int64 `json:"id"` Date string `json:"date"` Period int `json:"period"` AttendanceDetail []PortalAttendanceDetail `json:"attendanceDetail"` } type PortalAttendanceResponse struct { Data map[string][]PortalAttendanceSession `json:"data"` Message string `json:"message"` } func (c *Client) GetAttendance(ctx context.Context, token string, classID int64, courseID int64) (*PortalAttendanceResponse, error) { u, _ := url.Parse(c.baseURL + "/attendance") q := u.Query() q.Set("class_id", fmt.Sprintf("%d", classID)) q.Set("course_id", fmt.Sprintf("%d", courseID)) u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, err } c.setAuthHeaders(req, token) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("qldt get attendance %d: %s", resp.StatusCode, string(body)) } var out PortalAttendanceResponse if err := json.Unmarshal(body, &out); err != nil { return nil, err } return &out, nil } func (c *Client) setAuthHeaders(req *http.Request, token string) { req.Header.Set("Authorization", "Bearer "+token) if c.origin != "" { req.Header.Set("Origin", c.origin) req.Header.Set("Referer", c.origin+"/") } req.Header.Set("Accept", "application/json") } func AttendanceStatusLabel(status int) string { switch status { case 1: return "Nghỉ có phép" case 2: return "Nghỉ nửa buổi" case 3: return "Đi học muộn" case 4: return "Đi học đầy đủ" default: return "Nghỉ không phép" } } func ShiftDurationMinutes(start, end string) int { return parseHM(end) - parseHM(start) } func parseHM(t string) int { parts := strings.Split(t, ":") if len(parts) < 2 { return 0 } h, m := 0, 0 fmt.Sscanf(parts[0], "%d", &h) fmt.Sscanf(parts[1], "%d", &m) return h*60 + m } func InferStatusFromOnlineMinutes(onlineMins, shiftMins int) int { if shiftMins <= 0 { if onlineMins > 0 { return 4 } return 0 } rate := float64(onlineMins) / float64(shiftMins) switch { case rate >= 0.85: return 4 case rate >= 0.5: return 2 case onlineMins > 0: return 3 default: return 0 } } func NowDateVN() string { return time.Now().Format("2006-01-02") }