258 lines
6.2 KiB
Go
258 lines
6.2 KiB
Go
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")
|
|
}
|