542 lines
14 KiB
Go
542 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func InitDB(filepath string) *sql.DB {
|
|
db, err := sql.Open("sqlite", filepath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err = db.Ping(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
createTables(db)
|
|
return db
|
|
}
|
|
|
|
func createTables(db *sql.DB) {
|
|
queries := []string{
|
|
`CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT UNIQUE NOT NULL,
|
|
password TEXT NOT NULL,
|
|
is_admin BOOLEAN NOT NULL DEFAULT 0,
|
|
weekly_hours REAL NOT NULL DEFAULT 40.0
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS schedules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
day_of_week INTEGER NOT NULL,
|
|
start_time TEXT NOT NULL,
|
|
end_time TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
title TEXT NOT NULL
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS time_entries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
schedule_id INTEGER NOT NULL,
|
|
date TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
start_time TEXT NOT NULL,
|
|
end_time TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (schedule_id) REFERENCES schedules(id)
|
|
)`,
|
|
}
|
|
|
|
for _, query := range queries {
|
|
if _, err := db.Exec(query); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
hash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
|
_, err := db.Exec(`
|
|
INSERT OR IGNORE INTO users (id, username, password, is_admin, weekly_hours)
|
|
VALUES (?, ?, ?, ?, ?)`,
|
|
1, "admin", string(hash), true, 40.0,
|
|
)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// func createTables(db *sql.DB) {
|
|
// queries := []string{
|
|
// `CREATE TABLE IF NOT EXISTS users (
|
|
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
// username TEXT UNIQUE NOT NULL,
|
|
// password TEXT NOT NULL,
|
|
// is_admin BOOLEAN NOT NULL DEFAULT 0
|
|
// )`,
|
|
// `CREATE TABLE IF NOT EXISTS schedules (
|
|
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
// day_of_week INTEGER NOT NULL,
|
|
// start_time TEXT NOT NULL,
|
|
// end_time TEXT NOT NULL,
|
|
// type TEXT NOT NULL,
|
|
// title TEXT NOT NULL
|
|
// )`,
|
|
// `CREATE TABLE IF NOT EXISTS time_entries (
|
|
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
// user_id INTEGER NOT NULL,
|
|
// schedule_id INTEGER NOT NULL,
|
|
// date TEXT NOT NULL,
|
|
// type TEXT NOT NULL,
|
|
// start_time TEXT NOT NULL,
|
|
// end_time TEXT NOT NULL,
|
|
// created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
// FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
// FOREIGN KEY (schedule_id) REFERENCES schedules(id)
|
|
// )`,
|
|
// }
|
|
|
|
// for _, query := range queries {
|
|
// if _, err := db.Exec(query); err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
// }
|
|
|
|
// hash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
|
// _, err := db.Exec(`
|
|
// INSERT OR IGNORE INTO users (id, username, password, is_admin)
|
|
// VALUES (?, ?, ?, ?)`,
|
|
// 1, "admin", string(hash), true,
|
|
// )
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
// }
|
|
|
|
func GetUserByUsername(db *sql.DB, username string) (*User, error) {
|
|
user := &User{}
|
|
err := db.QueryRow("SELECT id, username, password, is_admin, weekly_hours FROM users WHERE username = ?", username).
|
|
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.WeeklyHours)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func GetUserByID(db *sql.DB, userID int) (*User, error) {
|
|
user := &User{}
|
|
err := db.QueryRow("SELECT id, username, password, is_admin, weekly_hours FROM users WHERE id = ?", userID).
|
|
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.WeeklyHours)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func CreateUser(db *sql.DB, username, hashedPassword string, isAdmin bool, weeklyHours float64) error {
|
|
_, err := db.Exec("INSERT INTO users (username, password, is_admin, weekly_hours) VALUES (?, ?, ?, ?)",
|
|
username, hashedPassword, isAdmin, weeklyHours)
|
|
return err
|
|
}
|
|
|
|
func GetAllUsers(db *sql.DB) ([]User, error) {
|
|
rows, err := db.Query("SELECT id, username, is_admin, weekly_hours FROM users")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []User
|
|
for rows.Next() {
|
|
var u User
|
|
if err := rows.Scan(&u.ID, &u.Username, &u.IsAdmin, &u.WeeklyHours); err != nil {
|
|
continue
|
|
}
|
|
users = append(users, u)
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
// func GetUserByUsername(db *sql.DB, username string) (*User, error) {
|
|
// user := &User{}
|
|
// err := db.QueryRow("SELECT id, username, password, is_admin FROM users WHERE username = ?", username).
|
|
// Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// return user, nil
|
|
// }
|
|
|
|
// func CreateUser(db *sql.DB, username, hashedPassword string, isAdmin bool) error {
|
|
// _, err := db.Exec("INSERT INTO users (username, password, is_admin) VALUES (?, ?, ?)",
|
|
// username, hashedPassword, isAdmin)
|
|
// return err
|
|
// }
|
|
|
|
// func GetAllUsers(db *sql.DB) ([]User, error) {
|
|
// rows, err := db.Query("SELECT id, username, is_admin FROM users")
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// defer rows.Close()
|
|
|
|
// var users []User
|
|
// for rows.Next() {
|
|
// var u User
|
|
// if err := rows.Scan(&u.ID, &u.Username, &u.IsAdmin); err != nil {
|
|
// continue
|
|
// }
|
|
// users = append(users, u)
|
|
// }
|
|
// return users, nil
|
|
// }
|
|
|
|
func UpdateUser(db *sql.DB, userID int, weeklyHours float64) error {
|
|
_, err := db.Exec("UPDATE users SET weekly_hours = ? WHERE id = ?",
|
|
weeklyHours, userID)
|
|
return err
|
|
}
|
|
|
|
func ResetUserPassword(db *sql.DB, userID int, hashedPassword string) error {
|
|
_, err := db.Exec("UPDATE users SET password = ? WHERE id = ?", hashedPassword, userID)
|
|
return err
|
|
}
|
|
|
|
func DeleteUser(db *sql.DB, id int) error {
|
|
if id == 1 {
|
|
return fmt.Errorf("cannot delete admin user")
|
|
}
|
|
_, err := db.Exec("DELETE FROM users WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func CreateSchedule(db *sql.DB, schedule *Schedule) error {
|
|
_, err := db.Exec("INSERT INTO schedules (day_of_week, start_time, end_time, type, title) VALUES (?, ?, ?, ?, ?)",
|
|
schedule.DayOfWeek, schedule.StartTime, schedule.EndTime, schedule.Type, schedule.Title)
|
|
return err
|
|
}
|
|
|
|
func GetAllSchedules(db *sql.DB) ([]Schedule, error) {
|
|
rows, err := db.Query("SELECT id, day_of_week, start_time, end_time, type, title FROM schedules ORDER BY day_of_week, start_time")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var schedules []Schedule
|
|
for rows.Next() {
|
|
var s Schedule
|
|
if err := rows.Scan(&s.ID, &s.DayOfWeek, &s.StartTime, &s.EndTime, &s.Type, &s.Title); err != nil {
|
|
continue
|
|
}
|
|
schedules = append(schedules, s)
|
|
}
|
|
return schedules, nil
|
|
}
|
|
|
|
func DeleteSchedule(db *sql.DB, id int) error {
|
|
_, err := db.Exec("DELETE FROM schedules WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func UpdateTimeEntry(db *sql.DB, entryID int, date, startTime, endTime, entryType string) error {
|
|
_, err := db.Exec("UPDATE time_entries SET date = ?, start_time = ?, end_time = ?, type = ? WHERE id = ?",
|
|
date, startTime, endTime, entryType, entryID)
|
|
return err
|
|
}
|
|
|
|
func DeleteTimeEntry(db *sql.DB, entryID int) error {
|
|
_, err := db.Exec("DELETE FROM time_entries WHERE id = ?", entryID)
|
|
return err
|
|
}
|
|
|
|
func CreateTimeEntry(db *sql.DB, entry *TimeEntry) error {
|
|
_, err := db.Exec("INSERT INTO time_entries (user_id, schedule_id, date, type, start_time, end_time) VALUES (?, ?, ?, ?, ?, ?)",
|
|
entry.UserID, entry.ScheduleID, entry.Date, entry.Type, entry.StartTime, entry.EndTime)
|
|
return err
|
|
}
|
|
|
|
func GetTimeEntriesByUser(db *sql.DB, userID int) ([]TimeEntry, error) {
|
|
rows, err := db.Query(`
|
|
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
|
FROM time_entries te
|
|
JOIN users u ON te.user_id = u.id
|
|
WHERE te.user_id = ?
|
|
ORDER BY te.date DESC, te.created_at DESC
|
|
`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []TimeEntry
|
|
for rows.Next() {
|
|
var e TimeEntry
|
|
if err := rows.Scan(&e.ID, &e.UserID, &e.ScheduleID, &e.Date, &e.Type, &e.StartTime, &e.EndTime, &e.CreatedAt, &e.Username); err != nil {
|
|
continue
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
func GetAllTimeEntries(db *sql.DB) ([]TimeEntry, error) {
|
|
rows, err := db.Query(`
|
|
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
|
FROM time_entries te
|
|
JOIN users u ON te.user_id = u.id
|
|
ORDER BY te.date DESC, te.created_at DESC
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []TimeEntry
|
|
for rows.Next() {
|
|
var e TimeEntry
|
|
if err := rows.Scan(&e.ID, &e.UserID, &e.ScheduleID, &e.Date, &e.Type, &e.StartTime, &e.EndTime, &e.CreatedAt, &e.Username); err != nil {
|
|
continue
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
|
rows, err := db.Query(`
|
|
SELECT
|
|
te.user_id,
|
|
u.username,
|
|
te.date,
|
|
te.start_time,
|
|
te.end_time,
|
|
te.type,
|
|
u.weekly_hours
|
|
FROM time_entries te
|
|
JOIN users u ON te.user_id = u.id
|
|
ORDER BY te.date DESC
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
hoursMap := make(map[string]*WeeklyHours)
|
|
|
|
for rows.Next() {
|
|
var userID int
|
|
var username, dateStr, startTime, endTime, entryType string
|
|
var expectedWeeklyHours float64
|
|
|
|
if err := rows.Scan(&userID, &username, &dateStr, &startTime, &endTime, &entryType, &expectedWeeklyHours); err != nil {
|
|
continue
|
|
}
|
|
|
|
t, err := time.Parse("2006-01-02", dateStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
year, week := t.ISOWeek()
|
|
|
|
var hours float64
|
|
if entryType == "lesson" {
|
|
hours = 1.0
|
|
} else {
|
|
hours = calculateHoursDiff(startTime, endTime)
|
|
}
|
|
|
|
key := fmt.Sprintf("%d_%d_%d", userID, year, week)
|
|
|
|
if existing, exists := hoursMap[key]; exists {
|
|
existing.TotalHours += hours
|
|
} else {
|
|
hoursMap[key] = &WeeklyHours{
|
|
UserID: userID,
|
|
Username: username,
|
|
Year: year,
|
|
Week: week,
|
|
TotalHours: hours,
|
|
ExpectedHours: expectedWeeklyHours,
|
|
RemainingHours: expectedWeeklyHours - hours,
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, h := range hoursMap {
|
|
h.RemainingHours = h.ExpectedHours - h.TotalHours
|
|
}
|
|
|
|
var result []WeeklyHours
|
|
for _, h := range hoursMap {
|
|
result = append(result, *h)
|
|
}
|
|
|
|
sort.Slice(result, func(i, j int) bool {
|
|
if result[i].Year != result[j].Year {
|
|
return result[i].Year > result[j].Year
|
|
}
|
|
if result[i].Week != result[j].Week {
|
|
return result[i].Week > result[j].Week
|
|
}
|
|
return result[i].Username < result[j].Username
|
|
})
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
|
// rows, err := db.Query(`
|
|
// SELECT
|
|
// te.user_id,
|
|
// u.username,
|
|
// te.date,
|
|
// te.start_time,
|
|
// te.end_time
|
|
// FROM time_entries te
|
|
// JOIN users u ON te.user_id = u.id
|
|
// ORDER BY te.date DESC
|
|
// `)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// defer rows.Close()
|
|
|
|
// hoursMap := make(map[string]*WeeklyHours)
|
|
|
|
// for rows.Next() {
|
|
// var userID int
|
|
// var username, dateStr, startTime, endTime string
|
|
|
|
// if err := rows.Scan(&userID, &username, &dateStr, &startTime, &endTime); err != nil {
|
|
// continue
|
|
// }
|
|
|
|
// t, err := time.Parse("2006-01-02", dateStr)
|
|
// if err != nil {
|
|
// continue
|
|
// }
|
|
|
|
// year, week := t.ISOWeek()
|
|
|
|
// hours := calculateHoursDiff(startTime, endTime)
|
|
|
|
// key := fmt.Sprintf("%d_%d_%d", userID, year, week)
|
|
|
|
// if existing, exists := hoursMap[key]; exists {
|
|
// existing.TotalHours += hours
|
|
// } else {
|
|
// hoursMap[key] = &WeeklyHours{
|
|
// UserID: userID,
|
|
// Username: username,
|
|
// Year: year,
|
|
// Week: week,
|
|
// TotalHours: hours,
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// var result []WeeklyHours
|
|
// for _, h := range hoursMap {
|
|
// result = append(result, *h)
|
|
// }
|
|
|
|
// sort.Slice(result, func(i, j int) bool {
|
|
// if result[i].Year != result[j].Year {
|
|
// return result[i].Year > result[j].Year
|
|
// }
|
|
// if result[i].Week != result[j].Week {
|
|
// return result[i].Week > result[j].Week
|
|
// }
|
|
// return result[i].Username < result[j].Username
|
|
// })
|
|
|
|
// return result, nil
|
|
// }
|
|
func calculateHoursDiff(startTime, endTime string) float64 {
|
|
parseTime := func(timeStr string) float64 {
|
|
parts := strings.Split(timeStr, ":")
|
|
if len(parts) != 2 {
|
|
return 0
|
|
}
|
|
|
|
hours, err1 := strconv.ParseFloat(parts[0], 64)
|
|
minutes, err2 := strconv.ParseFloat(parts[1], 64)
|
|
|
|
if err1 != nil || err2 != nil {
|
|
return 0
|
|
}
|
|
|
|
return hours + (minutes / 60.0)
|
|
}
|
|
|
|
start := parseTime(startTime)
|
|
end := parseTime(endTime)
|
|
|
|
if end > start {
|
|
return end - start
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// func DeleteUser(db *sql.DB, id int) error {
|
|
// if id == 1 {
|
|
// return fmt.Errorf("cannot delete admin user")
|
|
// }
|
|
// _, err := db.Exec("DELETE FROM users WHERE id = ?", id)
|
|
// return err
|
|
// }
|
|
|
|
func DeleteTimeEntriesByUserAndWeek(db *sql.DB, userID int, year int, week int) error {
|
|
dates := calculateWeekDates(year, week)
|
|
|
|
var dateList []string
|
|
for day := 0; day <= 4; day++ {
|
|
dateList = append(dateList, dates.Dates[fmt.Sprintf("%d", day)])
|
|
}
|
|
|
|
query := `
|
|
DELETE FROM time_entries
|
|
WHERE user_id = ?
|
|
AND date IN (?, ?, ?, ?, ?)
|
|
`
|
|
_, err := db.Exec(query, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4])
|
|
return err
|
|
}
|
|
|
|
func CheckUserHasEntriesForWeek(db *sql.DB, userID int, year int, week int) (bool, error) {
|
|
dates := calculateWeekDates(year, week)
|
|
|
|
var dateList []string
|
|
for day := 0; day <= 4; day++ {
|
|
dateList = append(dateList, dates.Dates[fmt.Sprintf("%d", day)])
|
|
}
|
|
|
|
query := `
|
|
SELECT COUNT(*)
|
|
FROM time_entries
|
|
WHERE user_id = ?
|
|
AND date IN (?, ?, ?, ?, ?)
|
|
`
|
|
|
|
var count int
|
|
err := db.QueryRow(query, userID,
|
|
dateList[0], dateList[1], dateList[2], dateList[3], dateList[4]).Scan(&count)
|
|
|
|
if err != nil {
|
|
log.Printf("Error checking entries: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|