TTime/backend/internal/database/db.go

326 lines
9.7 KiB
Go
Raw Normal View History

2024-02-12 12:40:49 +01:00
package database
import (
"embed"
"path/filepath"
"ttime/internal/types"
2024-02-12 12:40:49 +01:00
"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite"
2024-02-12 12:40:49 +01:00
)
2024-02-29 20:33:20 +01:00
// Interface for the database
type Database interface {
2024-03-12 20:44:40 +01:00
// Insert a new user into the database, password should be hashed before calling
2024-02-29 20:33:20 +01:00
AddUser(username string, password string) error
2024-03-17 01:32:10 +01:00
CheckUser(username string, password string) bool
2024-02-29 20:33:20 +01:00
RemoveUser(username string) error
PromoteToAdmin(username string) error
2024-02-29 20:33:20 +01:00
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
2024-03-17 14:38:20 +01:00
Migrate() error
GetProjectId(projectname string) (int, error)
2024-03-16 22:47:19 +01:00
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
2024-03-14 13:39:56 +01:00
AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error)
GetAllUsersApplication() ([]string, error)
GetProjectsForUser(username string) ([]types.Project, error)
GetAllProjects() ([]types.Project, error)
GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
2024-02-29 20:33:20 +01:00
}
2024-02-27 05:00:04 +01:00
// This struct is a wrapper type that holds the database connection
// Internally DB holds a connection pool, so it's safe for concurrent use
type Db struct {
*sqlx.DB
}
type UserProjectMember struct {
Username string `db:"username"`
UserRole string `db:"p_role"`
}
//go:embed migrations
var scripts embed.FS
2024-03-14 13:39:56 +01:00
// TODO: Possibly break these out into separate files bundled with the embed package?
2024-02-27 05:00:04 +01:00
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
2024-03-16 22:47:19 +01:00
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
2024-03-14 13:39:56 +01:00
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
2024-03-16 22:47:19 +01:00
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
2024-03-14 13:39:56 +01:00
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
2024-03-16 22:47:19 +01:00
const getProjectsForUser = `SELECT projects.id, projects.name, projects.description, projects.owner_user_id
FROM projects JOIN user_roles ON projects.id = user_roles.project_id
JOIN users ON user_roles.user_id = users.id WHERE users.username = ?;`
2024-02-27 05:00:04 +01:00
// DbConnect connects to the database
2024-02-29 20:33:20 +01:00
func DbConnect(dbpath string) Database {
2024-02-12 12:40:49 +01:00
// Open the database
db, err := sqlx.Connect("sqlite", dbpath)
2024-02-12 12:40:49 +01:00
if err != nil {
panic(err)
}
2024-02-27 05:00:04 +01:00
// Ping forces the connection to be established
2024-02-12 12:40:49 +01:00
err = db.Ping()
if err != nil {
panic(err)
}
2024-02-27 05:00:04 +01:00
return &Db{db}
}
2024-03-17 01:32:10 +01:00
func (d *Db) CheckUser(username string, password string) bool {
var dbPassword string
err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
if err != nil {
return false
}
return dbPassword == password
}
2024-03-15 16:57:42 +01:00
// GetProjectsForUser retrieves all projects associated with a specific user.
func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) {
var projects []types.Project
err := d.Select(&projects, getProjectsForUser, username)
return projects, err
}
2024-03-15 16:57:42 +01:00
// GetAllProjects retrieves all projects from the database.
func (d *Db) GetAllProjects() ([]types.Project, error) {
var projects []types.Project
err := d.Select(&projects, "SELECT * FROM projects")
return projects, err
}
2024-03-15 16:57:42 +01:00
// GetProject retrieves a specific project by its ID.
func (d *Db) GetProject(projectId int) (types.Project, error) {
var project types.Project
err := d.Select(&project, "SELECT * FROM projects WHERE id = ?")
return project, err
}
2024-03-16 22:47:19 +01:00
func (d *Db) AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error {
_, err := d.Exec(addWeeklyReport, userName, projectName, week, developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime)
2024-03-07 21:57:27 +01:00
return err
}
2024-03-15 16:57:42 +01:00
// AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
2024-03-14 13:39:56 +01:00
_, err3 := d.Exec(addUserToProject, userid, projectid, role)
return err3
2024-03-07 21:57:27 +01:00
}
2024-03-07 20:58:50 +01:00
2024-03-15 16:57:42 +01:00
// ChangeUserRole changes the role of a user within a project.
2024-03-14 13:39:56 +01:00
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
2024-03-15 16:57:42 +01:00
// Get the user ID
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
2024-03-07 20:58:50 +01:00
2024-03-15 16:57:42 +01:00
// Get the project ID
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
2024-03-15 16:57:42 +01:00
// Execute the SQL query to change the user's role
2024-03-14 13:39:56 +01:00
_, err3 := d.Exec(changeUserRole, role, userid, projectid)
return err3
}
2024-03-07 20:58:50 +01:00
2024-03-15 16:57:42 +01:00
// GetUserRole retrieves the role of a user within a project.
func (d *Db) GetUserRole(username string, projectname string) (string, error) {
var role string
err := d.Get(&role, "SELECT p_role FROM user_roles WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)", username, projectname)
return role, err
}
2024-02-27 05:00:04 +01:00
// AddUser adds a user to the database
func (d *Db) AddUser(username string, password string) error {
_, err := d.Exec(userInsert, username, password)
return err
}
// Removes a user from the database
func (d *Db) RemoveUser(username string) error {
_, err := d.Exec("DELETE FROM users WHERE username = ?", username)
return err
2024-02-12 12:40:49 +01:00
}
func (d *Db) PromoteToAdmin(username string) error {
_, err := d.Exec(promoteToAdmin, username)
return err
}
func (d *Db) GetUserId(username string) (int, error) {
var id int
err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular
return id, err
}
func (d *Db) GetProjectId(projectname string) (int, error) {
var id int
err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname)
return id, err
}
// Creates a new project in the database, associated with a user
func (d *Db) AddProject(name string, description string, username string) error {
_, err := d.Exec(projectInsert, name, description, username)
return err
}
func (d *Db) GetAllUsersProject(projectname string) ([]UserProjectMember, error) {
// Define the SQL query to fetch users and their roles for a given project
query := `
SELECT u.username, ur.p_role
FROM users u
INNER JOIN user_roles ur ON u.id = ur.user_id
INNER JOIN projects p ON ur.project_id = p.id
WHERE p.name = ?
`
// Execute the query
rows, err := d.Queryx(query, projectname)
if err != nil {
return nil, err
}
defer rows.Close()
// Iterate over the rows and populate the result slice
var users []UserProjectMember
for rows.Next() {
var user UserProjectMember
if err := rows.StructScan(&user); err != nil {
return nil, err
}
users = append(users, user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
// GetAllUsersApplication retrieves all usernames from the database
func (d *Db) GetAllUsersApplication() ([]string, error) {
// Define the SQL query to fetch all usernames
query := `
SELECT username FROM users
`
// Execute the query
rows, err := d.Queryx(query)
if err != nil {
return nil, err
}
defer rows.Close()
// Iterate over the rows and populate the result slice
var usernames []string
for rows.Next() {
var username string
if err := rows.Scan(&username); err != nil {
return nil, err
}
usernames = append(usernames, username)
}
if err := rows.Err(); err != nil {
return nil, err
}
return usernames, nil
}
func (d *Db) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) {
var report types.WeeklyReport
query := `
SELECT
report_id,
user_id,
project_id,
week,
development_time,
meeting_time,
admin_time,
own_work_time,
study_time,
testing_time
FROM
weekly_reports
WHERE
user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)
AND week = ?
`
err := d.Get(&report, query, username, projectName, week)
return report, err
}
// Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory
2024-03-17 14:38:20 +01:00
func (d *Db) Migrate() error {
// Read the embedded scripts directory
files, err := scripts.ReadDir("migrations")
if err != nil {
return err
}
2024-03-17 14:38:20 +01:00
if len(files) == 0 {
println("No migration files found")
return nil
}
tr := d.MustBegin()
// Iterate over each SQL file and execute it
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".sql" {
continue
}
// This is perhaps not the most elegant way to do this
2024-03-17 14:38:20 +01:00
sqlBytes, err := scripts.ReadFile("migrations/" + file.Name())
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
2024-03-02 04:29:50 +01:00
if tr.Commit() != nil {
return err
}
return nil
}