diff --git a/.gitignore b/.gitignore index bdbfff8..2d89407 100644 --- a/.gitignore +++ b/.gitignore @@ -6,13 +6,8 @@ *.dylib bin -database.txt -plantuml.jar db.sqlite3 -diagram.puml -backend/*.png -backend/*.jpg -backend/*.svg +*.png # Test binary, built with `go test -c` *.test diff --git a/README.md b/README.md index e7b2dfe..d75861a 100644 --- a/README.md +++ b/README.md @@ -62,21 +62,6 @@ You should consult the [WSL documentation](https://docs.microsoft.com/en-us/wind wsl --install -d Ubuntu-22.04 # To get a somewhat recent version of Go ``` -After this, you can open a (wsl) terminal and run the commands: - -```bash -sudo apt update && sudo apt upgrade -sudo apt install -y make podman - -sudo add-apt-repository ppa:longsleep/golang-backports -sudo apt update -sudo apt install golang-go - -# For a recent version of node: -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -nvm install node -``` - If you get any errors related to virtualization, you will need to enable virtualization in the BIOS. This is a common issue, and you can find a guide for your specific motherboard online. This is a one-time operation and will not affect your windows installation. This setting is usually called "VT-x" or "AMD-V" and is usually found in the CPU settings. If you can't find it, shoot me a message and I'll find it for you. If you're **still dead set** on using a vanilla Windows environment, you will need the following: diff --git a/backend/Makefile b/backend/Makefile index 9cfa335..d005846 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -27,10 +27,6 @@ clean: $(GOCLEAN) rm -rf bin rm -f db.sqlite3 - rm -f diagram* - rm -f plantuml.jar - rm -f erd.png - rm -f config.toml # Test target test: db.sqlite3 @@ -58,9 +54,6 @@ migrate: db.sqlite3: make migrate -dbdump: - sqlite3 $(DB_FILE) .dump > database.txt - backup: mkdir -p backups sqlite3 $(DB_FILE) .dump | gzip -9 > ./backups/BACKUP_$(DB_FILE)_$(shell date +"%Y-%m-%d_%H:%M:%S").sql.gz @@ -102,18 +95,6 @@ install-lint: @echo "Installing golangci-lint" @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.42.1 -# Fetches the latest plantuml.jar and checks its SHA256 hash -plantuml.jar: - curl -sSfL https://github.com/plantuml/plantuml/releases/download/v1.2024.3/plantuml.jar -o plantuml.jar \ - && echo "519a4a7284c6a0357c369e4bb0caf72c4bfbbde851b8c6d6bbdb7af3c01fc82f plantuml.jar" | sha256sum -c - -# Generate UML diagrams diagral.png & diagram.svg -.PHONY: uml -uml: plantuml.jar - goplantuml -recursive . > diagram.puml - java -jar plantuml.jar -tpng diagram.puml - java -jar plantuml.jar -tsvg diagram.puml - # Convenience target to install just (requires sudo privileges) install-just: @echo "Installing just" diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index f05530a..5a88873 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -14,17 +14,18 @@ import ( type Database interface { // Insert a new user into the database, password should be hashed before calling AddUser(username string, password string) error + RemoveUser(username string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error Migrate(dirname string) error - GetProjectId(projectname string) (int, error) - AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error - AddUserToProject(username string, projectname string, role string) error - ChangeUserRole(username string, projectname string, role string) error - GetAllUsersProject(projectname string) ([]UserProjectMember, error) - GetAllUsersApplication() ([]string, error) + // AddTimeReport(projectname string, start time.Time, end time.Time) error + // AddUserToProject(username string, projectname string) error + // ChangeUserRole(username string, projectname string, role string) error + // AddTimeReport(projectname string, start time.Time, end time.Time) error + // AddUserToProject(username string, projectname string) error + // ChangeUserRole(username string, projectname string, role string) error } // This struct is a wrapper type that holds the database connection @@ -33,24 +34,15 @@ type Db struct { *sqlx.DB } -type UserProjectMember struct { - Username string `db:"username"` - UserRole string `db:"p_role"` -} - //go:embed migrations var scripts embed.FS -// TODO: Possibly break these out into separate files bundled with the embed package? 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 = ?" -const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), - ProjectLookup AS (SELECT id FROM projects WHERE name = ?) - INSERT INTO time_reports (project_id, user_id, start, end) - VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup), ?, ?);` -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 = ?" +const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP +const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)" // WIP +// const changeUserRole = "" // DbConnect connects to the database func DbConnect(dbpath string) Database { @@ -69,8 +61,8 @@ func DbConnect(dbpath string) Database { return &Db{db} } -func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP - _, err := d.Exec(addTimeReport, userName, projectName, start, end) +func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP + _, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false) return err } @@ -87,26 +79,13 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) panic(err2) } - _, err3 := d.Exec(addUserToProject, userid, projectid, role) + _, err3 := d.Exec(addUserToProject, projectid, userid, role) return err3 } -func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - var userid int - userid, err := d.GetUserId(username) - if err != nil { - panic(err) - } +// func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - var projectid int - projectid, err2 := d.GetProjectId(projectname) - if err2 != nil { - panic(err2) - } - - _, err3 := d.Exec(changeUserRole, role, userid, projectid) - return err3 -} +// } // AddUser adds a user to the database func (d *Db) AddUser(username string, password string) error { @@ -131,9 +110,9 @@ func (d *Db) GetUserId(username string) (int, error) { return id, err } -func (d *Db) GetProjectId(projectname string) (int, error) { +func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof var id int - err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname) + err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname) return id, err } @@ -143,69 +122,6 @@ func (d *Db) AddProject(name string, description string, username string) error 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 -} - // Reads a directory of migration files and applies them to the database. // This will eventually be used on an embedded directory func (d *Db) Migrate(dirname string) error { diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index e5aceb2..96eb9b7 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -2,7 +2,6 @@ package database import ( "testing" - "time" ) // Tests are not guaranteed to be sequential @@ -93,196 +92,14 @@ func TestPromoteToAdmin(t *testing.T) { } } -func TestAddTimeReport(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } +// func TestAddTimeReport(t *testing.T) { - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } +// } - err = db.AddProject("testproject", "description", "testuser") - if err != nil { - t.Error("AddProject failed:", err) - } +// func TestAddUserToProject(t *testing.T) { - var now = time.Now() - var then = now.Add(time.Hour) +// } - err = db.AddTimeReport("testproject", "testuser", now, then) - if err != nil { - t.Error("AddTimeReport failed:", err) - } -} +// func TestChangeUserRole(t *testing.T) { -func TestAddUserToProject(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddProject("testproject", "description", "testuser") - if err != nil { - t.Error("AddProject failed:", err) - } - - var now = time.Now() - var then = now.Add(time.Hour) - - err = db.AddTimeReport("testproject", "testuser", now, then) - if err != nil { - t.Error("AddTimeReport failed:", err) - } - - err = db.AddUserToProject("testuser", "testproject", "user") - if err != nil { - t.Error("AddUserToProject failed:", err) - } -} - -func TestChangeUserRole(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddProject("testproject", "description", "testuser") - if err != nil { - t.Error("AddProject failed:", err) - } - - err = db.AddUserToProject("testuser", "testproject", "user") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - - err = db.ChangeUserRole("testuser", "testproject", "admin") - if err != nil { - t.Error("ChangeUserRole failed:", err) - } -} - -func TestGetAllUsersProject(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser1", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddUser("testuser2", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddProject("testproject", "description", "testuser1") - if err != nil { - t.Error("AddProject failed:", err) - } - - err = db.AddUserToProject("testuser1", "testproject", "project_manager") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - - err = db.AddUserToProject("testuser2", "testproject", "user") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - - users, err := db.GetAllUsersProject("testproject") - if err != nil { - t.Error("GetAllUsersProject failed:", err) - } - - // Check if both users are returned with their roles - if len(users) != 2 { - t.Errorf("Expected 2 users, got %d", len(users)) - } - - // Check if testuser1 has project manager role - foundProjectManager := false - for _, user := range users { - if user.Username == "testuser1" && user.UserRole == "project_manager" { - foundProjectManager = true - break - } - } - if !foundProjectManager { - t.Error("Project Manager user not found") - } - - // Check if testuser2 has user role - foundUser := false - for _, user := range users { - if user.Username == "testuser2" && user.UserRole == "user" { - foundUser = true - break - } - } - if !foundUser { - t.Error("User user not found") - } -} - -func TestGetAllUsersApplication(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser1", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddUser("testuser2", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - users, err := db.GetAllUsersApplication() - if err != nil { - t.Error("GetAllUsersApplication failed:", err) - } - - // Check if both users are returned - if len(users) != 2 { - t.Errorf("Expected 2 users, got %d", len(users)) - } - - // Check if the test users are included in the list - foundTestUser1 := false - foundTestUser2 := false - for _, user := range users { - if user == "testuser1" { - foundTestUser1 = true - } - if user == "testuser2" { - foundTestUser2 = true - } - } - - if !foundTestUser1 { - t.Error("testuser1 not found") - } - if !foundTestUser2 { - t.Error("testuser2 not found") - } -} +// } diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index 58d8e97..adfb818 100644 --- a/backend/internal/database/migrations/0020_projects.sql +++ b/backend/internal/database/migrations/0020_projects.sql @@ -1,9 +1,11 @@ CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY, + projectId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, owner_user_id INTEGER NOT NULL, FOREIGN KEY (owner_user_id) REFERENCES users (id) ); +CREATE INDEX IF NOT EXISTS projects_projectId_index ON projects (projectId); CREATE INDEX IF NOT EXISTS projects_user_id_index ON projects (owner_user_id); \ No newline at end of file diff --git a/backend/internal/database/migrations/0030_time_reports.sql b/backend/internal/database/migrations/0030_time_reports.sql index 76812a1..e8f3ec1 100644 --- a/backend/internal/database/migrations/0030_time_reports.sql +++ b/backend/internal/database/migrations/0030_time_reports.sql @@ -1,11 +1,10 @@ CREATE TABLE IF NOT EXISTS time_reports ( id INTEGER PRIMARY KEY, + reportId TEXT DEFAULT (HEX(RANDOMBLOB(6))) NOT NULL UNIQUE, project_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, start DATETIME NOT NULL, end DATETIME NOT NULL, FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ); CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end diff --git a/backend/internal/database/migrations/0049_project_role.sql b/backend/internal/database/migrations/0049_project_role.sql index f7e7151..8716800 100644 --- a/backend/internal/database/migrations/0049_project_role.sql +++ b/backend/internal/database/migrations/0049_project_role.sql @@ -5,5 +5,5 @@ CREATE TABLE IF NOT EXISTS project_role ( ); -- Insert the possible roles a user can have in a project. -INSERT OR IGNORE INTO project_role (p_role) VALUES ('project_manager'); +INSERT OR IGNORE INTO project_role (p_role) VALUES ('admin'); INSERT OR IGNORE INTO project_role (p_role) VALUES ('member'); diff --git a/backend/internal/database/migrations/0050_user_roles.sql b/backend/internal/database/migrations/0050_user_roles.sql index d3e614d..aad25f7 100644 --- a/backend/internal/database/migrations/0050_user_roles.sql +++ b/backend/internal/database/migrations/0050_user_roles.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS user_roles ( user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, - p_role TEXT NOT NULL, -- 'project_manager' or 'member' + p_role TEXT NOT NULL, -- 'admin' or 'member' FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (project_id) REFERENCES projects (id) FOREIGN KEY (p_role) REFERENCES project_role (p_role) diff --git a/backend/main.go b/backend/main.go index bba3fa6..1aaca45 100644 --- a/backend/main.go +++ b/backend/main.go @@ -71,7 +71,6 @@ func main() { server.Post("/api/loginrenew", gs.LoginRenew) server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches - server.Post("/api/project", gs.CreateProject) // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index f33c87c..2dbd51e 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,4 +1,3 @@ -import { NewProject, Project } from "../Types/Project"; import { NewUser, User } from "../Types/Users"; // Defines all the methods that an instance of the API must implement @@ -7,10 +6,6 @@ interface API { registerUser(user: NewUser): Promise; /** Remove a user */ removeUser(username: string): Promise; - /** Create a project */ - createProject(project: NewProject): Promise; - /** Renew the token */ - renewToken(token: string): Promise; } // Export an instance of the API @@ -34,24 +29,4 @@ export const api: API = { body: JSON.stringify(username), }).then((res) => res.json() as Promise); }, - - async createProject(project: NewProject): Promise { - return fetch("/api/project", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(project), - }).then((res) => res.json() as Promise); - }, - - async renewToken(token: string): Promise { - return fetch("/api/loginrenew", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }).then((res) => res.json() as Promise); - }, }; diff --git a/frontend/src/Components/Header.tsx b/frontend/src/Components/Header.tsx index ba0a939..5c642b8 100644 --- a/frontend/src/Components/Header.tsx +++ b/frontend/src/Components/Header.tsx @@ -15,7 +15,7 @@ function Header({ username }: { username: string }): JSX.Element { > TTIME Logo diff --git a/frontend/src/Components/Register.tsx b/frontend/src/Components/Register.tsx deleted file mode 100644 index d0e3da6..0000000 --- a/frontend/src/Components/Register.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useState } from "react"; -import { NewUser } from "../Types/Users"; -import { api } from "../API/API"; - -export default function Register(): JSX.Element { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - - const handleRegister = async (): Promise => { - const newUser: NewUser = { userName: username, password }; - await api.registerUser(newUser); // TODO: Handle errors - }; - - return ( -
-
-
{ - e.preventDefault(); - void handleRegister(); - }} - > -

Register new user

-
- - { - setUsername(e.target.value); - }} - /> -
-
- - { - setPassword(e.target.value); - }} - /> -
-
- -
-
-

-
-
- ); -} diff --git a/frontend/src/Components/TimeReport.tsx b/frontend/src/Components/TimeReport.tsx deleted file mode 100644 index c4ddc38..0000000 --- a/frontend/src/Components/TimeReport.tsx +++ /dev/null @@ -1,59 +0,0 @@ -function NewTimeReport(): JSX.Element { - const activities = [ - "Development", - "Meeting", - "Administration", - "Own Work", - "Studies", - "Testing", - ]; - - return ( - <> -
- { - event.preventDefault(); - }} - onPaste={(event) => { - event.preventDefault(); - }} - /> - - - - - - - - - {activities.map((activity, index) => ( - - - - - ))} - -
Activity - Total Time (min) -
{activity} - { - const keyValue = event.key; - if (!/\d/.test(keyValue) && keyValue !== "Backspace") - event.preventDefault(); - }} - /> -
-
- - ); -} - -export default NewTimeReport; diff --git a/frontend/src/Pages/AdminPages/AdminAddProject.tsx b/frontend/src/Pages/AdminPages/AdminAddProject.tsx deleted file mode 100644 index 9fd8bed..0000000 --- a/frontend/src/Pages/AdminPages/AdminAddProject.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import BasicWindow from "../../Components/BasicWindow"; -import Button from "../../Components/Button"; - -function AdminAddProject(): JSX.Element { - const content = <>; - - const buttons = ( - <> -