diff --git a/backend/Makefile b/backend/Makefile index da0e254..9cfa335 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -118,7 +118,3 @@ uml: plantuml.jar install-just: @echo "Installing just" @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin - -.PHONY: types -types: - tygo generate \ No newline at end of file diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index c13308b..b5e1981 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -21,14 +21,13 @@ type Database interface { AddProject(name string, description string, username string) error Migrate(dirname string) error GetProjectId(projectname string) (int, error) - AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) 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) GetProjectsForUser(username string) ([]types.Project, error) GetAllProjects() ([]types.Project, error) - GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) } @@ -52,8 +51,8 @@ const projectInsert = "INSERT INTO projects (name, description, owner_user_id) S 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, activity_type, start, end) - VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?);` + 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 = ?" @@ -89,34 +88,23 @@ func DbConnect(dbpath string) Database { return &Db{db} } -// 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 } -// 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 } -// 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 -} - -// AddTimeReport adds a time report for a specific project and user. -func (d *Db) AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error { // WIP - _, err := d.Exec(addTimeReport, userName, projectName, activityType, start, end) +func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP + _, err := d.Exec(addTimeReport, userName, projectName, start, end) return err } -// 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) @@ -134,28 +122,23 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err3 } -// ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - // Get the user ID var userid int userid, err := d.GetUserId(username) if err != nil { panic(err) } - // Get the project ID var projectid int projectid, err2 := d.GetProjectId(projectname) if err2 != nil { panic(err2) } - // Execute the SQL query to change the user's role _, err3 := d.Exec(changeUserRole, role, userid, projectid) return err3 } -// 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) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 9118e2f..7650739 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -112,7 +112,7 @@ func TestAddTimeReport(t *testing.T) { var now = time.Now() var then = now.Add(time.Hour) - err = db.AddTimeReport("testproject", "testuser", "activity", now, then) + err = db.AddTimeReport("testproject", "testuser", now, then) if err != nil { t.Error("AddTimeReport failed:", err) } @@ -137,7 +137,7 @@ func TestAddUserToProject(t *testing.T) { var now = time.Now() var then = now.Add(time.Hour) - err = db.AddTimeReport("testproject", "testuser", "activity", now, then) + err = db.AddTimeReport("testproject", "testuser", now, then) if err != nil { t.Error("AddTimeReport failed:", err) } @@ -343,38 +343,3 @@ func TestGetProjectsForUser(t *testing.T) { t.Error("GetProjectsForUser failed: expected 1, got", len(projects)) } } - -func TestAddProject(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) - } - - // Retrieve the added project to verify its existence - projects, err := db.GetAllProjects() - if err != nil { - t.Error("GetAllProjects failed:", err) - } - - // Check if the project was added successfully - found := false - for _, project := range projects { - if project.Name == "testproject" { - found = true - break - } - } - if !found { - t.Error("Added project not found") - } -} diff --git a/backend/internal/database/migrations/0030_time_reports.sql b/backend/internal/database/migrations/0030_time_reports.sql index 7c169c2..76812a1 100644 --- a/backend/internal/database/migrations/0030_time_reports.sql +++ b/backend/internal/database/migrations/0030_time_reports.sql @@ -2,12 +2,10 @@ CREATE TABLE IF NOT EXISTS time_reports ( id INTEGER PRIMARY KEY, project_id INTEGER NOT NULL, user_id INTEGER NOT NULL, - activity_type TEXT 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 - FOREIGN KEY (activity_type) REFERENCES activity_types (name) ON DELETE CASCADE ); CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end diff --git a/backend/internal/database/migrations/0070_salts.sql b/backend/internal/database/migrations/0070_salts.sql index de9757d..b84dfac 100644 --- a/backend/internal/database/migrations/0070_salts.sql +++ b/backend/internal/database/migrations/0070_salts.sql @@ -1,7 +1,7 @@ -- It is unclear weather this table will be used -- Create the table to store hash salts -CREATE TABLE IF NOT EXISTS salts ( +CREATE TABLE salts ( id INTEGER PRIMARY KEY, salt TEXT NOT NULL ); diff --git a/backend/internal/database/migrations/0080_activity_types.sql b/backend/internal/database/migrations/0080_activity_types.sql deleted file mode 100644 index d984d58..0000000 --- a/backend/internal/database/migrations/0080_activity_types.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS activity_types ( - name TEXT PRIMARY KEY -); - -INSERT OR IGNORE INTO activity_types (name) VALUES ('Development'); -INSERT OR IGNORE INTO activity_types (name) VALUES ('Meeting'); -INSERT OR IGNORE INTO activity_types (name) VALUES ('Administration'); -INSERT OR IGNORE INTO activity_types (name) VALUES ('Own Work'); -INSERT OR IGNORE INTO activity_types (name) VALUES ('Studies'); -INSErt OR IGNORE INTO activity_types (name) VALUES ('Testing'); \ No newline at end of file diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 91d46a9..fea0dfd 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,7 +1,6 @@ package handlers import ( - "strconv" "time" "ttime/internal/database" "ttime/internal/types" @@ -226,24 +225,3 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { // Return a success message return c.SendStatus(fiber.StatusOK) } - -// GetProject retrieves a specific project by its ID -func (gs *GState) GetProject(c *fiber.Ctx) error { - // Extract the project ID from the request parameters or body - projectID := c.Params("projectID") - - // Parse the project ID into an integer - projectIDInt, err := strconv.Atoi(projectID) - if err != nil { - return c.Status(400).SendString("Invalid project ID") - } - - // Get the project from the database by its ID - project, err := gs.Db.GetProject(projectIDInt) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the project as JSON - return c.JSON(project) -} diff --git a/backend/tygo.yaml b/backend/tygo.yaml deleted file mode 100644 index 54c1e8f..0000000 --- a/backend/tygo.yaml +++ /dev/null @@ -1,9 +0,0 @@ -packages: - - path: "ttime/internal/types" - output_path: "../frontend/src/Types/goTypes.ts" - type_mappings: - time.Time: "string /* RFC3339 */" - null.String: "null | string" - null.Bool: "null | boolean" - uuid.UUID: "string /* uuid */" - uuid.NullUUID: "null | string /* uuid */" diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 1051c03..447a464 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -9,7 +9,7 @@ module.exports = { 'plugin:react-hooks/recommended', 'plugin:prettier/recommended', ], - ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'jest.config.cjs', 'goTypes.ts'], + ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'jest.config.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh', 'prettier'], rules: { diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 248ad37..f33c87c 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,120 +1,57 @@ import { NewProject, Project } from "../Types/Project"; import { NewUser, User } from "../Types/Users"; -// This type of pattern should be hard to misuse -interface APIResponse { - success: boolean; - message?: string; - data?: T; -} - -// Note that all protected routes also require a token // Defines all the methods that an instance of the API must implement interface API { /** Register a new user */ - registerUser(user: NewUser): Promise>; + registerUser(user: NewUser): Promise; /** Remove a user */ - removeUser(username: string, token: string): Promise>; + removeUser(username: string): Promise; /** Create a project */ - createProject( - project: NewProject, - token: string, - ): Promise>; + createProject(project: NewProject): Promise; /** Renew the token */ - renewToken(token: string): Promise>; + renewToken(token: string): Promise; } // Export an instance of the API export const api: API = { - async registerUser(user: NewUser): Promise> { - try { - const response = await fetch("/api/register", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(user), - }); - - if (!response.ok) { - return { success: false, message: "Failed to register user" }; - } else { - const data = (await response.json()) as User; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to register user" }; - } + async registerUser(user: NewUser): Promise { + return fetch("/api/register", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(user), + }).then((res) => res.json() as Promise); }, - async removeUser( - username: string, - token: string, - ): Promise> { - try { - const response = await fetch("/api/userdelete", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(username), - }); - - if (!response.ok) { - return { success: false, message: "Failed to remove user" }; - } else { - const data = (await response.json()) as User; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to remove user" }; - } + async removeUser(username: string): Promise { + return fetch("/api/userdelete", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(username), + }).then((res) => res.json() as Promise); }, - async createProject( - project: NewProject, - token: string, - ): Promise> { - try { - const response = await fetch("/api/project", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(project), - }); - - if (!response.ok) { - return { success: false, message: "Failed to create project" }; - } else { - const data = (await response.json()) as Project; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to create project" }; - } + 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> { - try { - const response = await fetch("/api/loginrenew", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to renew token" }; - } else { - const data = (await response.json()) as string; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to renew token" }; - } + 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/Register.tsx b/frontend/src/Components/Register.tsx index 0c0fcd0..e4a3ba0 100644 --- a/frontend/src/Components/Register.tsx +++ b/frontend/src/Components/Register.tsx @@ -4,32 +4,6 @@ import { api } from "../API/API"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; -function InputField(props: { - label: string; - type: string; - value: string; - onChange: (e: React.ChangeEvent) => void; -}): JSX.Element { - return ( -
- - -
- ); -} - export default function Register(): JSX.Element { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -40,7 +14,7 @@ export default function Register(): JSX.Element { }; return ( -
+
Register New User - { - setUsername(e.target.value); - }} - /> - { - setPassword(e.target.value); - }} - /> +
+ + { + setUsername(e.target.value); + }} + /> +
+
+ + { + setPassword(e.target.value); + }} + /> +
- + 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/AdminAddUser.tsx b/frontend/src/Pages/AdminPages/AdminAddUser.tsx index c0f9492..5e8c01f 100644 --- a/frontend/src/Pages/AdminPages/AdminAddUser.tsx +++ b/frontend/src/Pages/AdminPages/AdminAddUser.tsx @@ -1,16 +1,18 @@ import BasicWindow from "../../Components/BasicWindow"; import Button from "../../Components/Button"; -import Register from "../../Components/Register"; function AdminAddUser(): JSX.Element { - const content = ( - <> - - - ); + const content = <>; const buttons = ( <> +