Compare commits

...

999 commits

Author SHA1 Message Date
Peter KW
2f8e7547da Merge branch 'frontend' into gruppPP 2024-04-17 23:06:26 +02:00
Peter KW
3be2319bce ChangeUserPassword.tsx created and implemented + minor fix in API.ts 2024-04-17 23:04:27 +02:00
Peter KW
8948067514 ChangeProjectName.tsx created and implemented + minor fix in main.go api paths 2024-04-17 23:03:32 +02:00
Peter KW
99eb5f17b5 Merge remote-tracking branch 'origin/dev' into gruppPP 2024-04-17 22:29:45 +02:00
al8763be
0176f78067 ChangeUserPassword Handler + API 2024-04-17 22:04:23 +02:00
al8763be
47d4bda99b ChangeProjectName Handler + API 2024-04-17 21:31:47 +02:00
Mattias
97ea6713a4 Update hover effect on menu items and user links 2024-04-15 16:45:45 +02:00
Mattias
2bc3b1b665 Update hover effect on menu items and user links 2024-04-15 16:38:17 +02:00
Mattias
bbac1ed4c8 Added hovering effect to menu items 2024-04-15 16:29:26 +02:00
Mattias
eeb17eabb1 Refactor UserViewStatistics component to remove unnecessary heading 2024-04-15 16:12:56 +02:00
Davenludd
a5a94c7839 Update UserStatistics component to include username parameter in getStatistics API call 2024-04-15 12:02:40 +02:00
Davenludd
128221a6ed Add token parameter to getStatistics API method 2024-04-15 12:02:22 +02:00
Davenludd
90d24ad7c4 Merge branch 'dev' into gruppDM 2024-04-15 11:59:40 +02:00
Imbus
6c4fe7bda3 Statistics api again 2024-04-15 11:58:43 +02:00
Davenludd
cdc070e5a6 Merge branch 'dev' into gruppDM 2024-04-15 11:54:16 +02:00
Imbus
d46c5fc05f Statistics TS API 2024-04-15 11:50:38 +02:00
Davenludd
5033b657a4 Merge branch 'dev' into gruppDM 2024-04-15 11:45:28 +02:00
Imbus
a3f16e713a Statistics 2024-04-15 11:44:19 +02:00
Davenludd
1fd167d412 Merge branch 'dev' into gruppDM 2024-04-15 11:30:17 +02:00
Imbus
b51a00b723 Statistics path rework 2024-04-15 11:09:51 +02:00
Davenludd
e1aefb9934 Update button text 2024-04-14 16:22:17 +02:00
Davenludd
3cd2c7ba40 Update Total Time In Project button text to include project name 2024-04-14 16:12:47 +02:00
Davenludd
f8ca68dbea Update Total Time In Project button text to include username and project name 2024-04-14 16:08:52 +02:00
Davenludd
9148ede977 Update UserViewTimeReportsPage to include statistics-page 2024-04-14 16:03:53 +02:00
Davenludd
78231eaae9 Update routing path 2024-04-14 16:00:29 +02:00
Davenludd
482c732b3d Update UserViewTimeReportsPage to include username in Total Time In Project button link 2024-04-14 15:58:27 +02:00
Davenludd
a25413affa Add total time in TimePerActivity component 2024-04-14 15:52:13 +02:00
Davenludd
d1b6866c39 Fix UserStatistics component to use new API.getStatistics function 2024-04-14 15:47:26 +02:00
Davenludd
c96c0d7b4d Fix order of parameters in getStatistics function in API.ts 2024-04-14 15:47:01 +02:00
Davenludd
97f810fce2 Merge branch 'frontend' into gruppDM 2024-04-14 15:30:37 +02:00
Davenludd
8e60ba405b Update UserViewTimeReportsPage to include Total Time In Project button 2024-04-14 15:25:13 +02:00
Davenludd
6c64dfcb0e Add UserViewStatistics component to UserPages 2024-04-14 15:24:52 +02:00
Davenludd
ac5e67d77f Update button text in PMProjectMembers component 2024-04-14 15:24:42 +02:00
Davenludd
48dd53b56b Add UserStatistics component to show total time per activity in a project 2024-04-14 15:24:10 +02:00
Davenludd
980ac19199 Add UserViewStatistics component to main.tsx 2024-04-14 15:23:42 +02:00
Peter KW
b6d9b51865 Merge branch 'frontend' into gruppPP 2024-04-14 11:58:16 +02:00
Imbus
c7e036ec04 Major db/API refactor, transaction as default 2024-04-14 09:21:34 +02:00
Imbus
fe9d5f74bb getStatistics handler, db-interface and ts API 2024-04-14 07:49:39 +02:00
Peter KW
564b2e6f58 Small fix 2024-04-13 21:36:02 +02:00
Peter KW
f66b6a0f0a Now uses new input types and checks if input is allowed + alerts and design changes 2024-04-13 21:18:01 +02:00
Peter KW
2f3f7261a0 Some new inputfield types 2024-04-13 21:14:17 +02:00
Peter KW
a5aac2065d Some constants for the different limits of password, username etc. 2024-04-13 21:13:26 +02:00
Peter KW
395e855257 Some regex to compare with strings 2024-04-13 21:12:46 +02:00
Peter KW
0e2b62f5ba Minor design and import changes 2024-04-13 21:10:37 +02:00
Peter KW
bcac9c020e Checks if new project name meets requirements 2024-04-13 21:09:07 +02:00
Peter KW
ebe0f380c5 Navigation button 2024-04-13 21:07:48 +02:00
Peter KW
6ca7f0d31f Improved alerts for error handling login 2024-04-13 21:07:32 +02:00
Peter KW
88c6757bd3 Checks if user already is selected role + minor design change 2024-04-13 21:07:08 +02:00
Peter KW
35a9c750d1 Routes back to home page on created project + design and alert updates 2024-04-13 20:53:05 +02:00
Peter KW
b71da1a698 Removed unused file + fixed paths 2024-04-13 20:50:23 +02:00
Davenludd
9ec7261a94 Merge branch 'dev' into gruppDM 2024-04-13 16:28:18 +02:00
Imbus
2d2b63938c Fix unsign 2024-04-13 09:36:34 +02:00
Peter KW
d9af77a8f7 Simplified + tiny design changes 2024-04-11 00:42:40 +02:00
Peter KW
43d83ca7b3 Search and design changes 2024-04-11 00:41:44 +02:00
Peter KW
2692127fdf Added some alerts and a new password field 2024-04-11 00:41:17 +02:00
Peter KW
1e0b32d32d Added search and some design changes 2024-04-11 00:39:33 +02:00
Peter KW
72d27bb644 Added search functionality and inputfield for changing p-name 2024-04-11 00:39:06 +02:00
Peter KW
385ceba248 Some "wrong login" alerts 2024-04-11 00:35:25 +02:00
Peter KW
0d4d77633d Prevents admin from changing its name 2024-04-11 00:34:56 +02:00
Peter KW
c8553d4eae Added search functionality and changed design a little 2024-04-11 00:34:27 +02:00
Peter KW
059a322d9e Changed inputfield slightly + update inputfields in files 2024-04-11 00:32:18 +02:00
Peter KW
7ec207bf03 Added more sample data 2024-04-11 00:29:06 +02:00
al8763be
57e969a562 Test for UnsignReport 2024-04-09 22:17:50 +02:00
Davenludd
ff07bd1ed6 Update OtherUsersTR component to include unsign and delete functionality 2024-04-09 22:14:00 +02:00
Davenludd
56e566ea9b Update confirmation message for deleting a user in ProjectMembers component 2024-04-09 20:57:22 +02:00
Davenludd
5e104ec7f9 Merge branch 'BumBranch' into gruppDM 2024-04-09 19:40:35 +02:00
al8763be
67723bfccc DeleteProject Handler + API function, ber till gud att denna funkar first try 2024-04-09 19:08:22 +02:00
Davenludd
1631c8b6b0 Merge branch 'dev' into gruppDM 2024-04-09 18:18:02 +02:00
al8763be
a5e3d4259d unsignReport handler + API function 2024-04-09 17:39:10 +02:00
al8763be
f57c445ead Merge remote-tracking branch 'origin/dev' into BumBranch 2024-04-09 16:11:53 +02:00
al8763be
11341ce37e Merge branch 'master' into BumBranch 2024-04-09 16:11:30 +02:00
Davenludd
a266a6f7fc Update ProjectMembers component to allow deleting users from a project 2024-04-09 10:59:27 +02:00
Davenludd
b56e4ed76e Add Button component to OtherUsersTR and update route path in main.tsx to include signedOrUnsigned parameter 2024-04-09 10:15:19 +02:00
Davenludd
f61449dea1 Update AllTimeReportsInProjectOtherUser component to include signed status in route path 2024-04-09 10:14:48 +02:00
Davenludd
fd7c609e5d Update route path in main.tsx to include signedOrUnsigned parameter 2024-04-09 10:14:34 +02:00
Davenludd
9725a0fc48 Update EditWeeklyReport component to prevent editing of signed reports 2024-04-08 22:57:34 +02:00
Davenludd
6d0775586e Update EditWeeklyReport component to change depending on if the report is signed or not 2024-04-08 22:44:55 +02:00
Davenludd
badeb84282 Update route path in main.tsx to include signedOrUnsigned parameter 2024-04-08 22:43:46 +02:00
Davenludd
e63377effa Update route path in main.tsx to include signedOrUnsigned parameter 2024-04-08 22:43:26 +02:00
Imbus
1fd4cd18e3 Version bumps in frontend 2024-04-05 00:03:49 +02:00
Imbus
9d65764899 Go version bumps 2024-04-04 23:56:35 +02:00
Imbus
a81020f660 Removing verbose flag from testing 2024-04-04 23:26:53 +02:00
Imbus
7e88bd69c1 Killing process if tests fail in itest target 2024-04-04 23:24:38 +02:00
Imbus
d65bbc897d Fixing logic error in paths related to getting reports 2024-04-04 23:23:11 +02:00
Davenludd
7964e0d1d7 Merge branch 'gruppDM' into dev 2024-04-04 23:04:38 +02:00
Davenludd
591d0201cf Update AllTimeReportsInProjectOtherUser component to display signed status correct 2024-04-04 23:03:30 +02:00
Imbus
f7f29241b3 Fixing itest makefile target 2024-04-04 23:02:19 +02:00
Imbus
1f4abc2a6a Linter fixes and proper error handling in PromoteToPm 2024-04-04 23:02:10 +02:00
Davenludd
7635791f09 Merge branch 'frontend' into gruppDM 2024-04-04 22:40:59 +02:00
Imbus
68e6f94d13 Fixing report signing database interface 2024-04-04 22:39:55 +02:00
Davenludd
91bccba871 Add username parameter and error alerts in GetOtherUsersReport component 2024-04-04 22:20:09 +02:00
Davenludd
a6f3fc4a1c Fixed DisplayUserProject component to fetch and display usernames 2024-04-04 22:19:35 +02:00
Davenludd
544383809b Merge branch 'frontend' into gruppDM 2024-04-04 20:10:57 +02:00
Imbus
abfb79b991 getUserName path implemented 2024-04-04 19:54:31 +02:00
Peter KW
db6fdf3c29 Small design fix 2024-04-04 12:27:38 +02:00
Peter KW
5bc7aadfa3 Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-04-04 12:22:55 +02:00
Peter KW
2d8d200340 Can now remove users from projects 2024-04-04 12:22:28 +02:00
Peter KW
0cb6af03e4 Merge remote-tracking branch 'origin/dev' into gruppPP 2024-04-04 12:00:55 +02:00
Davenludd
f75e6e4a6e Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
10d06767c4 Fix for javascript optional parameter formatting error 2024-04-04 11:55:04 +02:00
Imbus
ee49bbde69 Docs 2024-04-04 11:55:04 +02:00
Davenludd
266aaa482c Fix initial state values in TimePerRole component 2024-04-04 11:55:04 +02:00
Davenludd
194e1d52a8 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-04 11:55:04 +02:00
Davenludd
82e72432f6 Update API method to get all weekly reports 2024-04-04 11:55:04 +02:00
Imbus
620073f688 Logic error in getAllWeeklyReports fixed 2024-04-04 11:55:04 +02:00
Imbus
7313a27b31 Rename 2024-04-04 11:55:04 +02:00
Davenludd
d7789ab844 Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
4319f30799 Tests for getAllWeeklyReports 2024-04-04 11:55:03 +02:00
Imbus
2ca8c60418 TS Api for getAllWeeklyReport 2024-04-04 11:55:03 +02:00
Imbus
0a951ecd2b Rename, fix and testing for getAllWeeklyReports path 2024-04-04 11:55:03 +02:00
Peter KW
13eb6597a7 Some fixes to how they handle names and inputs 2024-04-04 11:54:34 +02:00
Peter KW
9a0f855d2b Fixes to adding members 2024-04-04 11:26:39 +02:00
Davenludd
7c995573ef Update error message in NewWeeklyReport component 2024-04-04 11:19:35 +02:00
Mattias
a5ea74c996 Add useNavigate hook and handle navigation based on user "role" 2024-04-04 10:29:25 +02:00
Davenludd
a0ff329845 Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-03 18:36:17 +02:00
Imbus
e67c54540c RemoveUserFromProject handler implemented, corresponding TS api, untested 2024-04-03 18:08:02 +02:00
Davenludd
91671b2b63 Merge branch 'frontend' into gruppDM 2024-04-03 18:07:18 +02:00
Imbus
76ae587116 Fix for javascript optional parameter formatting error 2024-04-03 18:05:56 +02:00
Davenludd
66a064c9f8 Fix initial state values in TimePerRole component 2024-04-03 18:05:30 +02:00
Davenludd
be963b2281 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-03 18:05:19 +02:00
Davenludd
c0d36f8df1 Update API method to get all weekly reports 2024-04-03 18:05:07 +02:00
Imbus
f9c30c4951 Docs 2024-04-03 17:48:02 +02:00
Davenludd
520e2f74f0 Merge branch 'frontend' into gruppDM 2024-04-03 17:45:32 +02:00
Imbus
12810075f9 Logic error in getAllWeeklyReports fixed 2024-04-03 17:44:50 +02:00
Davenludd
8cc0458e1b Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-03 17:44:26 +02:00
Imbus
12a2691d55 Rename 2024-04-03 17:40:48 +02:00
Imbus
9c5aa10414 Tests for getAllWeeklyReports 2024-04-03 17:32:50 +02:00
Imbus
fcd035fe6e TS Api for getAllWeeklyReport 2024-04-03 17:32:07 +02:00
Imbus
a39cfedad3 Rename, fix and testing for getAllWeeklyReports path 2024-04-03 17:31:39 +02:00
Imbus
a1d2520d88 Updating typescript api 2024-04-03 17:12:29 +02:00
Imbus
903132e56d Polishing tests 2024-04-03 17:06:08 +02:00
Imbus
7c973009ac Adding pycache to gitignore 2024-04-03 15:54:27 +02:00
Imbus
ffe5d53625 Splitting test script 2024-04-03 15:53:52 +02:00
Imbus
8ea6dec346 Fixes for various paths 2024-04-03 15:53:36 +02:00
Imbus
61a2d1ce0c PromoteToPm handler 2024-04-03 15:53:15 +02:00
Imbus
9cca8edd9d Additional tests 2024-04-03 14:50:04 +02:00
Imbus
1f9ccca9bf Fixing integration test script 2024-04-03 14:21:55 +02:00
Davenludd
6b880244a1 Merge branch 'gruppDM' into frontend 2024-04-03 14:00:14 +02:00
Davenludd
4d7b3e0d57 Refactor API call and types in AllTimeReportsInProjectOtherUser component 2024-04-03 13:59:44 +02:00
Peter KW
46eb3c76a8 Small fix to statistics 2024-04-02 19:18:05 +02:00
Peter KW
632676e3d2 Added admin to all projects in sample_data 2024-04-02 19:16:02 +02:00
Mattias
1b4e521508 Update variable names in TimePerActivity component 2024-04-02 18:02:40 +02:00
Mattias
a7cc48d392 Refactor TimePerRole component to use API response for time per activity 2024-04-02 18:02:04 +02:00
Davenludd
ff37236cf6 Minor fixes TimePerActivity component to use readOnly input fields 2024-04-02 17:35:02 +02:00
Davenludd
eb741ba20d Add total time calculation to DisplayUnsignedReports component 2024-04-02 17:33:20 +02:00
Davenludd
00ca5514e5 Added sign functionality to component 2024-04-02 17:27:17 +02:00
Mattias
7c7755085e Add comments for getUnsignedReportsInProject in API 2024-04-02 17:11:30 +02:00
Mattias
1e1677fc57 Refactor getUnsignedReports in DisplayUnsignedReports component 2024-04-02 17:07:47 +02:00
Mattias
0b8b430f38 Refactor DisplayUnsignedReports component to use API and WeeklyReport type 2024-04-02 17:05:55 +02:00
Mattias
93659a72dc Add getUnsignedReportsInProject API method 2024-04-02 17:05:46 +02:00
Mattias
8d6da684bf Add username retrieval from local storage 2024-04-02 16:21:23 +02:00
Davenludd
762a1b7368 Merge branch 'frontend' into gruppDM 2024-04-02 15:51:11 +02:00
Mattias
398305d3ed Fix input validation in NewWeeklyReport component 2024-04-02 15:43:18 +02:00
Mattias
6c2213b488 Update handleUpdateWeeklyReport function and fix input validation 2024-04-02 15:43:12 +02:00
Mattias
b3e363f391 Add updateWeeklyReport function to API.ts 2024-04-02 15:43:05 +02:00
Peter KW
51a4d2a0b7 Fix to comments 2024-04-02 13:45:03 +02:00
Peter KW
524bd6c691 Can now delete project 2024-04-02 13:44:31 +02:00
Peter KW
b50d88f670 DeleteProject component 2024-04-02 13:44:06 +02:00
Peter KW
3ed4393c77 Fixed fetch path in removeProject 2024-04-02 13:43:32 +02:00
Peter KW
75876e43da Minor design fix 2024-04-02 13:22:46 +02:00
Peter KW
ea5bbf5f0a Merge branch 'frontend' into gruppPP 2024-04-02 13:18:25 +02:00
Peter KW
948dcce1ca Some fixes to design and comments 2024-04-02 13:14:08 +02:00
Peter KW
cb68a6323b Now shows project statistics 2024-04-02 13:11:33 +02:00
Peter KW
ca88daf493 GetProjectTimes component 2024-04-02 13:10:04 +02:00
Peter KW
644d0ee12c getProjectTimes API 2024-04-02 13:09:28 +02:00
Peter KW
6efc961774 Added getProjectTimes 2024-04-02 13:08:55 +02:00
Davenludd
7df1654bdc Merge branch 'frontend' into gruppDM 2024-04-02 11:49:46 +02:00
Davenludd
6dfa917cf0 Fix in DisplayUserProjects component 2024-04-02 11:37:53 +02:00
Peter KW
17a571fd7c Uses component to get projects now 2024-04-01 02:25:12 +02:00
Peter KW
f3466854c7 Removed unused pages and paths to them in main 2024-04-01 02:24:26 +02:00
Peter KW
1212b3c5ef Removed some stuff 2024-04-01 02:20:48 +02:00
Peter KW
dc98fb510e Clears username+password fields on successful register 2024-04-01 02:17:57 +02:00
Peter KW
58deef400a Removed unused code 2024-04-01 02:17:02 +02:00
Peter KW
cc039d27ae New modal for member info 2024-04-01 02:16:23 +02:00
Peter KW
3981190c7a Can now change username in this modal + moved some stuff to a separate modal 2024-04-01 02:16:06 +02:00
Peter KW
9b0a231701 Some fixes to ChangeUsername 2024-04-01 02:14:44 +02:00
Peter KW
e06aced6dd ChangeRole component to change role and a view for it 2024-04-01 02:13:24 +02:00
Peter KW
68fbbb4b19 Added some alerts + removed unused code 2024-04-01 02:09:28 +02:00
Peter KW
378dd99592 Changed so that you can only change other users role 2024-04-01 02:08:19 +02:00
Peter KW
6fa8135e32 ChangeUserRole API added + bugfix 2024-04-01 02:02:22 +02:00
Peter KW
60fb333090 Fix to paths 2024-03-31 21:04:58 +02:00
Peter KW
e7911574be Removed unused files 2024-03-31 20:56:40 +02:00
Peter KW
0c8a394f74 Small fix so that it uses component for getting users in a proj 2024-03-31 20:56:08 +02:00
Peter KW
5f42fa7818 Fixed types and imports of types 2024-03-31 20:54:00 +02:00
Peter KW
8b6462abee Changed so that username is required to get projects, so that you can get another user's projects (for admin stuff) 2024-03-29 20:19:22 +01:00
Peter KW
4ab23b3c3c Merge branch 'dev' into gruppPP 2024-03-29 20:05:10 +01:00
Imbus
bc9b01d85a Example component GenApiDemo on how to use the new API 2024-03-29 19:37:21 +01:00
Imbus
d2b4bf2a89 Brand new typescript API interface generated from swagger 2024-03-29 19:36:47 +01:00
Imbus
8d5329146d New tygo generated goTypes 2024-03-29 19:36:06 +01:00
Imbus
77f028fd39 Prettierignore for generated files 2024-03-29 19:23:35 +01:00
Imbus
f1e15137d6 Freshly generated swagger docs 2024-03-29 18:43:26 +01:00
Imbus
87a19bfd4e Swagger annotations for JWT key 2024-03-29 18:42:53 +01:00
Imbus
c2fa9aa0c1 Lots of fiddling with swagger annotations in user related handlers 2024-03-29 18:42:12 +01:00
Imbus
1385011769 Make swagger-typescript-api makefile target wipe the previous version 2024-03-29 18:41:20 +01:00
Davenludd
c1f49915ba Refactor signReport method signature 2024-03-29 18:29:38 +01:00
Davenludd
2aaa327a01 Merge branch 'dev' into gruppDM 2024-03-29 17:55:32 +01:00
Davenludd
05545f6f88 Minor fixes 2024-03-29 17:53:37 +01:00
Imbus
374e357820 Add database.txt to clean target 2024-03-29 16:41:55 +01:00
Imbus
0792c6b8a3 More sane swagger-typescript-api generator parameters 2024-03-29 16:36:47 +01:00
Imbus
45e891ed2c Regenerated swagger docs 2024-03-29 15:59:36 +01:00
Imbus
1db1b84e8f Initial demo of swagger-typescript-api interface generation 2024-03-29 15:58:36 +01:00
Imbus
0cd0c8d832 Handler re-order to satisfy human OCD 2024-03-29 15:36:42 +01:00
Imbus
4538a3b193 SignReport handler changes along with tests and TS interface 2024-03-29 15:33:20 +01:00
Imbus
b927fb80fb Refactor again, splitting project related handlers 2024-03-29 15:00:29 +01:00
Imbus
1d5fcd61b6 Refactor, report related endpoints now reside in individual files 2024-03-29 14:50:56 +01:00
Imbus
13d3035e49 Major refactor, splitting user handlers into separate files and changes to how the database is accessed 2024-03-29 14:37:22 +01:00
al8763be
bcb661dc22 bug fix getUnsignedReports 2024-03-29 13:02:31 +01:00
al8763be
c466a98b15 test for getUnsigned 2024-03-29 12:43:35 +01:00
Imbus
9edcc74ee2 Fix for IsProjectManagerHandler 2024-03-29 12:41:51 +01:00
Peter KW
b036ef906c Small fixes in all files that fetches user projects, so that they pass username as argument to GetProjects-function 2024-03-28 21:31:30 +01:00
Peter KW
85795f5406 Changed GetUserProjects so that you have to get username from params. Now admin can choose a user and see what projects the user belongs to 2024-03-28 21:25:59 +01:00
al8763be
8d7d815745 Merge remote-tracking branch 'origin/gruppDM' into BumBranch 2024-03-28 16:46:29 +01:00
al8763be
09f2a2202f Merge remote-tracking branch 'origin/frontend' into BumBranch 2024-03-28 16:45:39 +01:00
al8763be
5d714bbacf Merge remote-tracking branch 'origin/AlexTester' into BumBranch 2024-03-28 16:44:29 +01:00
al8763be
9a54175d49 Co-authored-by: Imbus <imbus64@users.noreply.github.com> 2024-03-28 16:43:34 +01:00
al8763be
f6dcdcc376 GetUnsignedReports 2024-03-28 16:40:55 +01:00
Peter KW
218b0b3ab7 Merge branch 'frontend' into gruppPP 2024-03-28 12:41:43 +01:00
Peter KW
a96f0fb488 Can now delete user, and also asks user to confirm action 2024-03-28 12:39:23 +01:00
Peter KW
48e53b8768 Added some alerts 2024-03-28 12:38:40 +01:00
Peter KW
3d9bd2ef96 Fixed bugs in removeUser api 2024-03-28 12:38:12 +01:00
Peter KW
50042ded41 Fix in handler 2024-03-28 12:37:04 +01:00
Davenludd
4a71078f2a Fix API endpoint for checking project manager 2024-03-28 12:25:24 +01:00
Davenludd
c002f0e530 Merge remote-tracking branch 'origin/AlexTester' into gruppDM 2024-03-28 12:19:45 +01:00
al8763be
6a7bb9ab26 fix ifPM igen 2024-03-28 12:19:11 +01:00
Davenludd
cc186f8ad0 Refactor handleProjectClick function to remove unused username parameter 2024-03-28 12:16:35 +01:00
Davenludd
e589726ab2 Merge remote-tracking branch 'origin/AlexTester' into gruppDM 2024-03-28 12:10:53 +01:00
Davenludd
37d06b10be Fix typo in route parameter name 2024-03-28 12:09:16 +01:00
Davenludd
48e4d1a8df Refactor fetchWeeklyReport in EditWeeklyReport component 2024-03-28 12:09:09 +01:00
al8763be
6a78e67e7e lol 2024-03-28 12:08:14 +01:00
al8763be
7c46797634 ifPM fix 2024-03-28 12:06:59 +01:00
Peter KW
cef7b34092 Small change to newprojmember type 2024-03-28 01:36:48 +01:00
Peter KW
9052babaae Now uses addUserToProject 2024-03-28 01:35:36 +01:00
Peter KW
752d366cf7 Added some alerts 2024-03-28 01:35:16 +01:00
Peter KW
19cb8dbd94 Small fixes 2024-03-28 01:34:49 +01:00
Peter KW
b6d1a2926e Moved MemberAdd to own component and changed design a little 2024-03-28 01:33:03 +01:00
Peter KW
dc3f91eee1 Add member component 2024-03-28 01:31:53 +01:00
Peter KW
0299bb4779 Removed unused path 2024-03-28 01:31:35 +01:00
Peter KW
99a1430188 Fix to addUserToProject API 2024-03-28 00:57:16 +01:00
Peter KW
36c158b54b Insert user "admin" to site_admin in sample_data 2024-03-28 00:27:07 +01:00
Alexander Ek
d4cc556366 Co-authored-by: al8763be <al8763be@users.noreply.github.com> 2024-03-27 21:18:44 +01:00
Davenludd
7c73a01d4c Add success alert message after submitting weekly report 2024-03-27 20:47:24 +01:00
Davenludd
0fc8957e55 Merge branch 'master' into gruppDM 2024-03-27 20:38:31 +01:00
Imbus
e1b410c850 Merge branch 'dev' 2024-03-26 04:19:27 +01:00
Imbus
94f36d4e06 Cleaning 2024-03-23 20:32:00 +01:00
Imbus
96238ceb2e Fix asset resolution at build time 2024-03-23 20:29:08 +01:00
Alexander Ek
42a2ad02e4 Description comment in main.go file 2024-03-23 15:22:58 +01:00
Davenludd
492cfed08c Merge branch 'dev' into gruppDM 2024-03-21 18:09:17 +01:00
Davenludd
6ed53fe94a retract changes made in error handling in SubmitWeeklyReport handler 2024-03-21 18:07:56 +01:00
Imbus
dbbe4da401 Fix for submitWeeklyReport ts API 2024-03-21 18:05:41 +01:00
Davenludd
8d739396a1 Change input types from number to text in ViewOtherTimeReport component 2024-03-21 14:01:33 +01:00
Davenludd
689daf4e1f Updated Link in ProjectMembers component 2024-03-21 14:01:19 +01:00
Davenludd
1d054e660c Fixes for PMOtherUsersTR 2024-03-21 14:00:47 +01:00
Davenludd
589a135bb5 New Page for PM 2024-03-21 13:59:57 +01:00
Davenludd
4c22ba478d Add OtherUsersTR component for displaying weekly report of a user in a project 2024-03-21 13:59:24 +01:00
Davenludd
deeff6c3c2 Added AllTimeReportsInProjectOtherUser component 2024-03-21 13:59:10 +01:00
Davenludd
e47f12c6d7 Add new route for PM components 2024-03-21 13:58:45 +01:00
Davenludd
b656204457 Added component for PM to view other users time reports 2024-03-21 12:46:05 +01:00
Mattias
9f931a2643 Add heading to Edit Time Report page 2024-03-21 12:37:44 +01:00
Mattias
a31a50965f Update routes and components for PMViewUnsignedReport 2024-03-21 12:36:14 +01:00
Davenludd
ebc59e0c11 Refactor handleNewWeeklyReport function to return a boolean indicating success or failure if week already has a report 2024-03-21 12:31:16 +01:00
Davenludd
d8a73329a1 Refactor error handling in SubmitWeeklyReport handler 2024-03-21 12:31:16 +01:00
Mattias
1b20173ece Remove unused import and add DisplayUnsignedReports component 2024-03-21 12:10:53 +01:00
Mattias
4b6c93a202 Add DisplayUnsignedReports component and update route for otherUsersTimeReports 2024-03-21 12:10:43 +01:00
Mattias
70e6cbf12e Update project members page layout and remove unnecessary links 2024-03-21 11:30:07 +01:00
Davenludd
856ae40900 Refactor input fields to handle empty values 2024-03-21 11:10:37 +01:00
Melker
ec362cfa3a GetProjectTimesHandler 2024-03-21 10:51:47 +01:00
Mattias
e2d2310275 Add ViewOtherTimeReport component to PMOtherUsersTR page 2024-03-21 10:31:24 +01:00
Mattias
85be4c79d6 Refactor getProjectMembers function in ProjectMembers component 2024-03-21 10:05:51 +01:00
Davenludd
ae0208ff23 Add project navigation based on user and pm role 2024-03-21 10:02:59 +01:00
Mattias
14133a9f22 CleanUp, making lint happy 2024-03-21 09:59:12 +01:00
Mattias
c415539904 Refactor ProjectMembers component to use API for fetching project members 2024-03-21 09:36:08 +01:00
Davenludd
01a6f9e61d Add comment NewWeeklyReport 2024-03-21 08:57:56 +01:00
Davenludd
faf998e652 Merge branch 'dev' into gruppDM 2024-03-21 08:56:07 +01:00
Peter KW
c9e28e9b5b New type to match fetch 2024-03-21 03:39:53 +01:00
Peter KW
b2db9c54ca Add member functionality added 2024-03-21 03:39:18 +01:00
Peter KW
c5bc6c1c58 Add user to project component 2024-03-21 03:37:37 +01:00
Peter KW
2694beb0e8 AddUserToProject API 2024-03-21 03:36:57 +01:00
Peter KW
a73432669c New path 2024-03-21 03:36:30 +01:00
pavel Hamawand
e9eb2e9ab6 checks 2024-03-21 02:51:28 +01:00
dDogge
2d4ff7e087 Fully implemented UpdateWeeklyReport for database, handler and corresponding tests 2024-03-21 02:48:55 +01:00
pavel Hamawand
baf11f19d6 added token 2024-03-21 02:47:51 +01:00
Imbus
b484346031 Better integration test target 2024-03-21 02:26:30 +01:00
pavel Hamawand
1950112202 implementing changeUsername component 2024-03-21 02:22:23 +01:00
pavel Hamawand
3e11b87eee Modify the implementation of the changeUserName method in the api object 2024-03-21 01:58:57 +01:00
pavel Hamawand
9e2a3cca81 Update the method signature in the API interface to use StrNameChange 2024-03-21 01:56:27 +01:00
al8763be
dea802bd91 bummed handler 2024-03-21 01:37:39 +01:00
Imbus
ed88220a47 Fix containerfile permission errors 2024-03-21 01:24:17 +01:00
Imbus
d4547e997c Merge branch 'dev' 2024-03-21 00:48:44 +01:00
Imbus
89ba0415f7 Merge branch 'melle' into dev 2024-03-21 00:44:07 +01:00
dDogge
13720d633f Fixed test_change_user_name and added test_list_users_project once again 2024-03-21 00:41:40 +01:00
Imbus
a3bf3a04ac Merge branch 'melle' of github.com:imbus64/TTime into melle 2024-03-21 00:33:27 +01:00
Melker
6bc09c656a GetProjectTimes och tester till det 2024-03-21 00:32:38 +01:00
dDogge
44f6b31056 Handler for ChangeUserName changed and corresponding test added 2024-03-21 00:17:08 +01:00
Melker
e7acfb37b1 GetProjectTimes och tester till det 2024-03-21 00:09:15 +01:00
Imbus
a6d7ee2de6 Merge branch 'dev' 2024-03-21 00:03:51 +01:00
Imbus
acdee28eb0 Fix for EditWeeklyReport component 2024-03-21 00:03:15 +01:00
Imbus
03f6edd320 Merge branch 'gruppPP' into frontend 2024-03-20 23:55:29 +01:00
Peter KW
e63b45f38a Changes so that it shows relevant info depending on if managing project member or a system user 2024-03-20 23:49:09 +01:00
Peter KW
e8262ed5e0 Now shows users in project when clicked 2024-03-20 23:45:50 +01:00
Peter KW
6dfc31832c Fixes to design 2024-03-20 23:44:36 +01:00
Peter KW
b2333d8cbd Removed unused file 2024-03-20 23:43:13 +01:00
al8763be
e839d02cfd Merge branch 'dev' into frontend 2024-03-20 23:37:56 +01:00
al8763be
3f2e104078 Merge branch 'dev' of https://github.com/imbus64/TTime into dev 2024-03-20 23:32:07 +01:00
borean
28935f285b fixed typo 2024-03-20 23:25:50 +01:00
al8763be
01b934969a fixed dep array 2024-03-20 23:24:28 +01:00
Imbus
b7523cf04d Merge branch 'dev' into imbs 2024-03-20 23:20:04 +01:00
al8763be
a2a8ccd185 bättre stämmning 2024-03-20 23:18:51 +01:00
al8763be
8aa9e285b8 Merge branch 'dev' into BumBranch 2024-03-20 23:05:49 +01:00
al8763be
afa445a426 Merge remote-tracking branch 'origin/imbs' into BumBranch 2024-03-20 23:04:17 +01:00
Imbus
740289decc Merge branch 'dev' into imbs 2024-03-20 22:52:13 +01:00
Davenludd
a4bd2c9315 Merge branch 'dev' into gruppDM 2024-03-20 22:50:56 +01:00
Mattias
1a87effc3a Changes to EditWeeklyReport component 2024-03-20 22:49:54 +01:00
al8763be
ddea76e24a fixed AllTimeReportsInProject 2024-03-20 22:47:02 +01:00
Imbus
fdaba36d57 Log database error in SubmitWeeklyReport 2024-03-20 22:43:03 +01:00
al8763be
c4b8bef7f8 fix database 2024-03-20 22:37:15 +01:00
Davenludd
d0209094a9 Remove unused variable in EditWeeklyReport component 2024-03-20 22:10:53 +01:00
Davenludd
f91f0ff8f5 Merge branch 'dev' into gruppDM 2024-03-20 22:05:04 +01:00
Imbus
1a7bc9c73c Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-20 22:04:42 +01:00
Imbus
b00ce7a0ed Typescript type regeneration 2024-03-20 22:04:36 +01:00
Imbus
ec138163c6 Very large changes related to database and its interface 2024-03-20 22:04:36 +01:00
Davenludd
c995dc52ad Merge branch 'dev' into gruppDM 2024-03-20 22:04:11 +01:00
al8763be
a51d984229 Merge branch 'dev' of https://github.com/imbus64/TTime into dev 2024-03-20 22:03:25 +01:00
al8763be
70cf40feb5 fixed getWeeklyReport 2024-03-20 22:02:34 +01:00
Imbus
0dc7e3bf99 Deleted sqlite3-journal 2024-03-20 21:56:03 +01:00
Imbus
65101384e2 Typescript type regeneration 2024-03-20 21:54:13 +01:00
Davenludd
900797facc Fix initial state values and handle empty input values in NewWeeklyReport component 2024-03-20 21:53:27 +01:00
al8763be
4288b064f3 fixade dålig stämmning igen 2024-03-20 21:53:08 +01:00
Imbus
cdea2dce1c Very large changes related to database and its interface 2024-03-20 21:51:36 +01:00
al8763be
49ca043157 ingen dålig stämmning 2024-03-20 21:51:02 +01:00
Davenludd
b9b17bf229 Merge branch 'dev' into gruppDM 2024-03-20 21:21:59 +01:00
al8763be
69d07624ce Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-20 20:55:53 +01:00
Imbus
22f4fba36a Merge 2024-03-20 20:46:27 +01:00
Johanna
ae77bcf0bb Added comments to TypeScript API, merge 2024-03-20 20:44:11 +01:00
Johanna
9f61a36a68 Added comments to TypeScript API, merge 2024-03-20 20:36:34 +01:00
al8763be
a9e515925e getWeeklyReportsForUser fixed 2024-03-20 20:24:56 +01:00
al8763be
91c9f50491 sampledata now has weeklyReports for user & user2 2024-03-20 20:08:00 +01:00
al8763be
e8c660705f sampledata now has weeklyReports for user & user2 2024-03-20 20:05:04 +01:00
al8763be
be3adb09ae Merge branch 'dev' into BumBranch 2024-03-20 19:39:05 +01:00
al8763be
8fbb4be0ad merge again 2024-03-20 19:38:52 +01:00
al8763be
eff14516b4 merge 2024-03-20 19:37:07 +01:00
Imbus
9bf00e8dce Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 18:47:47 +01:00
Imbus
3525598df2 Tests for AddProject to ensure creator is Manager 2024-03-20 18:47:37 +01:00
Peter KW
e4a0246b84 Merge branch 'frontend' into gruppPP 2024-03-20 18:41:01 +01:00
Peter KW
9b30b82237 Gets all project members and displays them properly now 2024-03-20 18:38:37 +01:00
Peter KW
bca12151b7 Added new type to match fetch for project members 2024-03-20 18:36:45 +01:00
Peter KW
8250c92694 Fixed so that it uses new type 2024-03-20 18:36:10 +01:00
Peter KW
d1be752ac3 getAllUsersProject uses new type 2024-03-20 18:35:02 +01:00
Melker
bef8a6af85 GetTotalTimePerActivity och ett halvt test för den 2024-03-20 18:20:14 +01:00
Imbus
45761dadcb Fix for checkProjectManager endpoint and test_check_if_project_manager 2024-03-20 17:50:51 +01:00
Imbus
0017e9b9ef Fixing error in ts API, fixing AllTimeReportsInProject 2024-03-20 17:31:59 +01:00
Davenludd
bf59503517 Update background images in LoginPage.css 2024-03-20 17:24:54 +01:00
Imbus
933191b076 Generate typescript types 2024-03-20 17:20:47 +01:00
Imbus
b405b44460 Merge frontend -> dev 2024-03-20 17:19:09 +01:00
Imbus
cc013632a7 Updated python testing script to pass test_get_weekly_reports_user 2024-03-20 17:14:57 +01:00
Imbus
154c2240c8 Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 17:10:16 +01:00
Imbus
d19372d66b Frontend API interface fixed (untested) for getWeeklyReportsForUser 2024-03-20 17:10:11 +01:00
Peter KW
cd452459c2 getAllUsersProject API, fetches all users in a project 2024-03-20 17:02:36 +01:00
Peter KW
056ca3660d GetUsersInProject component 2024-03-20 16:59:54 +01:00
borean
1147a216ce typo 2024-03-20 16:55:55 +01:00
Imbus
649a4ba1fd Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 16:50:09 +01:00
Imbus
49e3542dce Merge branch 'BumBranch' into dev 2024-03-20 16:49:59 +01:00
dDogge
88f232e21b ChangeUserName function in db.go fixed, corresponding test added 2024-03-20 16:46:48 +01:00
Davenludd
e4f5fbda44 Fix useEffect dependency in AllTimeReportsInProject component 2024-03-20 16:46:44 +01:00
Mattias
b99de71c38 Update links in PMProjectMembers component 2024-03-20 16:41:32 +01:00
Mattias
0befc4c7d1 Minor fix 2024-03-20 16:36:39 +01:00
Mattias
a3fc71f4bf Changed path in main 2024-03-20 16:36:32 +01:00
Mattias
4030031ce9 New comp TimePerActivity 2024-03-20 16:36:24 +01:00
Johanna
ef6c3951fd Merge branch 'johanna-testing' into dev 2024-03-20 16:28:10 +01:00
Mattias
dd02c2c5c6 Brand new TimePerRole component deluxe edition 2024-03-20 16:24:38 +01:00
Mattias
287e97fe6f Updated path in main 2024-03-20 16:24:10 +01:00
Johanna
67680e2616 Added comments to db_test.go test functions 2024-03-20 16:13:53 +01:00
Imbus
0f4c3ac5ec Reworking endpoint getWeeklyReportsUser 2024-03-20 16:05:50 +01:00
pavel Hamawand
75e835895a minor fix 2024-03-20 15:48:45 +01:00
pavel Hamawand
d94f21ab67 minor fix 2024-03-20 15:47:34 +01:00
pavel Hamawand
ebb009bd20 BackButton 2024-03-20 15:33:56 +01:00
pavel Hamawand
32d9cc4684 backButton 2024-03-20 15:33:26 +01:00
pavel Hamawand
d3938f76e1 backButton 2024-03-20 15:32:40 +01:00
Davenludd
8084f722b5 Fixies in API and component code 2024-03-20 15:32:30 +01:00
Davenludd
caa793d036 Fix week validation in NewWeeklyReport component for safari and firefox 2024-03-20 15:32:14 +01:00
pavel Hamawand
196446be1f Backbutton 2024-03-20 15:31:18 +01:00
pavel Hamawand
0076a9f4bb backButton 2024-03-20 15:30:16 +01:00
Davenludd
847073c6f8 Merge branch 'BumBranch' into gruppDM 2024-03-20 15:21:26 +01:00
Davenludd
54e42cd2a8 Add support for week input in Chrome and Edge browsers 2024-03-20 15:19:09 +01:00
al8763be
af5813681d Jag avskyr denna handlern 2024-03-20 15:17:00 +01:00
al8763be
7d30165465 Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-20 15:13:05 +01:00
al8763be
771118f85e Update API.ts 2024-03-20 15:13:00 +01:00
Imbus
7799eb09e9 Delete unused handler prototype 2024-03-20 15:11:25 +01:00
Imbus
a84c2c6129 Rename GetWeeklyReportsForProject -> GetWeeklyReportsForUser 2024-03-20 14:54:22 +01:00
Davenludd
eddf678f3a Merge branch 'BumBranch' into gruppDM 2024-03-20 14:48:32 +01:00
Mattias
14b68e4463 Formatting 2024-03-20 14:20:09 +01:00
Mattias
47ce986834 Minor fixes 2024-03-20 14:17:33 +01:00
borean
c6969b9503 updated api path for DeleteProject 2024-03-20 14:16:47 +01:00
Davenludd
0fb06790af Fix useEffect dependency array in ProjectMembers component 2024-03-20 14:09:16 +01:00
Davenludd
89cb3d23b7 Fix missing comma in getWeeklyReportsForProject function signature 2024-03-20 14:07:12 +01:00
Davenludd
adf9500bbf Fix useEffect dependency in AllTimeReportsInProject component 2024-03-20 14:06:54 +01:00
Johanna
00f72df65f Added comments 2024-03-20 14:06:15 +01:00
al8763be
74e1347162 Changed error message 2024-03-20 14:02:34 +01:00
Davenludd
4574af25cb Merge branch 'BumBranch' into gruppDM 2024-03-20 14:02:11 +01:00
al8763be
d41bfcf888 Fix for getWeeklyReportsForUser 2024-03-20 14:00:00 +01:00
Davenludd
29d893362c Docs/Comments to user project menu component 2024-03-20 13:57:02 +01:00
Davenludd
c7c38d4201 Docs/Comments to registration form component 2024-03-20 13:57:02 +01:00
Davenludd
d9068949cd Docs/Comments to new weekly report form component 2024-03-20 13:57:02 +01:00
Davenludd
7e7dbe2cc7 Docs/Comments to header component 2024-03-20 13:57:02 +01:00
Davenludd
cf6c8cd34c Docs/Comments to Footer component 2024-03-20 13:57:02 +01:00
Davenludd
dbeeb609b3 Docs/Comments to GetWeeklyReport component 2024-03-20 13:57:02 +01:00
Davenludd
aa5c4017bb Docs/Comments to DisplayUserProjects component 2024-03-20 13:57:02 +01:00
Davenludd
4212dae5bf Delete CountButton component 2024-03-20 13:57:02 +01:00
Davenludd
bd27b549aa Add JSDoc comments to Button component 2024-03-20 13:57:02 +01:00
Davenludd
af92f4faee Docs/Comments to BasicWindow component 2024-03-20 13:57:02 +01:00
Davenludd
2dffffde50 Docs/coments to background animation component 2024-03-20 13:57:02 +01:00
Davenludd
c0e03d7d0d Doc/Comments to BackButton comp 2024-03-20 13:57:02 +01:00
Davenludd
ff4ea47846 Doc/Comments in AllTimeReportsInProject component 2024-03-20 13:57:02 +01:00
Mattias
d3bb6f49e5 New comp changeRoles 2024-03-20 13:51:49 +01:00
borean
a18ce465de Added a handler for getting all users for a specific project, WIP and untested 2024-03-20 13:03:43 +01:00
borean
3515a86bbb Added ChangeUserName handler, untested and WIP, 2024-03-20 12:50:04 +01:00
Mattias
1b21b2574a Comp for displaying projectmembers and changed path in main 2024-03-20 12:36:30 +01:00
Peter KW
33b269e0c9 Now uses GetProjects 2024-03-20 12:25:59 +01:00
Peter KW
cea2b6c03c GetProjects compo 2024-03-20 12:25:07 +01:00
borean
1919b7cb99 Added ChangeUserName in db 2024-03-20 12:11:05 +01:00
Peter KW
ae96f67630 Merge branch 'dev' into gruppPP 2024-03-20 11:55:05 +01:00
Peter KW
6317c7674c Now uses projectListAdmin to show projects 2024-03-20 11:53:27 +01:00
Peter KW
e271794b57 List for showing all projects as admin 2024-03-20 11:51:50 +01:00
Peter KW
a5adec82e2 Modul for showing projectinfo *not finished* 2024-03-20 11:51:04 +01:00
borean
bb93cef1d3 New deleteproject handler, WIP 2024-03-20 11:43:47 +01:00
borean
c01dd21b0a uncommented 2024-03-20 11:23:24 +01:00
borean
dce91943b3 Added DeleteProject handler, untested 2024-03-20 11:22:33 +01:00
Davenludd
7ed9398bcb Merge branch 'frontend' into gruppDM 2024-03-20 11:19:02 +01:00
borean
cd74758b2f DeleteProject added in db.go, untested 2024-03-20 11:09:48 +01:00
Peter KW
6c433208ea Merge branch 'frontend' into gruppPP 2024-03-20 10:54:38 +01:00
borean
a468b896db WIP for funcs that need to be implemented 2024-03-20 10:41:16 +01:00
Davenludd
033268f4ef Added info to component 2024-03-20 08:47:11 +01:00
al8763be
7b78767f50 restore 2024-03-20 01:14:30 +01:00
dDogge
b13a8b5432 Merge 2024-03-20 00:35:37 +01:00
Peter KW
4630fb336f Merge branch 'dev' into gruppPP 2024-03-20 00:32:15 +01:00
Peter KW
4e100229f5 Now uses GetAllUsers 2024-03-20 00:16:56 +01:00
Peter KW
b20817ec8a Small fix to props and imports 2024-03-20 00:16:30 +01:00
Peter KW
cc231dbfaa Component for getting all users 2024-03-20 00:14:23 +01:00
Peter KW
8300fb3a6f Added getAllUsers to API 2024-03-20 00:10:24 +01:00
Melker
b7b831d869 Lagt till om Admin=True/false 2024-03-19 23:54:54 +01:00
Davenludd
b5d3ab7cb7 Refactor AllTimeReportsInProject component to use API for fetching weekly reports 2024-03-19 23:44:44 +01:00
Davenludd
863a14c316 Add WeeklyReport type and update getWeeklyReportsForProject method 2024-03-19 23:44:44 +01:00
Samuel Högbom Aronson
cb44954477 -added auth for rolechange, endpoint and test 2024-03-19 23:08:14 +01:00
Mattias
ffaa661628 Minor fix 2024-03-19 22:51:54 +01:00
Mattias
900c1614e9 minor fix 2024-03-19 22:50:57 +01:00
Mattias
cd2a81e7f2 Added unauthorizedpage and path in main 2024-03-19 22:50:40 +01:00
Mattias
cc9678f375 Created component for authorized route 2024-03-19 22:50:23 +01:00
Mattias
fe89ae0970 Added backbutton 2024-03-19 22:13:06 +01:00
Johanna
1974607fc7 Formating 2024-03-19 22:04:20 +01:00
Mattias
55a2dc7fac Add PMProjectMenu component and refactor PMProjectPage 2024-03-19 22:03:45 +01:00
Mattias
d355b55634 Add UserProjectMenu component and refactor UserProjectPage 2024-03-19 21:59:37 +01:00
Davenludd
08a0ebb533 Merge branch 'BumBranch' into gruppDM 2024-03-19 21:51:07 +01:00
Johanna
63fd2e9b6f //fix parse in NewWeeklyReport 2024-03-19 21:47:16 +01:00
Mattias
dc6ce4b725 Additional minor fix 2024-03-19 21:31:20 +01:00
Mattias
05b9740c36 Fixed links and changed path in main for pm 2024-03-19 21:29:37 +01:00
Mattias
3fea87a88a New component for displaying userprojects 2024-03-19 21:22:49 +01:00
al8763be
0b6edd359e Bumfix for BumCode on checkIfProjectManager 2024-03-19 20:29:36 +01:00
al8763be
e48bf5d98c Added checkIfProjectManager, hope it works 2024-03-19 20:25:26 +01:00
Imbus
88c53ae4b6 Golds automatic documentation generator 2024-03-19 20:21:38 +01:00
al8763be
382ea38558 Merge branch 'dev' into BumBranch 2024-03-19 20:15:20 +01:00
al8763be
74285dc1cf Before fuck up 2024-03-19 19:45:56 +01:00
dDogge
ce4cf788ae Added handler and corresponding test for the IsProjectManager function 2024-03-19 19:30:01 +01:00
dDogge
5778d6e892 Added isProjectManager function to db.go and corresponding test to db_test.go 2024-03-19 19:14:55 +01:00
dDogge
2b41085865 Added GetWeeklyReportsUser function and handler, also corresponding tests to both GetWeeklyReportsUser and handler added 2024-03-19 19:04:45 +01:00
Peter KW
a72aea1382 Removed unused authority check 2024-03-19 17:12:38 +01:00
Peter KW
cd84a2bb21 Everyone except admin has authority = 2 2024-03-19 17:12:09 +01:00
Peter KW
8dfe9dac96 Removed unused path 2024-03-19 17:09:45 +01:00
Davenludd
4568d68746 Merge branch 'frontend' into gruppDM 2024-03-19 17:02:50 +01:00
Peter KW
4bcbf162f9 Merge branch 'frontend' into gruppPP 2024-03-19 17:00:04 +01:00
Imbus
9c4ce0e733 Merge branch 'dev' into frontend 2024-03-19 16:57:49 +01:00
Imbus
2f3730ca90 New typescript types from tygo 2024-03-19 16:50:16 +01:00
Mattias
5803c7b29b Refactor fetchWeeklyReport function, updated submit button text, week fetched by params 2024-03-19 11:41:50 +01:00
Davenludd
d0d0e311e5 Add project name to UserViewTimeReportsPage and add AllTimeReportsInProject component 2024-03-19 11:10:47 +01:00
Davenludd
03819aff44 Update link to time reports in UserProjectPage 2024-03-19 11:10:02 +01:00
Davenludd
7fd128e1f3 Add AllTimeReportsInProject component 2024-03-19 11:09:52 +01:00
Davenludd
09a7cbced1 Update routes in main.tsx 2024-03-19 11:09:35 +01:00
Davenludd
ee2fb6d543 Minor change UserProjectPage 2024-03-19 09:41:24 +01:00
Davenludd
a30a6a4988 Fix button UserNewTimeReportPage 2024-03-19 09:37:13 +01:00
Mattias
cda2b72d08 projectName is now fetched from params 2024-03-19 09:35:15 +01:00
Davenludd
4f53f9de94 Fix initial value of week state in NewWeeklyReport component for error message 2024-03-19 09:33:20 +01:00
Davenludd
53f468d7c8 Update navigation in EditWeeklyReport component 2024-03-19 09:23:09 +01:00
Davenludd
d775a6e381 Update navigation in NewWeeklyReport component 2024-03-19 09:21:33 +01:00
Davenludd
08532444f0 Cleanup YourProjectsPage 2024-03-19 09:14:30 +01:00
Imbus
5f6977354f Justfile save-release target now saves image with name containing commit hash and date 2024-03-19 04:02:04 +01:00
al8763be
3b8b9bb3f2 Merge branch 'dev' into frontend 2024-03-19 03:44:23 +01:00
al8763be
3df9ddcd4b Merge remote-tracking branch 'origin/gruppPP' into frontend 2024-03-19 03:44:02 +01:00
Imbus
ab313551c9 Merge branch 'frontend' into dev 2024-03-19 03:40:24 +01:00
pavel Hamawand
cbb62438c8 implementing AdminChangeUsername 2024-03-19 03:40:17 +01:00
pavel Hamawand
8b7ad8911b implementing ChangeUser 2024-03-19 03:39:05 +01:00
Imbus
2ce1837223 Merge branch 'frontend' into dev 2024-03-19 03:36:42 +01:00
pavel Hamawand
4df8d3f858 new component 2024-03-19 03:22:54 +01:00
pavel Hamawand
52aecd14d4 minor fix 2024-03-19 03:14:14 +01:00
al8763be
502cd67b4c Merge branch 'dev' into BumBranch 2024-03-19 02:15:33 +01:00
Imbus
4dbbee3249 Checking errors from transactions in go 2024-03-19 02:14:09 +01:00
al8763be
e3fd9f52ca Merge branch 'gruppPP' into BumBranch 2024-03-19 02:12:37 +01:00
al8763be
8081f289b5 fixed NewWeeklyReport 2024-03-19 02:11:47 +01:00
Peter KW
cdbd6ca0ce Small fixes to design 2024-03-19 01:48:01 +01:00
Imbus
7db03e8dbd Merge branch 'imbs' into dev 2024-03-19 01:43:10 +01:00
Imbus
5f88e92415 Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-19 01:40:36 +01:00
borean
3125b511bb correcting AddProject 2024-03-19 01:38:40 +01:00
Imbus
09014c6659 Better test feedback in python script 2024-03-19 01:38:39 +01:00
Imbus
e498f0ed63 Silencing python testing, optional verbose output 2024-03-19 01:38:39 +01:00
al8763be
77a24421e9 Merge branch 'gruppdm' into BumBranch 2024-03-19 01:24:55 +01:00
al8763be
de234c12f2 Merge branch 'imbs' into BumBranch 2024-03-19 01:24:38 +01:00
Imbus
68b01f2144 Merge branch 'borean' of github.com:imbus64/TTime into borean 2024-03-19 01:23:18 +01:00
borean
58d9001be3 AddProject changed so that user is promoted to projectmanager 2024-03-19 01:23:07 +01:00
Imbus
5bcca0202b Better test feedback in python script 2024-03-19 01:12:14 +01:00
borean
0217f2b512 AddProject changed so that user is promoted to projectmanager 2024-03-19 01:10:02 +01:00
pavel Hamawand
6a84b1c14d fix backbutton 2024-03-19 01:03:38 +01:00
pavel Hamawand
c072aff9da added change username button 2024-03-19 01:02:39 +01:00
al8763be
9434c31013 Merge remote-tracking branch 'origin/dev' into frontend 2024-03-19 00:48:13 +01:00
Imbus
c03be8c5d9 Path rename in python testing script 2024-03-19 00:46:28 +01:00
Davenludd
ba2bb1fc5e Merge branch 'frontend' into gruppDM 2024-03-19 00:43:25 +01:00
al8763be
847427a543 Merge remote-tracking branch 'origin/dev' into frontend 2024-03-19 00:42:32 +01:00
Imbus
b174ec8922 Rename for clarity and consistensy with TS api 2024-03-19 00:41:30 +01:00
Davenludd
b8c69fabf5 Remove unused variable and update API call in YourProjectsPage.tsx 2024-03-19 00:35:27 +01:00
pavel Hamawand
d2a8399bde minor fix 2024-03-19 00:35:15 +01:00
Imbus
2cfbcd15a7 Merge branch 'docs' of github.com:imbus64/TTime into docs 2024-03-19 00:34:15 +01:00
Samuel Högbom Aronson
9ce70e74e9 tyding up and tryinng to get tokens in docs to work 2024-03-19 00:33:38 +01:00
Samuel Högbom Aronson
3e35586bbe finished docs for user reletaded stucts and added endpoint for listallusers 2024-03-19 00:33:38 +01:00
Peter KW
b3dfbc47a4 Merge branch 'frontend' into gruppPP 2024-03-19 00:33:30 +01:00
Imbus
7e4e35f597 Silencing python testing, optional verbose output 2024-03-19 00:31:15 +01:00
Davenludd
17c30f5dd9 Merge branch 'frontend' into gruppDM 2024-03-19 00:28:12 +01:00
al8763be
d7e14f1886 quick fix for getUserProjects API 2024-03-19 00:27:35 +01:00
Samuel Högbom Aronson
8711f9a20d tyding up and tryinng to get tokens in docs to work 2024-03-19 00:27:31 +01:00
al8763be
59c4dab2e2 quick fix for getUserProjects API 2024-03-19 00:26:17 +01:00
Peter KW
d2ff2428cd Some corrections 2024-03-19 00:26:05 +01:00
Peter KW
36524e5cbb Changed so that it makes a modal for each user instead of a link 2024-03-19 00:25:37 +01:00
Davenludd
a2bc13ec22 Merge branch 'frontend' into gruppDM 2024-03-19 00:20:43 +01:00
al8763be
83f8097c2b API getUserProjects Fucked 2024-03-19 00:20:08 +01:00
Peter KW
a0759b099a Modul for viewing user info in admin manage users page 2024-03-19 00:17:29 +01:00
Peter KW
db4f869712 Delete user component 2024-03-19 00:15:42 +01:00
Davenludd
652f74884f Merge branch 'frontend' into gruppDM 2024-03-19 00:15:41 +01:00
al8763be
3e1f899414 Merge branch 'dev' into frontend 2024-03-19 00:14:55 +01:00
Peter KW
e55b380bb4 Should be able to delete users except for self now 2024-03-19 00:14:55 +01:00
Imbus
2ffbc2f9fd Merge docs -> dev 2024-03-19 00:10:42 +01:00
Imbus
fe08d01e15 Insanely verbose logging 2024-03-19 00:00:18 +01:00
Imbus
71caf37642 Resolve testing.py 2024-03-18 23:43:14 +01:00
Imbus
108850c20c Merge imbs -> dev 2024-03-18 23:40:56 +01:00
Imbus
4c297ab2ef Old unresolved merge conflict fixed 2024-03-18 23:39:02 +01:00
Imbus
0fa7558d64 Proper logging, all endpoint debug printing replaced with suitable log level 2024-03-18 23:38:50 +01:00
Davenludd
2eab030212 Merge branch 'frontend' into gruppDM 2024-03-18 23:37:48 +01:00
Davenludd
ff9eba039f Minor fixes YourProjectsPage 2024-03-18 23:36:59 +01:00
al8763be
2cd2ef9ef5 Merge branch 'frontend' of https://github.com/imbus64/TTime into frontend 2024-03-18 23:36:50 +01:00
al8763be
9056aafd2e Test for getUserProjectsAdded 2024-03-18 23:34:03 +01:00
Samuel Högbom Aronson
ad85194d4f finished docs for user reletaded stucts and added endpoint for listallusers 2024-03-18 23:21:49 +01:00
al8763be
2cff1d55f9 Fixed migration data 2024-03-18 23:08:38 +01:00
Imbus
7932350980 Exit with non-zero if migration fails 2024-03-18 23:04:08 +01:00
Davenludd
cc09eb0ead Remove duplicate import statements 2024-03-18 23:01:30 +01:00
Davenludd
8df3311f5a Remove duplicate code in UserProjectPage 2024-03-18 22:59:16 +01:00
al8763be
bed9381509 Merge branch 'BumBranch' into dev 2024-03-18 22:48:23 +01:00
al8763be
95b09a8ce7 Fixed getProjectForUsers 2024-03-18 22:46:53 +01:00
Samuel Högbom Aronson
f3c5abf4f3 added docs for loginrenew, login, regsiter 2024-03-18 22:40:51 +01:00
al8763be
a5399c9335 Samle data without query 2024-03-18 22:39:43 +01:00
al8763be
7ae6cce6b4 Sample data 2024-03-18 22:39:02 +01:00
Imbus
472940cedc Remove index from userId 2024-03-18 22:20:25 +01:00
Imbus
f5a914330f Removed userId identifier from user table, introducing numerous errors 2024-03-18 22:10:19 +01:00
Imbus
c31f145c35 Database sample data, make target and go code 2024-03-18 22:07:02 +01:00
Davenludd
f5a4c3d0e5 Merge branch 'frontend' into gruppDM 2024-03-18 22:05:50 +01:00
Davenludd
55fd42090d Remove username prop from BasicWindow component on all pages 2024-03-18 22:00:58 +01:00
Davenludd
5a4049eaf3 Remove username prop from BasicWindow component 2024-03-18 21:56:37 +01:00
Davenludd
59add3b6b3 Remove username prop from BasicWindow component 2024-03-18 21:55:47 +01:00
Davenludd
6982d21016 Add project name to URL in UserProjectPage 2024-03-18 21:55:15 +01:00
Davenludd
f16dc1722c Update project name in YourProjectsPage.tsx URL 2024-03-18 21:53:16 +01:00
Davenludd
31c5a78dae Refactor routing paths in main.tsx 2024-03-18 21:52:34 +01:00
Davenludd
93addc9870 Fixes in NewWeeklyReport component 2024-03-18 21:40:07 +01:00
Davenludd
847180cf75 Update user navigation route 2024-03-18 21:37:52 +01:00
Davenludd
b9d7e57f2c Update background image in Header component 2024-03-18 21:37:31 +01:00
Davenludd
25713443e2 Remove username prop from BasicWindow component 2024-03-18 21:33:20 +01:00
Imbus
47b60038b4 Merge branch 'frontend' into dev 2024-03-18 21:24:42 +01:00
Imbus
e0de61dd94 Type fixes in frontend, Register & YourProjectsPage 2024-03-18 21:24:26 +01:00
Imbus
f437b25da5 Merge branch 'frontend' into dev 2024-03-18 21:18:49 +01:00
al8763be
8eb23bf7f9 lint bro happ + test for getUserProject 2024-03-18 21:08:33 +01:00
Imbus
4683dd459a Remove code related to demo button in backend 2024-03-18 20:56:00 +01:00
Imbus
2be4afd0e0 Correct ish swagger docstring 2024-03-18 20:05:47 +01:00
Imbus
2aade5d2fe Docs example 2024-03-18 19:59:14 +01:00
Davenludd
d64ec708a1 Minor fixes 2024-03-18 19:37:37 +01:00
Davenludd
3e9dc87100 Add NotFoundPage to handle 404 errors 2024-03-18 19:36:28 +01:00
Davenludd
a2ad2913e4 Add NotFoundPage component 2024-03-18 19:34:15 +01:00
Peter KW
83e781c877 Fixed getting the username and removed comment 2024-03-18 18:31:58 +01:00
al8763be
4392b68397 Removed duplicate getUserProjects 2024-03-18 17:40:53 +01:00
Davenludd
531e9a0535 Fix links in UserProjectPage component 2024-03-18 17:38:45 +01:00
al8763be
d0cc6f2c1b Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-18 17:35:19 +01:00
al8763be
805d05f8a5 Merge branch 'gruppPP' into BumBranch 2024-03-18 17:31:03 +01:00
Imbus
fe6942aa81 Merge imbs -> dev 2024-03-18 17:30:50 +01:00
al8763be
e5904253e3 Merge branch 'gruppdm' into BumBranch 2024-03-18 17:28:57 +01:00
Peter KW
d692165f99 Removed username prop from basicwindow in all pages 2024-03-18 17:27:32 +01:00
Peter KW
388a430613 Tiny fix 2024-03-18 17:22:40 +01:00
Peter KW
2493932f77 Removed username prop, no longer used 2024-03-18 17:20:29 +01:00
al8763be
fbf46b7cd0 Merge branch 'frontend' into BumBranch 2024-03-18 17:14:23 +01:00
pavel Hamawand
b93ef48500 minor fix 2024-03-18 17:07:48 +01:00
pavel Hamawand
0f7f866cde minor fix 2024-03-18 17:06:05 +01:00
pavel Hamawand
3ec0d168eb minor fix 2024-03-18 17:05:37 +01:00
pavel Hamawand
e47b251c14 implement component 2024-03-18 17:03:02 +01:00
pavel Hamawand
39983c7f6f Render Project List 2024-03-18 17:01:11 +01:00
pavel Hamawand
f61ef87d5e Fetch Projects from API - needs fixing 2024-03-18 16:58:46 +01:00
pavel Hamawand
90afe80408 Implement State management 2024-03-18 16:54:16 +01:00
pavel Hamawand
4173003d32 initial component Setup 2024-03-18 16:53:13 +01:00
pavel Hamawand
b8f669e454 new component UserProjectListAdmin 2024-03-18 16:52:40 +01:00
pavel Hamawand
22b9580f51 fix backbutton 2024-03-18 16:52:40 +01:00
pavel Hamawand
8291f4caf3 delete component 2024-03-18 16:52:40 +01:00
pavel Hamawand
576a137038 new component 2024-03-18 16:52:40 +01:00
Peter KW
ace11570a5 Merge branch 'gruppDM' into gruppPP 2024-03-18 16:49:02 +01:00
Peter KW
2bd9878359 Merge branch 'frontend' into gruppPP 2024-03-18 16:44:13 +01:00
Imbus
0c2617d0cb Full fix for getProject route, testing, integration testing and frontend-API code 2024-03-18 16:42:35 +01:00
dDogge
9ad89d6063 Handler for SignReport added and corresponding test in testing.pu added 2024-03-18 16:01:51 +01:00
Mattias
437520183b removed import 2024-03-18 15:46:49 +01:00
Mattias
b962a856f4 fixes 2024-03-18 15:46:23 +01:00
Mattias
3bcb7a89b8 edited paths 2024-03-18 15:39:29 +01:00
Mattias
43f13dc534 Old paths in main are back 2024-03-18 15:33:19 +01:00
Mattias
1893340c63 Changed name of new component and minor fix 2024-03-18 15:17:53 +01:00
borean
3a3690e3da ignore go.work.sum 2024-03-18 15:12:11 +01:00
dDogge
4979378779 Handler for AddUserToProject and promoteToAdmin, successfully tested 2024-03-18 14:47:15 +01:00
Mattias
3106e0f445 Fixes for viewing-report-component 2024-03-18 14:46:32 +01:00
al8763be
d6ce4a3c57 errMessage fixed 2024-03-18 14:43:28 +01:00
Mattias
b82f73d192 Removed button 2024-03-18 14:42:21 +01:00
Mattias
0634f83689 Now displaying the right component 2024-03-18 14:40:32 +01:00
Mattias
69d4067209 Added new component for viewing weeklyreport 2024-03-18 14:40:20 +01:00
pavel Hamawand
409d973d8a minor fix 2024-03-18 14:35:56 +01:00
dDogge
76fefd2b24 Added function to check if someone is admin 2024-03-18 13:32:55 +01:00
Davenludd
3f8d56963b Add ProjectNameContext to NewWeeklyReport and handle project selection in YourProjectsPage 2024-03-18 12:49:58 +01:00
Davenludd
164ff781b3 Add useEffect hook to handle authority navigation and log response in YourProjectsPage 2024-03-18 10:44:15 +01:00
Davenludd
f6b2d17b97 Update Header component to use localStorage for username 2024-03-18 10:44:15 +01:00
Mattias
7a6b875aeb Token is now fetched from local storage 2024-03-18 10:28:20 +01:00
Davenludd
68b25350ef Add username to local storage and retrieve it in YourProjectsPage 2024-03-18 10:22:38 +01:00
Davenludd
3c8b7f9da2 Merge branch 'gruppPP' into gruppDM 2024-03-18 10:10:40 +01:00
Davenludd
5bef01396b Add user-specific project list and API integration 2024-03-18 10:09:12 +01:00
Mattias
711f46f960 fixed import 2024-03-18 09:56:07 +01:00
Davenludd
ba58fea1e3 Merge branch 'frontend' into gruppDM 2024-03-18 09:01:05 +01:00
Peter KW
92119dd49e Added path + fixed import 2024-03-18 01:56:04 +01:00
Peter KW
8a2152395f Small fixes 2024-03-18 01:31:58 +01:00
Peter KW
516784c6bb Merge branch 'frontend' into gruppPP 2024-03-18 00:51:34 +01:00
Peter KW
4876038613 Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-03-18 00:48:21 +01:00
Peter KW
f7b8ea7d97 Path fix 2024-03-18 00:46:04 +01:00
Peter KW
bd588048dd Import fix 2024-03-18 00:45:49 +01:00
Peter KW
070e9cc1e5 Link to button 2024-03-18 00:45:22 +01:00
Peter KW
844e94ed26 Small fixes to layout and added 2024-03-18 00:44:56 +01:00
Peter KW
0044b61ac8 Uses input field component instead now 2024-03-18 00:44:11 +01:00
Peter KW
3b49faec2d Import fix 2024-03-18 00:42:22 +01:00
Peter KW
8249678488 Using local storage for token 2024-03-18 00:42:05 +01:00
Peter KW
24e7e68ea0 Logout functionality 2024-03-18 00:41:46 +01:00
Peter KW
200da51633 AddProject component 2024-03-18 00:41:18 +01:00
Peter KW
d97516e0cd Added some paths again 2024-03-18 00:40:42 +01:00
al8763be
953c06212d API Changes 2024-03-18 00:22:32 +01:00
al8763be
7cc74866fc Merge branch 'dev' into frontend 2024-03-18 00:13:04 +01:00
al8763be
b45a20c9f5 Merge branch 'dev' into frontend 2024-03-18 00:06:20 +01:00
Imbus
741ad50ccf Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-17 23:51:52 +01:00
dDogge
f6e4603603 Added handler for SignWeeklyReport named SignReport in handlers_report_related 2024-03-17 23:31:52 +01:00
Imbus
c487b47373 Merge branch 'BumBranch' of github.com:imbus64/TTime into BumBranch 2024-03-17 23:17:18 +01:00
Imbus
9070846b0b Fixing getWeeklyReport tests 2024-03-17 23:17:06 +01:00
al8763be
ff6af1d250 Merge branch 'dev' into BumBranch 2024-03-17 23:11:49 +01:00
al8763be
9f20d46fa6 Test for getWeeklyReport 2024-03-17 22:57:19 +01:00
Imbus
e012b6ff12 Error checking in register component, redirect to login if success 2024-03-17 22:48:38 +01:00
al8763be
c3ce25236f Full implementation of getWeeklyProject 2024-03-17 22:35:51 +01:00
al8763be
6823102b44 Deleted API test 2024-03-17 22:27:21 +01:00
al8763be
06c50e7a42 Merge branch 'frontend' into BumBranch 2024-03-17 22:20:48 +01:00
al8763be
7339a69bce getWeeklyReport 2024-03-17 22:20:34 +01:00
Imbus
5696310a68 Merge branch 'dev' into frontend 2024-03-17 22:19:05 +01:00
Davenludd
ead1482e50 Merge branch 'frontend' into gruppDM 2024-03-17 21:39:19 +01:00
Davenludd
07aaab5530 Merge branch 'frontend' into gruppDM 2024-03-17 21:22:10 +01:00
dDogge
40caa2d158 Changed name to a test in db_test.go 2024-03-17 20:35:48 +01:00
dDogge
37bbbb6098 Added SignWeeklyReport function and 2 corresponding test, also small change to WeeklyReport.go 2024-03-17 20:33:53 +01:00
Imbus
e03727613d Extremely important formatting 2024-03-17 20:04:29 +01:00
dDogge
8a34fc07fa Weekly_report fixed for real this time 2024-03-17 19:58:44 +01:00
dDogge
b93df693d2 Fixed weekly_report 2024-03-17 19:56:16 +01:00
al8763be
447f2b73eb Merge branch 'gruppPP' into BumBranch 2024-03-17 19:42:01 +01:00
Peter KW
888256e9f6 Changed logincheck type and error clarification 2024-03-17 19:38:51 +01:00
Imbus
3683552af8 Verbose debug printing in register endpoint 2024-03-17 19:33:13 +01:00
al8763be
c7fe5e8775 Merge branch 'dev' into frontend 2024-03-17 19:28:57 +01:00
al8763be
402b0ac08b Update API.test.ts 2024-03-17 19:28:03 +01:00
Imbus
9240d5e052 Verbose debug printing in login endpoint 2024-03-17 19:24:13 +01:00
Peter KW
1977125923 Now exporting APIResponse interface 2024-03-17 19:18:03 +01:00
Peter KW
b999e47f94 Changes to handlesubmit 2024-03-17 19:17:34 +01:00
Peter KW
e7e79ced13 Now checks if user is in database with api.login and sets proper authority level based on name for now 2024-03-17 19:16:57 +01:00
al8763be
1a412dc8a0 Merge branch 'frontend' of https://github.com/imbus64/TTime into frontend 2024-03-17 19:08:17 +01:00
Imbus
eae252c2a4 Merge branch 'frontend' of github.com:imbus64/TTime into frontend 2024-03-17 19:07:55 +01:00
Imbus
30cf0b6065 Making linter bro happy 2024-03-17 19:07:49 +01:00
al8763be
693b5fb9ae Merge branch 'dev' into frontend 2024-03-17 19:06:14 +01:00
al8763be
14668ea675 Test file for API 2024-03-17 19:05:39 +01:00
dDogge
3e00a532cf Added handler for GetWeeklyReport 2024-03-17 18:05:54 +01:00
dDogge
a77e57e496 Added GetWeeklyReport function and corresponding test 2024-03-17 17:58:02 +01:00
dDogge
c90d495636 Added new types and changed SQL since apperently sqlite does use autoincrement 2024-03-17 17:47:31 +01:00
Imbus
ec46d29423 Merge branch 'imbs' into dev 2024-03-17 16:56:00 +01:00
Mattias
17a081e816 minor fixes 2024-03-17 16:55:51 +01:00
Imbus
d6fc0594a9 Splitting backend endpoints into smaller bits 2024-03-17 16:55:40 +01:00
Imbus
1be3ee89c2 Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-17 16:42:02 +01:00
Imbus
ec36054bd9 Merge branch 'imbs' into dev 2024-03-17 16:41:51 +01:00
Imbus
670ed46d51 WeeklyReport type as represented in the db 2024-03-17 16:41:29 +01:00
Mattias
81893ae3e8 Minor fixes 2024-03-17 16:24:09 +01:00
Davenludd
9dd47f3d71 Update import paths for Types 2024-03-17 16:23:37 +01:00
pavel Hamawand
141e5c8bb6 minor fix 2024-03-17 16:21:36 +01:00
Davenludd
2ba10e837a Refactor API.ts to handle error response in submitWeeklyReport 2024-03-17 16:14:50 +01:00
al8763be
d57fe55074 Changes to SubmitWeeklyReport 2024-03-17 16:11:03 +01:00
Peter KW
b7fcafd75c Merge branch 'frontend' into gruppPP 2024-03-17 15:54:06 +01:00
Davenludd
830c234325 Update login response type in API.ts 2024-03-17 15:50:50 +01:00
al8763be
386f7c3a94 API Login return { success: true, data: data.token } 2024-03-17 15:49:34 +01:00
al8763be
d11e5e64f6 API Login return { success: true, data } data as JSON with token 2024-03-17 15:41:23 +01:00
Peter KW
060dc1ee3d Merge branch 'frontend' into gruppPP 2024-03-17 15:39:16 +01:00
Davenludd
c708ec47ca Merge branch 'frontend' into gruppDM 2024-03-17 15:36:41 +01:00
al8763be
a28bab459a API login returns { success: true, data: token } 2024-03-17 15:36:39 +01:00
dDogge
21cc7ff8a3 Added signed_by attribute to weekly_report SQL table 2024-03-17 15:34:48 +01:00
al8763be
9511d509ca API imports fixed 2024-03-17 15:31:15 +01:00
Davenludd
b1f57e5ed8 Merge branch 'frontend' into gruppDM 2024-03-17 15:28:20 +01:00
al8763be
f5787fbc2e correct routes in main 2024-03-17 15:23:21 +01:00
al8763be
179a5196e9 Merge branch 'BumBranch' into frontend 2024-03-17 15:10:19 +01:00
al8763be
5327ac7ad1 lint + login function for API 2024-03-17 15:03:34 +01:00
Imbus
ff00b984fe Formatting and typing to make linter happy v2 2024-03-17 14:52:13 +01:00
Davenludd
6d5c25be5c Refactor Register component to use InputField component 2024-03-17 14:49:42 +01:00
Mattias
d6d2b6d170 Visual fixes 2024-03-17 14:49:42 +01:00
Imbus
cab7c3bdfd Formatting and typing to make linter happy 2024-03-17 14:44:25 +01:00
al8763be
2eecf17d42 Login feature API 2024-03-17 14:43:09 +01:00
Imbus
58b74e3f04 Merge branch 'dev' into frontend 2024-03-17 14:38:44 +01:00
Imbus
7c21677310 Migrations fix in go 2024-03-17 14:38:20 +01:00
Davenludd
46eebee84f Refactor API.ts for improved readability and maintainability 2024-03-17 14:21:38 +01:00
Mattias
f3931f905a Minor changes 2024-03-17 14:19:57 +01:00
pavel Hamawand
79eb59ad46 button fix 2024-03-17 14:13:35 +01:00
Mattias
62f1926305 Corrected NewWeeklyReport (prev. TimeReports) and changed imports 2024-03-17 13:39:16 +01:00
Peter KW
9bab627255 Change typ on button to submit 2024-03-17 12:57:18 +01:00
al8763be
8d0da111eb API - SubmitWeeklyReport & getUserProjects added 2024-03-17 12:35:53 +01:00
Peter KW
b6535c883d Small fix 2024-03-17 11:34:39 +01:00
Peter KW
3e73b11698 Merge branch 'frontend' into gruppPP 2024-03-17 11:02:44 +01:00
al8763be
d6d4eb3336 Merge branch 'master' into gruppDM 2024-03-17 10:51:23 +01:00
Peter KW
5e0b96ed87 Merge branch 'frontend' into gruppPP 2024-03-17 10:43:49 +01:00
Peter KW
8cf873a98b Changed so that most functionality was moved to separate components 2024-03-17 10:39:57 +01:00
Peter KW
15ec2108d3 Everything run from this file 2024-03-17 10:38:25 +01:00
Peter KW
02332c284b Login field component 2024-03-17 10:36:29 +01:00
Peter KW
94f5d3f85b Component for checking login, to be replaced with proper fetching 2024-03-17 10:35:48 +01:00
Peter KW
217b7b4a22 Input field component 2024-03-17 10:34:47 +01:00
Peter KW
2921458d82 Made backround animation on login page to its own component 2024-03-17 10:34:15 +01:00
Peter KW
c12e19770e Restructuring, less paths 2024-03-17 10:33:37 +01:00
Imbus
887f31dde0 goTypes generated from go code with tygo 2024-03-17 04:23:26 +01:00
Imbus
dbb2ff84e5 WeeklyReport testing 2024-03-17 04:16:54 +01:00
Imbus
0d053add5e Add some sanity checking in SubmitWeeklyReport route 2024-03-17 04:16:26 +01:00
Imbus
23dd22eab5 Json field alias for WeeklyReport 2024-03-17 04:14:40 +01:00
Imbus
b69f8d82ff Better testing comments 2024-03-17 03:46:16 +01:00
Imbus
2e44d14370 Extending test script 2024-03-17 03:39:31 +01:00
Imbus
04d7a2cdec Test script 2024-03-17 01:55:24 +01:00
Imbus
c13378d3b9 Proper login endpoint functionality 2024-03-17 01:32:10 +01:00
Imbus
c6d9307979 Fix for CreateProject handler 2024-03-16 22:57:48 +01:00
Imbus
d99de54c5d Major changes related to reports 2024-03-16 22:47:19 +01:00
Imbus
7f5270f536 Merge branch 'gruppDM' of github.com:imbus64/TTime into gruppDM 2024-03-16 18:38:50 +01:00
Davenludd
d58720cdbd Refactor Register component to use InputField component 2024-03-16 18:38:42 +01:00
Mattias
92a4808cdd Created new type for timereport 2024-03-16 18:38:42 +01:00
Mattias
f3c5ce57eb Submit-button has been moved to the timereport 2024-03-16 18:38:42 +01:00
Mattias
3419898c66 Created groundwork for the timereport 2024-03-16 18:38:42 +01:00
Mattias
aae05acedb Added the register component to content 2024-03-16 18:38:42 +01:00
Mattias
a225020f6f Visual fixes 2024-03-16 18:38:42 +01:00
Mattias
2ce480707a Removed duplicate path 2024-03-16 18:38:42 +01:00
pavel Hamawand
17edf6b911 backButton 2024-03-16 18:01:14 +01:00
pavel Hamawand
31e3b99448 backbutton 2024-03-16 18:00:10 +01:00
pavel Hamawand
38378c7a10 babkbutton 2024-03-16 17:59:25 +01:00
pavel Hamawand
92336ff4fd backbutton 2024-03-16 17:58:27 +01:00
pavel Hamawand
17e9fa9d0a backbutton 2024-03-16 17:57:51 +01:00
pavel Hamawand
bdd6f68470 Time / role link 2024-03-16 17:56:59 +01:00
pavel Hamawand
9aa5087871 time / activity link 2024-03-16 17:54:34 +01:00
pavel Hamawand
8164ba9040 fix back button 2024-03-16 17:50:15 +01:00
pavel Hamawand
49305d507f link to projectMembers 2024-03-16 17:48:23 +01:00
Imbus
3526decbad Merge imbs 2024-03-16 17:46:08 +01:00
Imbus
151d6de39b Tygo for go->typescript type generation 2024-03-16 17:43:38 +01:00
Imbus
3c87fd4d8c New api interface 2024-03-16 17:42:28 +01:00
pavel Hamawand
e837f6e746 link to your time Reports 2024-03-16 17:37:09 +01:00
Imbus
17c8a17ebf IF EXISTS condition in salts table 2024-03-16 17:32:51 +01:00
pavel Hamawand
a6846ad7a4 fix backbutton 2024-03-16 17:28:45 +01:00
pavel Hamawand
dc84bc997e link to unsigned reports 2024-03-16 17:25:43 +01:00
Imbus
47d7d9fe3c Activity type database changes with interface changes in go 2024-03-16 17:22:55 +01:00
pavel Hamawand
554cf9b12e remove backbutton 2024-03-16 17:20:14 +01:00
Davenludd
7fb220f768 Refactor Register component to use InputField component 2024-03-16 15:14:03 +01:00
Mattias
d71752ad6f Created new type for timereport 2024-03-16 13:11:39 +01:00
Mattias
87c044b5bf Submit-button has been moved to the timereport 2024-03-16 13:11:26 +01:00
Mattias
966f8540df Created groundwork for the timereport 2024-03-16 13:10:46 +01:00
Mattias
d227ffc6ae Added the register component to content 2024-03-16 02:47:39 +01:00
Mattias
a67c44564f Visual fixes 2024-03-16 02:47:17 +01:00
Mattias
148af4e499 Removed duplicate path 2024-03-16 02:46:58 +01:00
Peter KW
9ff3e9314c Small change to header; uses useLocation() to get correct name 2024-03-16 02:27:33 +01:00
Peter KW
edf503776a Changed design a little so that it uses the projectlist component, added some fake projects 2024-03-16 02:23:52 +01:00
Peter KW
4a864e1ab5 Finished design and added fake users for testing 2024-03-16 02:20:36 +01:00
Peter KW
8fdd6c0b1f Finished basic view for this page 2024-03-16 02:18:28 +01:00
Peter KW
466c25a7c2 A component for listing an array of users where every user gets a link, made for AdminManageUsers.tsx 2024-03-16 02:16:06 +01:00
Peter KW
f963ca6ae5 A component for listing an array of projects where every project gets a link, made for YourProjectsPage.tsx 2024-03-16 02:15:39 +01:00
dDogge
04e17a1721 Added comments to various functions 2024-03-15 16:57:42 +01:00
Peter KW
f0fc465d1a Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-03-15 16:54:26 +01:00
Imbus
976ce5900c Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-15 16:45:33 +01:00
dDogge
018dc24516 Handler for GetProject from db.go added in global_stage.go 2024-03-15 16:45:26 +01:00
dDogge
581209742a Added GetProject in db.go and corresponding test 2024-03-15 16:45:26 +01:00
pavel Hamawand
2da40e1f54 change back button UserViewTimeReportsPage 2024-03-15 15:32:06 +01:00
dDogge
78f5415d9a Handler for GetProject from db.go added in global_stage.go 2024-03-15 15:28:45 +01:00
pavel Hamawand
8430d88a07 change back button UserProjectPage 2024-03-15 15:28:35 +01:00
pavel Hamawand
a26499bde9 change backbutton UserEditTimeReport and UserNewTimeReport 2024-03-15 15:22:55 +01:00
dDogge
2468fe8fab Added GetProject in db.go and corresponding test 2024-03-15 15:14:45 +01:00
Peter KW
6789cc97ce Added back button to page 2024-03-15 14:57:49 +01:00
Mattias
4920966388 Additional register-layout changes 2024-03-15 14:52:05 +01:00
Peter KW
44aea9b765 Made a back button component since we will have one on almost every page 2024-03-15 14:51:46 +01:00
pavel Hamawand
69b0318eb0 remove edit button and fix back button 2024-03-15 14:48:40 +01:00
Davenludd
a388109a8a Add type attribute to button elements in Admin Pages 2024-03-15 14:34:08 +01:00
Davenludd
a16d1d8011 Add type="button" to buttons in PMChangeRole, PMOtherUsersTR, PMProjectMembers, PMProjectPage, PMTotalTimeActivity, PMTotalTimeRole, PMUnsignedReports, and PMViewUnsignedReport components 2024-03-15 14:32:17 +01:00
Davenludd
855dccdfa4 Add type="button" to buttons 2024-03-15 14:30:53 +01:00
Davenludd
a49cfc9f01 Update button styles and add type prop 2024-03-15 14:30:45 +01:00
Mattias
6a25eca01c Changed layout for register-component 2024-03-15 14:16:11 +01:00
Imbus
7f46202633 Merge branch 'gruppDM' of github.com:imbus64/TTime into gruppDM 2024-03-15 12:36:48 +01:00
pavel Hamawand
44b65858aa remove unused import 2024-03-15 12:35:46 +01:00
Imbus
31d82fd1d5 Merge branch 'gruppDM' of github.com:imbus64/TTime into gruppDM 2024-03-15 12:35:35 +01:00
pavel Hamawand
1f16afe528 remove unused import 2024-03-15 12:35:20 +01:00
Imbus
fe91f798ba Better readme build instructions for windows 2024-03-15 12:30:37 +01:00
Imbus
fb67825e29 Experimental port to CGO-free modernc.org/sqlite, no more WSL 2024-03-15 11:02:33 +01:00
pavel Hamawand
dc902855f4 add edit button to viewTimeReport 2024-03-15 04:42:38 +01:00
pavel Hamawand
901bcd39c5 link to viewTimeReport 2024-03-15 04:42:38 +01:00
Peter KW
cd1dbb494c Small fix to links in admin-menu page 2024-03-15 02:29:25 +01:00
Peter KW
2ad7146588 Added some functionality to login page. Checks username + password and compares with "fake" users to determine which page to get 2024-03-15 02:28:28 +01:00
pavel Hamawand
ef28e1743e links to all projects 2024-03-15 02:03:22 +01:00
pavel Hamawand
92cf36d178 increased responsiveness - outer div 2024-03-15 01:42:43 +01:00
pavel Hamawand
029e7922d9 remove unused import 2024-03-14 23:49:39 +01:00
dDogge
2409598c6e Handler ProjectRoleChange in global_state.go added for ChangeUserRole in db.go 2024-03-14 23:06:10 +01:00
dDogge
f2a4c417aa Handler ListAllUsersProject in global_state.go added for GetAllUsersProject in db.go 2024-03-14 22:56:50 +01:00
dDogge
34ad6ac777 Handler ListAllUsers in global_state.go added for GetAllUserApplication in db.go 2024-03-14 22:48:06 +01:00
Imbus
aa7c5de1fc GetUserProjects handler 2024-03-14 21:25:14 +01:00
Imbus
e1153934c6 Database interface extension, GetAllProjects, GetUserRole, GetProjectsForUser 2024-03-14 19:48:49 +01:00
Imbus
df7ca1ab90 Merge branch 'frontend' into dev 2024-03-14 18:40:07 +01:00
Imbus
baade40d77 Merge branch 'gruppDM' into frontend 2024-03-14 18:39:51 +01:00
Imbus
7f6a9f6fd1 Merge frontend with dev 2024-03-14 18:38:25 +01:00
Imbus
3bf0c34a5f Merge branch 'gruppDM' into frontend 2024-03-14 18:36:27 +01:00
Imbus
1c0884bb5d Formatting and edits to make linter happy 2024-03-14 18:32:56 +01:00
Imbus
6cd940866e Merge frontend into dev 2024-03-14 18:31:00 +01:00
Imbus
5e8af6098b Merge branch 'imbs' into dev 2024-03-14 18:27:12 +01:00
Imbus
7cb2e4a363 Better wsl instructions for recent versions of go & node 2024-03-14 18:24:51 +01:00
Imbus
a291972f82 Makefile targets for generating UML 2024-03-14 18:14:28 +01:00
dDogge
676d6637a2 Added GetAllUsersApplication and corresponding test 2024-03-14 16:25:54 +01:00
dDogge
0e1ea15cc9 Added GetAllUsersProject and corresponding test 2024-03-14 16:01:56 +01:00
Davenludd
8690e381c8 Add links to navigate between pages projectPage & NewTimeReport 2024-03-14 15:49:13 +01:00
Davenludd
41e1c32ee0 Added time report component to various pages 2024-03-14 15:17:38 +01:00
Mattias
46c4a5dc92 Added more paths in main 2024-03-14 14:59:57 +01:00
Davenludd
03e2be0a46 Add AdminProjectChangeUserRole page & buttons 2024-03-14 14:53:51 +01:00
Davenludd
39edc419df Add AdminProjectViewMemberInfo page & buttons 2024-03-14 14:53:51 +01:00
Davenludd
027bce6dfc Add AdminProjectAddMember page & buttons 2024-03-14 14:53:51 +01:00
Davenludd
da730a2d18 Add AdminProjectManageMembers page & buttons 2024-03-14 14:53:51 +01:00
Davenludd
dd370d86e3 Add AdminProjectStatistics page & buttons 2024-03-14 14:53:51 +01:00
Davenludd
ca7e4c6189 Add AdminProjectPage page & buttons 2024-03-14 14:53:51 +01:00
Mattias
19e3567c78 More paths in main 2024-03-14 14:42:26 +01:00
Davenludd
6a68ad1c3f Add AdminAddProject page & buttons 2024-03-14 14:39:34 +01:00
Davenludd
3047db28f6 Add AdminManageProjects page & buttons 2024-03-14 14:39:34 +01:00
Davenludd
2d5de569ae Add AdminChangeUsername page & buttons 2024-03-14 14:39:34 +01:00
Davenludd
5a6fe1c472 Add AdminViewUserInfo page & buttons 2024-03-14 14:39:34 +01:00
Imbus
555a3fa7ec Merge imbs again 2024-03-14 14:38:53 +01:00
Imbus
be04ba148d Merge imbs 2024-03-14 14:37:50 +01:00
Mattias
1b3660eb83 Added more paths in main 2024-03-14 14:33:43 +01:00
dDogge
0bd1fc5397 Merge remote-tracking branch 'refs/remotes/gh/dev' into dev 2024-03-14 14:31:01 +01:00
Davenludd
69df212fde Add AdminAddUser page & buttons 2024-03-14 14:30:56 +01:00
Davenludd
434879c26c Add AdminManageUsers page & buttons 2024-03-14 14:30:56 +01:00
dDogge
2b491ed798 Clean up 2024-03-14 14:29:37 +01:00
Mattias
c5d5c389dd Added new paths in main 2024-03-14 14:29:10 +01:00
Imbus
0824a344e3 Target for dumping database content to file 2024-03-14 14:10:25 +01:00
Imbus
dd4809d631 Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-14 14:08:27 +01:00
Imbus
3790e8a3c6 Test cleaning 2024-03-14 13:48:56 +01:00
Imbus
d12e3a26ef Role change database interface with corresponding tests 2024-03-14 13:47:04 +01:00
Imbus
6c8abf1f53 Database refactor and test coverage 2024-03-14 13:39:56 +01:00
dDogge
f0745c5a75 Added ChangeUserRole and corresponding test, also GetProjectId should be fixed. 2024-03-14 13:27:57 +01:00
Davenludd
03f350f303 Made a new component for time reporting 2024-03-14 13:21:19 +01:00
Hollgy
327f90e448 Co-authored-by: Imbus <imbus64@users.noreply.github.com> 2024-03-14 11:24:31 +01:00
Hollgy
1f2bff62f9 Refactor, lint removal 2024-03-14 11:23:57 +01:00
Davenludd
7e319e34c9 Change logo 2024-03-14 11:08:07 +01:00
Mattias
60774f6324 Added more paths in main 2024-03-14 11:00:55 +01:00
Mattias
db647c6e7c Added buttons 2024-03-14 11:00:55 +01:00
Mattias
45749afe69 Added buttons 2024-03-14 11:00:55 +01:00
Davenludd
2cce3f3ab4 Add time/activity and time/role buttons to PMProjectMembers page 2024-03-14 10:59:51 +01:00
Davenludd
a67e43e537 Add back button to PMUnsignedReports component 2024-03-14 10:57:42 +01:00
Davenludd
ce5d6d2837 Add "Back" button to PMTotalTimeRole component 2024-03-14 10:56:01 +01:00
Davenludd
f9260976df Add "Back" button to PMTotalTimeActivity component 2024-03-14 10:55:30 +01:00
Davenludd
5c0cf5fc33 Add buttons to PMViewUnsignedReport component 2024-03-14 10:54:37 +01:00
Davenludd
8a2724de5e Add save and back buttons to PMChangeRole component 2024-03-14 10:52:48 +01:00
Davenludd
7aa83b1d99 Add back button to PMOtherUsersTR component 2024-03-14 10:50:52 +01:00
Davenludd
2c9d3baafa Added button to PMProjectMembers 2024-03-14 10:49:15 +01:00
Davenludd
a5f15e5c06 Create all files for ProjectManagerPages 2024-03-14 10:41:10 +01:00
Mattias
c1aa0769bb Added path in main 2024-03-14 10:33:00 +01:00
Mattias
6be1060cff Added buttons to UserEditTimeReportPage 2024-03-14 10:32:39 +01:00
Imbus
6afe6345cf renewToken API interface 2024-03-13 20:56:47 +01:00
Hollgy
e1bf25148e Co-authored-by: Imbus <imbus64@users.noreply.github.com> 2024-03-13 17:56:31 +01:00
Hollgy
a4b19e32eb Boilerplate added for register, non functional 2024-03-13 17:56:04 +01:00
Imbus
b5c2987281 Add project API interface in frontend 2024-03-13 17:52:56 +01:00
Imbus
1c87380db7 Mounting handler for adding project 2024-03-13 17:52:37 +01:00
Imbus
974d86c2d9 Merge branch 'dev' into frontend 2024-03-13 17:07:12 +01:00
Imbus
3a7663124d Basic api funcitonality in frontend 2024-03-13 17:06:26 +01:00
Imbus
6b09cfbf23 New directories for Types and API in frontned 2024-03-13 16:25:50 +01:00
Imbus
6601ea52d4 Merge branch 'imbs' of github.com:imbus64/TTime into imbs 2024-03-13 16:21:05 +01:00
Mattias
42498ca1c4 Changed name on exampletext 2024-03-13 16:18:06 +01:00
Imbus
8db4ff2ec7 Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-13 16:17:02 +01:00
Davenludd
029fdd85b9 Minor change LoginPage 2024-03-13 16:11:36 +01:00
Davenludd
8bb4a1c893 Change logo to svg 2024-03-13 16:07:52 +01:00
Mattias
7c51f586ce Created three new empty pages for basic user 2024-03-13 14:18:06 +01:00
Imbus
2630a0c9ef Merge branch 'frontend' of github.com:imbus64/TTime into frontend 2024-03-13 14:17:22 +01:00
Mattias
41674c3969 Removed example pages Home and Settings aswell as the react-logo 2024-03-13 14:17:11 +01:00
Peter KW
1672b100d9 added new page for admin and path in main 2024-03-13 14:17:11 +01:00
Imbus
d9025611e1 Merge branch 'imbs' of github.com:imbus64/TTime into imbs 2024-03-13 14:07:12 +01:00
Imbus
07b9f6fca4 Containerfile fix for new path to main 2024-03-13 14:03:26 +01:00
Imbus
4d23f8acea Merge branch 'frontend' of github.com:imbus64/TTime into frontend 2024-03-13 13:51:35 +01:00
Imbus
8c8e57cb50 Merge branch 'frontend' 2024-03-13 13:42:49 +01:00
Imbus
25fdf3bb9b Nuke dead code 2024-03-13 12:29:15 +01:00
Imbus
c4632104a8 Commenting out trigger related to salts 2024-03-13 11:46:17 +01:00
Imbus
736cebe036 Sql comments and salts table 2024-03-12 20:44:54 +01:00
Imbus
9b67a580da Gluecode for database/handlers 2024-03-12 20:44:40 +01:00
Imbus
685a40e5d1 Containerfile fix for new path to main 2024-03-09 15:07:42 +01:00
borean
ad4d439887 cleanup 2024-03-08 15:07:25 +01:00
borean
d06122864e pls no error 2024-03-08 15:06:32 +01:00
borean
f8277617a9 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev 2024-03-08 15:02:18 +01:00
borean
6acfdd36b2 cleanup 2024-03-08 14:56:24 +01:00
borean
0b23c5f8f1 Merge branch 'borean-dev' into dev 2024-03-08 14:55:10 +01:00
Mattias
ac6638b344 Removed example pages Home and Settings aswell as the react-logo 2024-03-08 11:36:36 +01:00
Imbus
394b283712 Typo 2024-03-08 11:25:31 +01:00
Imbus
02c9c502b8 Eralchemy2 target for generating er diagrams 2024-03-08 11:22:09 +01:00
Imbus
a7ebfe7110 Ignore formatter output in main 2024-03-08 10:32:02 +01:00
Imbus
efc95e7fea Add target for formatting swag docs & formatting swag docs 2024-03-08 10:30:17 +01:00
Imbus
b52f13ee8b Better developer feedback 2024-03-08 10:25:22 +01:00
Imbus
d4a2a00f69 Makefile update with doc generation and convenience installers 2024-03-08 10:17:51 +01:00
Imbus
ae0b0895b2 Version controlling generated docs 2024-03-08 10:17:27 +01:00
Imbus
13c12b424a Mounting handlers and basic swagger docs example 2024-03-08 10:16:56 +01:00
Imbus
f75cb7e9fc Pulling in deps related to swagger 2024-03-08 10:15:05 +01:00
Imbus
bb81a2973e Moving main out of cmd to accomodate swag cli tool 2024-03-08 09:10:20 +01:00
Imbus
944f9b6ccc Removed overly verbose debug printing from db migration 2024-03-08 07:48:25 +01:00
Imbus
12536d5e89 Fixing inconsistent sql in db interface 2024-03-08 07:47:32 +01:00
Imbus
2351a0cb4a Github actions CI for db migrations 2024-03-08 07:42:11 +01:00
Imbus
a77b388f8e Warning in make migrate target 2024-03-08 07:34:26 +01:00
Imbus
f5d5eee267 Breaking changes in projects table related to naming 2024-03-08 07:33:58 +01:00
Imbus
553ba2c7c6 New project_role table to represent possible roles 2024-03-08 07:33:31 +01:00
borean
eec3a45509 Added GetProjectId and AddUserToProject, WIP 2024-03-07 23:24:54 +01:00
borean
5a85f2bf81 first draft of AddTimeReport in db.go 2024-03-07 21:57:27 +01:00
borean
7ed986e4eb update 2024-03-07 20:58:50 +01:00
Peter KW
d7cf291836 added new page for admin and path in main 2024-03-07 17:48:43 +01:00
dDogge
1c5cbd768d Merge remote-tracking branch 'refs/remotes/gh/dev' into dev 2024-03-07 14:25:55 +01:00
dDogge
bd434c3da7 Added template for various new functions in db.go and corresponding tests in db_test.go 2024-03-07 14:25:28 +01:00
Imbus
c9406b8e50 Ported Justfile to Makefile 2024-03-07 14:16:50 +01:00
Imbus
9b9f6dd962 Merge branch 'dev' of git.silversoft.se:Imbus/TTime into dev 2024-03-07 13:30:26 +01:00
dDogge
18eefab292 Implemented PromoteToAdmin and corresponding Test 2024-03-07 13:21:47 +01:00
dDogge
3adf0b7ef5 Admin SQL table added 2024-03-07 13:09:09 +01:00
Imbus
a202eaa610 Readme wsl notes 2024-03-07 12:26:08 +01:00
Imbus
acff254aa5 Indirect -> direct dependency in go.mod 2024-03-07 12:13:29 +01:00
Imbus
3e000358a7 Formatting and using for-of loop 2024-03-07 11:53:06 +01:00
Imbus
09ec0a0bb0 Merge 2024-03-07 11:49:31 +01:00
Imbus
40f7241550 Formatting and typing 2024-03-07 11:48:34 +01:00
Mattias
25284bb10e More formatting 2024-03-07 11:43:19 +01:00
Mattias
9c824f1d7b Slight formatting 2024-03-07 11:39:29 +01:00
Imbus
3175c62e6d Carmack level testing instructions 2024-03-07 11:33:06 +01:00
Davenludd
42a0745102 Small additional changes to YourProjectsPage 2024-03-07 10:45:23 +01:00
Mattias
7879394da3 Added a few pages and components to the frontend 2024-03-07 10:05:45 +01:00
Imbus
b31349ec37 Change test order in testall target 2024-03-06 15:44:18 +01:00
Imbus
4cfb227a1e Add install linter target 2024-03-06 15:40:24 +01:00
Imbus
524baf90d2 Hotfix for just errors related to podman 2024-03-06 15:34:43 +01:00
Imbus
c5f0bc509e Makefile target for installing just (hack of doom) 2024-03-06 15:15:41 +01:00
Imbus
d023bb0adf Untested implementation of login and renew endpoints 2024-03-06 12:51:46 +01:00
Imbus
f484674524 Pulled in jwt middleware for fiber 2024-03-06 12:51:00 +01:00
Imbus
85d080cebb Set fiber as direct dependency 2024-03-06 11:24:53 +01:00
Imbus
dcd9879d8d Demo of components with children and other props 2024-03-06 11:14:57 +01:00
Imbus
43afae5d77 Cleaning up CSS, minor refactor into pages Home & Settings, better docs 2024-03-06 11:14:08 +01:00
Imbus
70ae359856 Example of how to extend the API 2024-03-06 10:14:54 +01:00
Imbus
56693350be Pulling in and configuring react-router-dom 2024-03-06 10:09:59 +01:00
Imbus
4878ce1c39 Useless fix for the vite default template with tailwind 2024-03-06 09:51:26 +01:00
Imbus
9d39ff5bc2 Re-implemented demo button 2024-03-06 09:41:36 +01:00
borean
ae9ee91bc4 file correction 2024-03-04 19:34:35 +01:00
borean
a22dcb9f4e first push, broken import, WIP 2024-03-04 02:50:02 +01:00
Imbus
9e790696a5 Remove npm i from justfile testall target 2024-03-02 04:47:12 +01:00
Imbus
59ab430b06 Justfile target for a complete test run 2024-03-02 04:41:34 +01:00
Imbus
7a12b946a6 Fixing some issues found by the linter 2024-03-02 04:29:50 +01:00
Imbus
c54eccc625 golangci-lint in CI as well as a makefile target 2024-03-02 04:18:29 +01:00
Imbus
54cca2b151 Pulling in jest as a dependency for the frontent, example test included 2024-03-02 03:47:53 +01:00
Imbus
15a83882ef Omitting test from CI, including lint 2024-03-02 03:19:05 +01:00
Imbus
1be992bc34 Further wrestling with CI/CD 2024-03-02 03:14:45 +01:00
Imbus
3653146ce0 Wrestling with github CI/CD 2024-03-02 03:07:41 +01:00
Imbus
10abea8091 Merge branch 'master' of github.com:imbus64/TTime 2024-03-02 03:02:06 +01:00
Imbus
2808a0058f
Create node.js.yml 2024-03-02 03:01:26 +01:00
Imbus
b202665d75 Fix gh workflow 2024-03-02 02:56:00 +01:00
Imbus
a7c2e8f182 Merge branch 'master' of github.com:imbus64/TTime 2024-03-02 02:53:26 +01:00
Imbus
e93c4abba8
Create go.yml 2024-03-02 02:52:35 +01:00
Imbus
63596db60c Migrations readme warnings 2024-03-02 02:40:17 +01:00
Imbus
469f866554 Cleaning up examples in main and splitting the code into suitable packages 2024-03-02 02:38:26 +01:00
Imbus
5cefed405f Reduce verbosity of the tests 2024-03-02 02:31:20 +01:00
Imbus
a749857c98 User_roles table constraints 2024-03-01 07:47:06 +01:00
Imbus
b39ce645d6 Proper interface for the database 2024-02-29 20:33:20 +01:00
Imbus
4d510e5c51 Rename App -> Server, less confusing 2024-02-29 20:02:13 +01:00
Imbus
568fb0323e Makefile changes related to watch 2024-02-29 07:29:12 +01:00
Imbus
360689ce0c Configure tailwind for the frontend 2024-02-28 20:35:53 +01:00
Imbus
d51b9f78c2 Porting to fiber 2024-02-28 11:29:32 +01:00
Imbus
4c7a3fd9a0 Pulling in fiber as a dep 2024-02-28 11:28:57 +01:00
Imbus
4c7e660a20 Correcting path in makefile 2024-02-28 10:58:32 +01:00
Imbus
523a211933 Merge 2024-02-28 10:36:36 +01:00
Imbus
47040c8dc2 Format target 2024-02-28 10:34:51 +01:00
Imbus
2349fad4f4 Make use of the config 2024-02-28 08:39:42 +01:00
Imbus
34c0712825 Moving the migrations directory into database for embedding 2024-02-28 03:30:05 +01:00
Imbus
77e3324f79 Gitignore the config file 2024-02-28 03:21:33 +01:00
Imbus
e1cd596c13 Config parsing and database api changes 2024-02-28 03:21:13 +01:00
Imbus
06632c16da Better interface for writeconfig 2024-02-27 23:11:27 +01:00
Imbus
9d6cddb724 Various breaking changes to the database schema 2024-02-27 08:05:07 +01:00
Imbus
229ebc3a08 AddProject database interface with tests 2024-02-27 07:59:42 +01:00
Imbus
6f51151d64 Save backups in a backup directory 2024-02-27 07:45:38 +01:00
Imbus
6c82aa2047 Backup target for makefile 2024-02-27 07:43:20 +01:00
Imbus
6e48c0a088 Database migration readme 2024-02-27 06:09:09 +01:00
Imbus
ce1ce89b00 Some more example database interface code 2024-02-27 05:51:16 +01:00
Imbus
60e7b73f66 Unique username constraint on users table 2024-02-27 05:50:28 +01:00
Imbus
06076f93b7 Database interactions demo 2024-02-27 05:00:04 +01:00
Imbus
5be29d86af Makefile changes so that test depends on db.sqlite3 2024-02-27 04:59:24 +01:00
Imbus
296ed987d8 Make tests verbose and disable testing cache 2024-02-27 04:56:12 +01:00
Imbus
033402ffaf Mistake in migration scripts 2024-02-26 00:13:39 +01:00
Imbus
3ba446be5b Splitting migration scripts 2024-02-26 00:12:13 +01:00
Imbus
f9eb67da11 Direct dependency specification in go.mod 2024-02-26 00:06:04 +01:00
178 changed files with 17303 additions and 844 deletions

33
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,33 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

54
.github/workflows/golangci-lint.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: golangci-lint
on:
push:
branches: [ "master" ]
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.21"
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
# Require: The version of golangci-lint to use.
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
version: v1.54
# Optional: working directory, useful for monorepos
working-directory: ./backend
# Optional: golangci-lint command line arguments.
#
# Note: By default, the `.golangci.yml` file should be at the root of the repository.
# The location of the configuration file can be changed by using `--config=`
# args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
# Optional: if set to true, then all caching functionality will be completely disabled,
# takes precedence over all other caching options.
# skip-cache: true
# Optional: if set to true, then the action won't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
# skip-build-cache: true
# Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
# install-mode: "goinstall"

37
.github/workflows/node.js.yml vendored Normal file
View file

@ -0,0 +1,37 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
strategy:
matrix:
node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- run: npm ci
- run: npm run lint
- run: npm run build --if-present
- run: npm test

View file

@ -0,0 +1,25 @@
name: SQLite3 Migrations
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v3
- name: Install SQLite3
run: sudo apt-get install sqlite3
- name: Install Make
run: sudo apt-get install make
- name: Run Migrations
run: make migrate

16
.gitignore vendored
View file

@ -6,7 +6,19 @@
*.dylib
bin
database.txt
plantuml.jar
db.sqlite3
db.sqlite3-journal
diagram.puml
backend/*.png
backend/*.jpg
backend/*.svg
__pycache__
/go.work.sum
/package-lock.json
/backend/docs/swagger.json
# Test binary, built with `go test -c`
*.test
@ -30,6 +42,7 @@ dist/
.vscode/
.idea/
.DS_Store
.go.work.sum
# Ignore configuration files
.env
@ -48,5 +61,6 @@ dist/
*.7z
*.bak
backend/backups
config.toml

View file

@ -11,19 +11,29 @@ start-release: build-container-release remove-podman-containers
# Removes and stops any containers related to the project
[private]
remove-podman-containers:
podman container rm -f ttime
podman container rm -fi ttime
# Saves the release container to a tarball, pigz is just gzip but multithreaded
save-release: build-container-release
podman save --format=oci-archive ttime-server | pigz -9 > ttime-server.tar.gz
podman save --format=oci-archive ttime-server | pigz -9 > ttime-server_`date -I`_`git rev-parse --short HEAD`.tar.gz
# Loads the release container from a tarball
load-release file:
podman load --input {{file}}
# Tests every part of the project
testall:
cd frontend && npm install
cd frontend && npm test
cd frontend && npm run lint
cd frontend && npm run build
cd backend && make test
cd backend && make lint
cd backend && make itest
# Cleans up everything related to the project
clean: remove-podman-containers
podman image rm -f ttime-server
podman image rm -fi ttime-server
rm -rf frontend/dist
rm -rf frontend/node_modules
rm -f ttime-server.tar.gz
@ -33,4 +43,7 @@ clean: remove-podman-containers
# Cleans up everything related to podman, not just the project. Make sure you understand what this means.
[confirm]
podman-clean:
podman system reset --force
podman system reset --force
install-linter:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2

48
Makefile Normal file
View file

@ -0,0 +1,48 @@
# Builds a release container
build-container-release:
podman build -t ttime-server -f container/Containerfile .
# Builds a release container and runs it
start-release: build-container-release remove-podman-containers
podman run -d -e DATABASE_URL=sqlite:release.db -p 8080:8080 --name ttime ttime-server
@echo "Started production ttime-server on http://localhost:8080"
# Removes and stops any containers related to the project
remove-podman-containers:
podman container rm -fi ttime
# Tests every part of the project
testall:
cd frontend && npm install
cd frontend && npm test
cd frontend && npm run lint
cd frontend && npm run build
cd backend && make test
cd backend && make lint
cd backend && make itest
# Cleans up everything related to the project
clean: remove-podman-containers
podman image rm -fi ttime-server
rm -rf frontend/dist
rm -rf frontend/node_modules
rm -f ttime-server.tar.gz
cd backend && make clean
@echo "Cleaned up!"
.PHONY: itest
itest:
python testing.py
# Cleans up everything related to podman, not just the project. Make sure you understand what this means.
podman-clean:
podman system reset --force
# Installs the linter, which is not included in the ubuntu repo
install-linter:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2
# This installs just, a make alternative, which is slightly more ergonomic to use
install-just:
@echo "Installing just"
@curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin

View file

@ -23,12 +23,16 @@ Dependencies:
- [Podman](https://podman.io/) / [Docker](https://www.docker.com/)
- [Just](https://github.com/casey/just) (Optional)
### Fedora/Red Hat
If you're on [Fedora](https://fedoraproject.org/)/Red Hat derivatives, this is as simple as:
```bash
sudo dnf install -y make golang nodejs podman just
```
### Debian/Ubuntu
Any [Debian](https://www.debian.org/)/[Ubuntu](https://ubuntu.com/desktop)-based distro:
```bash
@ -36,19 +40,43 @@ sudo apt install -y make golang nodejs podman
sudo apt install -y just # For Ubuntu
```
### Arch Linux
[Arch Linux](https://archlinux.org/) & derivatives:
```bash
sudo pacman -S make go nodejs npm podman just
```
### MacOS
[MacOS](https://www.apple.com/macos/): (Requires [Homebrew](https://brew.sh/)) (Untested)
```bash
brew install make go nodejs npm podman just
```
[Windows](https://www.microsoft.com/en-us/windows):
### Windows
#### Vanilla Windows
The project should now build properly on Windows, given the dependencies:
- [Go](https://go.dev/)
- [Node & npm](https://nodejs.org/en/)
With chocolatey, you can install these dependencies with the following commands:
```powershell
choco install -y golang nodejs
```
Note that none of the convenience tools (Make, Podman, Just*) are available on Windows.
*Just is available, but the targets are written for a Unix-like environment.
#### Windows Subsystem for Linux (WSL)
Unfortunately, [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install) is required for the build process. Running any form of containerized workload on windows is currently unsupported. More info [here](https://podman.io/docs/installation#windows). From my understanding, WSL also requires virtualization extensions to be enabled in the BIOS, which is not always the case for all users.
It is possible to run the code on Windows, but this will be without the use of containers or any other build tools that are not available on Windows.
@ -59,15 +87,22 @@ My recommendation would be to make WSL your primary development environment if y
You should consult the [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/install), but for any recent version of windows, installation essentially boils down to running the following command in **PowerShell as an administrator**:
```powershell
wsl --install
wsl --install -d Ubuntu-22.04 # To get a somewhat recent version of Go
```
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.
After this, you can open a (wsl) terminal and run the commands:
If you're **still dead set** on using a vanilla Windows environment, you will need the following:
```bash
sudo apt update && sudo apt upgrade
sudo apt install -y make podman
- [Go](https://go.dev/)
- [Node & npm](https://nodejs.org/en/)
- [MariaDB](https://mariadb.org/) / [MySQL](https://www.mysql.com/) / [PostgreSQL](https://www.postgresql.org/) (This is undecided so far)
sudo add-apt-repository ppa:longsleep/golang-backports
sudo apt update
sudo apt install golang-go
With some grit and determination, you can get it to work. It's not recommended, but I (Imbus) will try to help you.
# 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.

View file

@ -8,29 +8,47 @@ GOGET = $(GOCMD) get
# SQLite database filename
DB_FILE = db.sqlite3
PROC_NAME = ttime_server
# Directory containing migration SQL scripts
MIGRATIONS_DIR = migrations
MIGRATIONS_DIR = internal/database/migrations
SAMPLE_DATA_DIR = internal/database/sample_data
# Build target
build:
$(GOBUILD) -o bin/server cmd/*.go
$(GOBUILD) -o bin/$(PROC_NAME) main.go
# Run target
run: build
./bin/server
./bin/$(PROC_NAME)
watch: build
watchexec -w . -r make run
watchexec -c -w . -r make run
# Clean target
clean:
$(GOCLEAN)
rm -rf bin
rm -f db.sqlite3
rm -f diagram*
rm -f plantuml.jar
rm -f erd.png
rm -f config.toml
rm -f database.txt
# Test target
test:
$(GOTEST) ./...
test: db.sqlite3
$(GOTEST) ./... -count=1
# Integration test target
.PHONY: itest
itest:
pgrep $(PROC_NAME) && echo "Server already running" && exit 1 || true
make build
./bin/$(PROC_NAME) >/dev/null 2>&1 &
sleep 1 # Adjust if needed
python ../testing/testing.py || pkill $(PROC_NAME)
pkill $(PROC_NAME)
# Get dependencies target
deps:
@ -43,11 +61,105 @@ update:
# Migration target
migrate:
@echo "If this ever fails, run make clean and try again"
@echo "Migrating database $(DB_FILE) using SQL scripts in $(MIGRATIONS_DIR)"
@for file in $(wildcard $(MIGRATIONS_DIR)/*.sql); do \
echo "Applying migration: $$file"; \
sqlite3 $(DB_FILE) < $$file; \
done
sampledata:
@echo "If this ever fails, run make clean and try again"
@echo "Migrating database $(DB_FILE) using SQL scripts in $(SAMPLE_DATA_DIR)"
@for file in $(wildcard $(SAMPLE_DATA_DIR)/*.sql); do \
echo "Applying migration: $$file"; \
sqlite3 $(DB_FILE) < $$file; \
done
# Target added primarily for CI/CD to ensure that the database is created before running tests
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
# Restore with:
# gzip -cd BACKUP_FILE.sql.gz | sqlite3 $(DB_FILE)
# Format
fmt:
$(GOCMD) fmt ./...
# Lint
lint:
golangci-lint run ./...
# Default target
default: build
default: build
# Generate swagger docs
.PHONY: docs
docs:
swag init -outputTypes go
api: ./docs/swagger.json
rm ../frontend/src/API/GenApi.ts
npx swagger-typescript-api \
--api-class-name GenApi \
--path ./docs/swagger.json \
--output ../frontend/src/API \
--name GenApi.ts \
./docs/swagger.json:
swag init -outputTypes json
.PHONY: docfmt
docfmt:
swag fmt
# Generate ERD
# Requires eralchemy2
.PHONY: erd
erd:
eralchemy2 -i sqlite:///db.sqlite3 -o erd.png
install-swag:
@echo "Installing swag"
@go get -u github.com/swaggo/swag/cmd/swag
# Convenience target to install golangci-lint
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"
@curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
.PHONY: types
types:
tygo generate
.PHONY: install-golds
install-golds:
go install go101.org/golds@latest
.PHONY: golds
golds:
golds -port 6060 -nouses -plainsrc -wdpkgs-listing=promoted ./...

View file

@ -1,61 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"ttime/internal/database"
_ "github.com/mattn/go-sqlite3"
)
// The button state as represented in memory
type ButtonState struct {
PressCount int `json:"pressCount"`
}
// This is what a handler with a receiver looks like
// Keep in mind that concurrent state access is not (usually) safe
// And will in practice be guarded by a mutex
func (b *ButtonState) pressHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
b.PressCount++
}
response, err := json.Marshal(b)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Println("Request received")
io.WriteString(w, string(response))
}
// This is what a handler looks like
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Println("Request received")
io.WriteString(w, "This is my website!\n")
}
func main() {
database.DbConnect()
b := &ButtonState{PressCount: 0}
// Mounting the handlers
fs := http.FileServer(http.Dir("static"))
http.Handle("/", fs)
http.HandleFunc("/hello", handler)
http.HandleFunc("/api/button", b.pressHandler)
// Start the server on port 8080
println("Currently listening on http://localhost:8080")
println("Visit http://localhost:8080/hello to see the hello handler in action")
println("Visit http://localhost:8080/button to see the button handler in action")
println("Press Ctrl+C to stop the server")
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}

404
backend/docs/docs.go Normal file
View file

@ -0,0 +1,404 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"license": {
"name": "AGPL",
"url": "https://www.gnu.org/licenses/agpl-3.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/login": {
"post": {
"description": "Logs in a user and returns a JWT token",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "Login",
"parameters": [
{
"description": "User credentials",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.NewUser"
}
}
],
"responses": {
"200": {
"description": "JWT token",
"schema": {
"$ref": "#/definitions/types.Token"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/loginrenew": {
"post": {
"security": [
{
"JWT": []
}
],
"description": "Renews the users token.",
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "LoginRenews",
"responses": {
"200": {
"description": "Successfully signed token for user",
"schema": {
"$ref": "#/definitions/types.Token"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/promote/{projectName}": {
"put": {
"security": [
{
"JWT": []
}
],
"description": "Promote a user to project manager",
"consumes": [
"text/plain"
],
"produces": [
"text/plain"
],
"tags": [
"Auth"
],
"summary": "Promote to project manager",
"parameters": [
{
"type": "string",
"description": "Project name",
"name": "projectName",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User name",
"name": "userName",
"in": "query",
"required": true
}
],
"responses": {
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/promoteToAdmin": {
"post": {
"security": [
{
"JWT": []
}
],
"description": "Promote chosen user to site admin",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"User"
],
"summary": "PromoteToAdmin",
"parameters": [
{
"description": "user info",
"name": "NewUser",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.NewUser"
}
}
],
"responses": {
"200": {
"description": "Successfully promoted user",
"schema": {
"$ref": "#/definitions/types.Token"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/register": {
"post": {
"description": "Register a new user",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"Auth"
],
"summary": "Register",
"parameters": [
{
"description": "User to register",
"name": "NewUser",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.NewUser"
}
}
],
"responses": {
"200": {
"description": "User added",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/userdelete/{username}": {
"delete": {
"security": [
{
"JWT": []
}
],
"description": "UserDelete deletes a user from the database",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"User"
],
"summary": "UserDelete",
"responses": {
"200": {
"description": "User deleted",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "You can only delete yourself",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/users/all": {
"get": {
"security": [
{
"JWT": []
}
],
"description": "lists all users",
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "ListsAllUsers",
"responses": {
"200": {
"description": "Successfully returned all users",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"types.NewUser": {
"type": "object",
"properties": {
"password": {
"type": "string",
"example": "password123"
},
"username": {
"type": "string",
"example": "username123"
}
}
},
"types.Token": {
"type": "object",
"properties": {
"token": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"JWT": {
"description": "Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with \"Bearer \".**",
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
},
"externalDocs": {
"description": "OpenAPI",
"url": "https://swagger.io/resources/open-api/"
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "0.0.1",
Host: "localhost:8080",
BasePath: "/api",
Schemes: []string{},
Title: "TTime API",
Description: "This is the API for TTime, a time tracking application.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View file

@ -3,8 +3,54 @@ module ttime
go 1.21.1
require (
github.com/BurntSushi/toml v1.3.2
github.com/gofiber/swagger v1.0.0
github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-sqlite3 v1.14.22
github.com/swaggo/swag v1.16.3
modernc.org/sqlite v1.29.5
)
require github.com/BurntSushi/toml v1.3.2 // indirect
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.49.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)
require (
github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
github.com/gofiber/contrib/jwt v1.0.8
github.com/golang-jwt/jwt/v5 v5.2.1
)
// These are all for fiber
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/gofiber/fiber/v2 v2.52.4
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.19.0 // indirect
)

View file

@ -1,11 +1,122 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY=
github.com/gofiber/contrib/jwt v1.0.8/go.mod h1:gWWBtBiLmKXRN7xy6a96QO0KGvPEyxdh8x496Ujtg84=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc=
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.15.0 h1:uwfCZOkKhaNNotgYW7kxkJwrkQC1HfGitt/7ousudJE=
modernc.org/ccgo/v4 v4.15.0/go.mod h1:XVITcYGiI+O97UNDLMsnZ9ZjJOhC+ACX+TfxpsWWyRc=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.49.1 h1:r4UaWllkYXRPA7Mq/KzmassZBvNJiH9egF4O/KV/gdE=
modernc.org/libc v1.49.1/go.mod h1:Hx2rWfza47GSzCluTU7Vf0Qx3z9rWCVORL6RNgq+Xog=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -22,7 +22,7 @@ type Config struct {
}
// WriteConfigToFile writes a Config to a file
func WriteConfigToFile(c *Config, filename string) error {
func (c *Config) WriteConfigToFile(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err

View file

@ -5,8 +5,12 @@ import (
"testing"
)
// TestNewConfig tests the creation of a new configuration object
func TestNewConfig(t *testing.T) {
// Arrange
c := NewConfig()
// Act & Assert
if c.Port != 8080 {
t.Errorf("Expected port to be 8080, got %d", c.Port)
}
@ -24,9 +28,15 @@ func TestNewConfig(t *testing.T) {
}
}
// TestWriteConfig tests the function to write the configuration to a file
func TestWriteConfig(t *testing.T) {
// Arrange
c := NewConfig()
err := WriteConfigToFile(c, "test.toml")
//Act
err := c.WriteConfigToFile("test.toml")
// Assert
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
@ -35,14 +45,23 @@ func TestWriteConfig(t *testing.T) {
_ = os.Remove("test.toml")
}
// TestReadConfig tests the function to read the configuration from a file
func TestReadConfig(t *testing.T) {
// Arrange
c := NewConfig()
err := WriteConfigToFile(c, "test.toml")
// Act
err := c.WriteConfigToFile("test.toml")
// Assert
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
// Act
c2, err := ReadConfigFromFile("test.toml")
// Assert
if err != nil {
t.Errorf("Expected no error, got %s", err)
}

View file

@ -1,32 +1,685 @@
package database
import (
"os"
"embed"
"encoding/json"
"errors"
"path/filepath"
"ttime/internal/types"
"github.com/gofiber/fiber/v2/log"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
_ "modernc.org/sqlite"
)
func DbConnect() *sqlx.DB {
// Check for the environment variable
dbpath := os.Getenv("SQLITE_DB_PATH")
// Interface for the database
type Database interface {
// Insert a new user into the database, password should be hashed before calling
AddUser(username string, password string) error
CheckUser(username string, password string) bool
RemoveUser(username string) error
RemoveUserFromProject(username string, projectname string) error
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
DeleteProject(name string, username string) error
GetProjectId(projectname string) (int, error)
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error
ChangeUserName(username string, newname 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)
GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error)
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error)
IsProjectManager(username string, projectname string) (bool, error)
ReportStatistics(username string, projectName string) (*types.Statistics, error)
GetProjectTimes(projectName string) (map[string]int, error)
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
RemoveProject(projectname string) error
GetUserName(id int) (string, error)
UnsignWeeklyReport(reportId int, projectManagerId int) error
DeleteReport(reportID int) error
ChangeProjectName(projectName string, newProjectName string) error
ChangeUserPassword(username string, password string) error
}
// Default to something reasonable
if dbpath == "" {
dbpath = "./db.sqlite3"
}
// 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.Tx
}
type UserProjectMember struct {
Username string `db:"username"`
UserRole string `db:"p_role"`
}
//go:embed migrations
var scripts embed.FS
//go:embed sample_data
var sampleData 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) VALUES (?, ?, (SELECT id FROM users WHERE username = ?))"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
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),?, ?, ?, ?, ?, ?, ?);`
const addUserToProject = `INSERT OR IGNORE INTO user_roles (user_id, project_id, p_role)
VALUES ((SELECT id FROM users WHERE username = ?),
(SELECT id FROM projects WHERE name = ?), ?)`
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)"
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id
WHERE u.username = ?`
const deleteProject = `DELETE FROM projects
WHERE id = ? AND owner_username = ?`
const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN users ON user_roles.user_id = users.id
JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
const removeUserFromProjectQuery = `DELETE FROM user_roles
WHERE user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)`
const reportStatistics = `SELECT SUM(development_time) AS total_development_time,
SUM(meeting_time) AS total_meeting_time,
SUM(admin_time) AS total_admin_time,
SUM(own_work_time) AS total_own_work_time,
SUM(study_time) AS total_study_time,
SUM(testing_time) AS total_testing_time
FROM weekly_reports
WHERE user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)
GROUP BY user_id, project_id`
// DbConnect connects to the database
func DbConnect(dbpath string) sqlx.DB {
// Open the database
// db, err := sqlx.Connect("sqlite3", ":memory:")
db, err := sqlx.Connect("sqlite3", dbpath)
db, err := sqlx.Connect("sqlite", dbpath)
if err != nil {
panic(err)
}
// Ping forces the connection to be established
err = db.Ping()
if err != nil {
panic(err)
}
return db
return *db
}
func (d *Db) ReportStatistics(username string, projectName string) (*types.Statistics, error) {
var result types.Statistics
err := d.Get(&result, reportStatistics, username, projectName)
if err != nil {
return nil, err
}
serialized, err := json.Marshal(result)
if err != nil {
return nil, err
}
log.Info(string(serialized))
return &result, nil
}
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
}
// 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.Get(&project, "SELECT * FROM projects WHERE id = ?", projectId)
if err != nil {
println("Error getting project: ", err)
}
return project, err
}
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)
return err
}
// AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error {
_, err := d.Exec(addUserToProject, username, projectname, role)
return err
}
func (d *Db) RemoveUserFromProject(username string, projectname string) error {
_, err := d.Exec(removeUserFromProjectQuery, username, projectname)
return err
}
// ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Execute the SQL query to change the user's role
_, err := d.Exec(changeUserRole, role, username, projectname)
return err
}
// ChangeUserName changes the username of a user.
func (d *Db) ChangeUserName(username string, newname string) error {
// Execute the SQL query to update the username
_, err := d.Exec("UPDATE users SET username = ? WHERE username = ?", newname, username)
return err
}
// 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
}
// 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
}
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 {
// Insert the project into the database
_, err := d.Exec(projectInsert, name, description, username)
if err != nil {
return err
}
// Add creator to project as project manager
_, err = d.Exec(addUserToProject, username, name, "project_manager")
if err != nil {
return err
}
return err
}
func (d *Db) DeleteProject(projectID string, username string) error {
_, err := d.Exec(deleteProject, projectID, 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,
signed_by
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
}
// SignWeeklyReport signs a weekly report by updating the signed_by field
// with the provided project manager's ID, but only if the project manager
// is in the same project as the report
func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
// Retrieve the project ID associated with the report
var reportProjectID int
err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId)
if err != nil {
return err
}
managerQuery := `SELECT project_id FROM user_roles
WHERE user_id = ?
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
AND p_role = 'project_manager'`
// Retrieve the project ID associated with the project manager
var managerProjectID int
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
if err != nil {
return err
}
// Check if the project manager is in the same project as the report
if reportProjectID != managerProjectID {
return errors.New("project manager doesn't have permission to sign the report")
}
// Update the signed_by field of the specified report
_, err = d.Exec("UPDATE weekly_reports SET signed_by = ? WHERE report_id = ?", projectManagerId, reportId)
return err
}
func (d *Db) UnsignWeeklyReport(reportId int, projectManagerId int) error {
// Retrieve the project ID associated with the report
var reportProjectID int
err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId)
if err != nil {
return err
}
managerQuery := `SELECT project_id FROM user_roles
WHERE user_id = ?
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
AND p_role = 'project_manager'`
// Retrieve the project ID associated with the project manager
var managerProjectID int
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
if err != nil {
return err
}
// Check if the project manager is in the same project as the report
if reportProjectID != managerProjectID {
return errors.New("project manager doesn't have permission to unsign the report")
}
// Update the signed_by field of the specified report
_, err = d.Exec("UPDATE weekly_reports SET signed_by = NULL WHERE report_id = ?;", reportId)
return err
}
func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) {
// Define the SQL query to fetch unsigned reports for a given user
query := `
SELECT
report_id,
user_id,
project_id,
week,
development_time,
meeting_time,
admin_time,
own_work_time,
study_time,
testing_time,
signed_by
FROM
weekly_reports
WHERE
signed_by IS NULL
AND project_id = (SELECT id FROM projects WHERE 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 reports []types.WeeklyReport
for rows.Next() {
var report types.WeeklyReport
if err := rows.StructScan(&report); err != nil {
return nil, err
}
reports = append(reports, report)
}
if err := rows.Err(); err != nil {
return nil, err
}
return reports, nil
}
// IsSiteAdmin checks if a given username is a site admin
func (d *Db) IsSiteAdmin(username string) (bool, error) {
// Define the SQL query to check if the user is a site admin
query := `
SELECT COUNT(*) FROM site_admin
JOIN users ON site_admin.admin_id = users.id
WHERE users.username = ?
`
// Execute the query
var count int
err := d.Get(&count, query, username)
if err != nil {
return false, err
}
// If count is greater than 0, the user is a site admin
return count > 0, nil
}
// Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory
func Migrate(db sqlx.DB) error {
// Read the embedded scripts directory
files, err := scripts.ReadDir("migrations")
if err != nil {
return err
}
if len(files) == 0 {
println("No migration files found")
return nil
}
tr := db.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
sqlBytes, err := scripts.ReadFile("migrations/" + file.Name())
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
if tr.Commit() != nil {
return err
}
return nil
}
// GetAllWeeklyReports retrieves weekly reports for a specific user and project.
func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) {
query := `
SELECT
wr.week,
wr.development_time,
wr.meeting_time,
wr.admin_time,
wr.own_work_time,
wr.study_time,
wr.testing_time,
wr.signed_by
FROM
weekly_reports wr
INNER JOIN
users u ON wr.user_id = u.id
INNER JOIN
projects p ON wr.project_id = p.id
WHERE
u.username = ? AND p.name = ?
`
var reports []types.WeeklyReportList
if err := d.Select(&reports, query, username, projectName); err != nil {
return nil, err
}
return reports, nil
}
// IsProjectManager checks if a given username is a project manager for the specified project
func (d *Db) IsProjectManager(username string, projectname string) (bool, error) {
var manager bool
err := d.Get(&manager, isProjectManagerQuery, username, projectname)
return manager, err
}
func (d *Db) UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error {
query := `
UPDATE weekly_reports
SET
development_time = ?,
meeting_time = ?,
admin_time = ?,
own_work_time = ?,
study_time = ?,
testing_time = ?
WHERE
user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)
AND week = ?
`
_, err := d.Exec(query, developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime, userName, projectName, week)
return err
}
// MigrateSampleData applies sample data to the database.
func MigrateSampleData(db sqlx.DB) error {
// Insert sample data
files, err := sampleData.ReadDir("sample_data")
if err != nil {
return err
}
if len(files) == 0 {
println("No sample data files found")
}
tr := db.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
sqlBytes, err := sampleData.ReadFile("sample_data/" + file.Name())
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
if tr.Commit() != nil {
return err
}
return nil
}
// GetProjectTimes retrieves a map with times per "Activity" for a given project
func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) {
query := `
SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time
FROM weekly_reports
JOIN projects ON weekly_reports.project_id = projects.id
WHERE projects.name = ?
`
rows, err := d.Query(query, projectName)
if err != nil {
return nil, err
}
defer rows.Close()
totalTime := make(map[string]int)
for rows.Next() {
var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int
if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil {
return nil, err
}
totalTime["development"] += developmentTime
totalTime["meeting"] += meetingTime
totalTime["admin"] += adminTime
totalTime["own_work"] += ownWorkTime
totalTime["study"] += studyTime
totalTime["testing"] += testingTime
}
if err := rows.Err(); err != nil {
return nil, err
}
return totalTime, nil
}
func (d *Db) RemoveProject(projectname string) error {
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
return err
}
func (d *Db) GetUserName(id int) (string, error) {
var username string
err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id)
return username, err
}
func (d *Db) DeleteReport(reportID int) error {
_, err := d.Exec("DELETE FROM weekly_reports WHERE report_id = ?", reportID)
return err
}
// ChangeProjectName is a handler that changes the name of a project
func (d *Db) ChangeProjectName(projectName string, newProjectName string) error {
_, err := d.Exec("UPDATE projects SET name = ? WHERE name = ?", newProjectName, projectName)
return err
}
func (d *Db) ChangeUserPassword(username string, password string) error {
_, err := d.Exec("UPDATE users SET password = ? WHERE username = ?", password, username)
return err
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
package database
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/jmoiron/sqlx"
)
// Simple middleware that provides a transaction as a local key "db"
func DbMiddleware(db *sqlx.DB) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
tx := db.MustBegin()
defer func() {
if err := tx.Commit(); err != nil {
if err = tx.Rollback(); err != nil {
log.Error("Failed to rollback transaction: ", err)
}
return
}
}()
var db_iface Database = &Db{tx}
c.Locals("db", &db_iface)
return c.Next()
}
}
// Helper function to get the database from the context, without fiddling with casts
func GetDb(c *fiber.Ctx) Database {
// Dereference a pointer to a local, casted to a pointer to a Database
return *c.Locals("db").(*Database)
}

View file

@ -0,0 +1,12 @@
-- Id is a surrogate key for in ternal use
-- userId is what is used for external id
-- username is what is used for login
-- password is the hashed password
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
-- Users are commonly searched by username and userId
CREATE INDEX IF NOT EXISTS users_username_index ON users (username);

View file

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
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_user_id_index ON projects (owner_user_id);

View file

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS weekly_reports (
report_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
project_id INTEGER NOT NULL,
week INTEGER NOT NULL,
development_time INTEGER,
meeting_time INTEGER,
admin_time INTEGER,
own_work_time INTEGER,
study_time INTEGER,
testing_time INTEGER,
signed_by INTEGER,
UNIQUE(user_id, project_id, week),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (signed_by) REFERENCES users(id)
);

View file

@ -0,0 +1,9 @@
-- This table represents the possible role a user can have in a project.
-- It has nothing to do with the site admin table.
CREATE TABLE IF NOT EXISTS project_role (
p_role TEXT PRIMARY KEY
);
-- 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 ('member');

View file

@ -0,0 +1,9 @@
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'
FOREIGN KEY (user_id) REFERENCES users (id)
FOREIGN KEY (project_id) REFERENCES projects (id)
FOREIGN KEY (p_role) REFERENCES project_role (p_role)
PRIMARY KEY (user_id, project_id)
);

View file

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS site_admin (
admin_id INTEGER PRIMARY KEY,
FOREIGN KEY (admin_id) REFERENCES users (id) ON DELETE CASCADE
)

View file

@ -0,0 +1,15 @@
# Database migrations
This directory contains all the database migrations for the backend.
[!WARNING]
Keep in mind that these migrations are **not yet stable**.
In practice, this means that the database schema may change at any time, and that the migrations may not be backwards compatible.
## Running migrations
In the root of the backend directory, run:
```bash
make migrate
```

View file

@ -0,0 +1,220 @@
INSERT OR IGNORE INTO users(username, password)
VALUES ("admin", "123"),
("user", "123"),
("user2", "123"),
("John", "123"),
("Emma", "123"),
("Michael", "123"),
("Liam", "123"),
("Oliver", "123"),
("Amelia", "123"),
("Benjamin", "123"),
("Mia", "123"),
("Elijah", "123"),
("Charlotte", "123"),
("Henry", "123"),
("Harper", "123"),
("Lucas", "123"),
("Emily", "123"),
("Alexander", "123"),
("Daniel", "123"),
("Ella", "123"),
("Matthew", "123"),
("Madison", "123"),
("Samuel", "123"),
("Avery", "123"),
("Sofia", "123"),
("David", "123"),
("Victoria", "123"),
("Jackson", "123"),
("Abigail", "123"),
("Gabriel", "123"),
("Luna", "123"),
("Wyatt", "123"),
("Chloe", "123"),
("Nora", "123"),
("Joshua", "123"),
("Hazel", "123"),
("Riley", "123"),
("Scarlett", "123"),
("Aria", "123"),
("Carter", "123"),
("Grace", "123"),
("Jayden", "123"),
("Hannah", "123"),
("Zoe", "123"),
("Luke", "123"),
("Sophia", "123"),
("Jack", "123"),
("Isabella", "123"),
("William", "123"),
("Mason", "123"),
("Evelyn", "123"),
("James", "123"),
("Cynthia", "123"),
("Abraham", "123"),
("Ava", "123"),
("Aiden", "123"),
("Natalie", "123"),
("Lily", "123"),
("Olivia", "123"),
("Alexander", "123"),
("Ethan", "123"),
("Mila", "123"),
("Evelyn", "123"),
("Logan", "123"),
("Riley", "123"),
("Grace", "123"),
("Arnold", "123"),
("Connor", "123"),
("Samantha", "123"),
("Emma", "123"),
("Sarah", "123"),
("Nathan", "123"),
("Layla", "123"),
("Ryan", "123"),
("Zoey", "123"),
("Megan", "123"),
("Christian", "123"),
("Eva", "123"),
("Isaac", "123"),
("Michaela", "123"),
("Caroline", "123"),
("Elijah", "123"),
("Elena", "123"),
("Julian", "123"),
("Sophie", "123"),
("Gabriella", "123"),
("Cole", "123"),
("Hannah", "123"),
("Lucy", "123"),
("Katherine", "123"),
("Benjamin", "123"),
("Ella", "123"),
("Evan", "123");
INSERT OR IGNORE INTO projects(name, description, owner_user_id)
VALUES ("projecttest1", "Description for projecttest1", 1),
("projecttest2", "Description for projecttest2", 1),
("projecttest3", "Description for projecttest3", 1),
("projecttest4", "Description for projecttest4", 1),
("projecttest5", "Description for projecttest5", 1),
("projecttest6", "Description for projecttest6", 1),
("projecttest7", "Description for projecttest7", 1),
("projecttest8", "Description for projecttest8", 1),
("projecttest9", "Description for projecttest9", 1),
("projecttest10", "Description for projecttest10", 1),
("projecttest11", "Description for projecttest11", 1),
("projecttest12", "Description for projecttest12", 1),
("projecttest13", "Description for projecttest13", 1),
("projecttest14", "Description for projecttest14", 1),
("projecttest15", "Description for projecttest15", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager"),
(1,2,"project_manager"),
(1,3,"project_manager"),
(1,4,"project_manager"),
(1,5,"project_manager"),
(1,6,"project_manager"),
(1,7,"project_manager"),
(1,8,"project_manager"),
(1,9,"project_manager"),
(1,10,"project_manager"),
(1,11,"project_manager"),
(1,12,"project_manager"),
(1,13,"project_manager"),
(1,14,"project_manager"),
(1,15,"project_manager"),
(2,1,"project_manager"),
(2,2,"member"),
(2,3,"member"),
(2,4,"member"),
(2,5,"member"),
(2,6,"member"),
(2,7,"member"),
(2,8,"member"),
(2,9,"member"),
(2,10,"member"),
(2,11,"member"),
(2,12,"member"),
(2,13,"member"),
(2,14,"member"),
(2,15,"member"),
(3,1,"member"),
(3,2,"member"),
(3,3,"member"),
(3,4,"member"),
(3,5,"member"),
(3,6,"member"),
(3,7,"member"),
(3,8,"member"),
(3,9,"member"),
(3,10,"member"),
(3,11,"member"),
(3,12,"member"),
(3,13,"member"),
(3,14,"member"),
(3,15,"member"),
(4,1,"member"),
(4,2,"member"),
(4,3,"member"),
(4,4,"member"),
(4,5,"member"),
(4,6,"member"),
(4,7,"member"),
(4,8,"member"),
(4,9,"member"),
(4,10,"member"),
(4,11,"member"),
(4,12,"member"),
(4,13,"member"),
(4,14,"member"),
(4,15,"member"),
(5,1,"member"),
(5,2,"member"),
(5,3,"member"),
(5,4,"member"),
(5,5,"member"),
(5,6,"member"),
(5,7,"member"),
(5,8,"member"),
(5,9,"member"),
(5,10,"member"),
(5,11,"member"),
(5,12,"member"),
(5,13,"member"),
(5,14,"member"),
(5,15,"member");
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (2, 1, 12, 100, 50, 30, 150, 80, 20, NULL),
(3, 1, 12, 200, 80, 20, 200, 100, 30, NULL),
(3, 1, 14, 150, 70, 40, 180, 90, 25, NULL),
(3, 2, 12, 120, 60, 35, 160, 85, 15, NULL),
(3, 3, 12, 180, 90, 25, 190, 110, 40, NULL),
(2, 1, 13, 130, 70, 40, 170, 95, 35, NULL),
(3, 1, 15, 140, 60, 50, 200, 120, 30, NULL),
(2, 2, 11, 110, 50, 45, 140, 70, 25, NULL),
(3, 3, 14, 170, 80, 30, 180, 100, 35, NULL),
(3, 3, 15, 200, 100, 20, 220, 130, 45, NULL),
(2, 4, 12, 120, 60, 40, 160, 80, 30, NULL),
(3, 5, 14, 150, 70, 30, 180, 90, 25, NULL),
(3, 5, 15, 180, 90, 20, 190, 110, 35, NULL),
(2, 6, 11, 100, 50, 35, 130, 60, 20, NULL),
(3, 7, 14, 170, 80, 25, 180, 100, 30, NULL),
(2, 8, 12, 130, 70, 30, 170, 90, 25, NULL),
(2, 8, 13, 150, 80, 20, 180, 110, 35, NULL),
(3, 9, 12, 140, 60, 40, 180, 100, 30, NULL),
(3, 10, 11, 120, 50, 45, 150, 70, 25, NULL),
(2, 11, 13, 110, 60, 35, 140, 80, 30, NULL),
(3, 12, 12, 160, 70, 30, 180, 100, 35, NULL),
(3, 12, 13, 180, 90, 25, 190, 110, 40, NULL),
(3, 12, 14, 200, 100, 20, 220, 130, 45, NULL),
(2, 13, 11, 100, 50, 45, 130, 60, 20, NULL),
(2, 13, 12, 120, 60, 40, 160, 80, 30, NULL),
(3, 14, 13, 140, 70, 30, 160, 90, 35, NULL),
(3, 15, 12, 150, 80, 25, 180, 100, 30, NULL),
(3, 15, 13, 170, 90, 20, 190, 110, 35, NULL);
INSERT OR IGNORE INTO site_admin VALUES (1);

View file

@ -0,0 +1,42 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
func AddUserToProjectHandler(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Add the user to the project with the specified role
err = db.GetDb(c).AddUserToProject(username, project, "member")
if err != nil {
log.Info("Error adding user to project:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", username, " added to project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,43 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// ChangeProjectName is a handler that changes the name of a project
func ChangeProjectName(c *fiber.Ctx) error {
//check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract the necessary parameters from the request
projectName := c.Params("projectName")
newProjectName := c.Query("newProjectName")
// Check if user is site admin
issiteadmin, err := db.GetDb(c).IsSiteAdmin(username)
if err != nil {
log.Warn("Error checking if siteadmin:", err)
return c.Status(500).SendString(err.Error())
} else if !issiteadmin {
log.Warn("User is not siteadmin")
return c.Status(401).SendString("User is not siteadmin")
}
// Perform the project name change
err = db.GetDb(c).ChangeProjectName(projectName, newProjectName)
if err != nil {
log.Warn("Error changing project name:", err)
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.Status(200).SendString("Project name changed successfully")
}

View file

@ -0,0 +1,30 @@
package projects
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
)
// CreateProject is a simple handler that creates a new project
func CreateProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
p := new(types.NewProject)
if err := c.BodyParser(p); err != nil {
return c.Status(400).SendString(err.Error())
}
// Get the username from the token and set it as the owner of the project
// This is ugly but
claims := user.Claims.(jwt.MapClaims)
owner := claims["name"].(string)
if err := db.GetDb(c).AddProject(p.Name, p.Description, owner); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.Status(200).SendString("Project added")
}

View file

@ -0,0 +1,19 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
)
func DeleteProject(c *fiber.Ctx) error {
projectID := c.Params("projectID")
username := c.Params("username")
if err := db.GetDb(c).DeleteProject(projectID, username); err != nil {
return c.Status(500).SendString((err.Error()))
}
return c.Status(200).SendString("Project deleted")
}

View file

@ -0,0 +1,38 @@
package projects
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
// GetProject retrieves a specific project by its ID
func GetProject(c *fiber.Ctx) error {
// Extract the project ID from the request parameters or body
projectID := c.Params("projectID")
if projectID == "" {
log.Info("No project ID provided")
return c.Status(400).SendString("No project ID provided")
}
log.Info("Getting project with ID: ", projectID)
// Parse the project ID into an integer
projectIDInt, err := strconv.Atoi(projectID)
if err != nil {
log.Info("Invalid project ID")
return c.Status(400).SendString("Invalid project ID")
}
// Get the project from the database by its ID
project, err := db.GetDb(c).GetProject(projectIDInt)
if err != nil {
log.Info("Error getting project:", err)
return c.Status(500).SendString(err.Error())
}
// Return the project as JSON
log.Info("Returning project: ", project.Name)
return c.JSON(project)
}

View file

@ -0,0 +1,63 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func GetProjectTimesHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Get project
projectName := c.Params("projectName")
if projectName == "" {
log.Info("No project name provided")
return c.Status(400).SendString("No project name provided")
}
// Get all users in the project and roles
userProjects, err := db.GetDb(c).GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users in project:", err)
return c.Status(500).SendString(err.Error())
}
// If the user is member
isMember := false
for _, userProject := range userProjects {
if userProject.Username == username {
isMember = true
break
}
}
// If the user is admin
if !isMember {
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is neither a project member nor a site admin:", username)
return c.Status(403).SendString("User is neither a project member nor a site admin")
}
}
// Get project times
projectTimes, err := db.GetDb(c).GetProjectTimes(projectName)
if err != nil {
log.Info("Error getting project times:", err)
return c.Status(500).SendString(err.Error())
}
// Return project times as JSON
log.Info("Returning project times for project:", projectName)
return c.JSON(projectTimes)
}

View file

@ -0,0 +1,26 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
// GetUserProjects returns all projects that the user is a member of
func GetUserProjects(c *fiber.Ctx) error {
username := c.Params("username")
if username == "" {
log.Info("No username provided")
return c.Status(400).SendString("No username provided")
}
// Then dip into the database to get the projects
projects, err := db.GetDb(c).GetProjectsForUser(username)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a json serialized list of projects
return c.JSON(projects)
}

View file

@ -0,0 +1,32 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
func IsProjectManagerHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary parameters from the request query string
projectName := c.Params("projectName")
log.Info("Checking if user ", username, " is a project manager for project ", projectName)
// Check if the user is a project manager for the specified project
isManager, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking project manager status:", err)
return c.Status(500).SendString(err.Error())
}
// Return the result as JSON
return c.JSON(fiber.Map{"isProjectManager": isManager})
}

View file

@ -0,0 +1,55 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func ListAllUsersProject(c *fiber.Ctx) error {
// Extract the project name from the request parameters or body
projectName := c.Params("projectName")
if projectName == "" {
log.Info("No project name provided")
return c.Status(400).SendString("No project name provided")
}
// Get the user token
userToken := c.Locals("user").(*jwt.Token)
claims := userToken.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Check if the user is a project manager for the specified project
isManager, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking project manager status:", err)
return c.Status(500).SendString(err.Error())
}
// If the user is not a project manager, check if the user is a site admin
if !isManager {
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is neither a project manager nor a site admin:", username)
return c.Status(403).SendString("User is neither a project manager nor a site admin")
}
}
// Get all users associated with the project from the database
users, err := db.GetDb(c).GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users for project:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning users for project: ", projectName)
// Return the list of users as JSON
return c.JSON(users)
}

View file

@ -0,0 +1,51 @@
package projects
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// ProjectRoleChange is a handler that changes a user's role within a project
func ProjectRoleChange(c *fiber.Ctx) error {
//check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract the necessary parameters from the request
data := new(types.RoleChange)
if err := c.BodyParser(data); err != nil {
log.Info("error parsing username, project or role")
return c.Status(400).SendString(err.Error())
}
// Check if user is trying to change its own role
if username == data.UserName {
log.Info("Can't change your own role")
return c.Status(403).SendString("Can't change your own role")
}
log.Info("Changing role for user: ", data.UserName, " in project: ", data.Projectname, " to: ", data.Role)
// Dubble diping and checcking if current user is
if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil {
log.Warn("Error checking if projectmanager:", err)
return c.Status(500).SendString(err.Error())
} else if !ismanager {
log.Warn("User is not projectmanager")
return c.Status(401).SendString("User is not projectmanager")
}
// Change the user's role within the project in the database
if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,55 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary Promote to project manager
// @Description Promote a user to project manager
// @Tags Auth
// @Security JWT
// @Accept plain
// @Produce plain
// @Param projectName path string true "Project name"
// @Param userName query string true "User name"
// @Failure 500 {string} string "Internal server error"
// @Failure 403 {string} string "Forbidden"
// @Router /promote/{projectName} [put]
//
// Login logs in a user and returns a JWT token
// Promote to project manager
func PromoteToPm(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
new_pm_name := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Add the user to the project with the specified role
err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager")
if err != nil {
log.Info("Error promoting user to project manager:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,35 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func RemoveProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Check if the user is a site admin
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is not a site admin:", username)
return c.Status(403).SendString("User is not a site admin")
}
projectName := c.Params("projectName")
if err := db.GetDb(c).RemoveProject(projectName); err != nil {
return c.Status(500).SendString((err.Error()))
}
return c.Status(200).SendString("Project deleted")
}

View file

@ -0,0 +1,40 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func RemoveUserFromProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Remove the user from the project
if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil {
log.Info("Error removing user from project:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", username, " removed from project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,22 @@
package reports
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
)
func DeleteReport(c *fiber.Ctx) error {
reportID := c.Params("reportID")
reportIDInt, err := strconv.Atoi(reportID)
if err != nil {
return c.Status(400).SendString("Invalid report ID")
}
if err := db.GetDb(c).DeleteReport(reportIDInt); err != nil {
return c.Status(500).SendString((err.Error()))
}
return c.Status(200).SendString("Weekly report deleted")
}

View file

@ -0,0 +1,56 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project
func GetAllWeeklyReports(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Params("projectName")
target_user := c.Query("targetUser") // The user whose reports are being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get all weekly reports for: ", target_user)
if projectName == "" {
log.Info("Missing project name")
return c.Status(400).SendString("Missing project name")
}
// If the user is not a project manager, they can only view their own reports
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !(pm || target_user == username) {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Retrieve weekly reports for the user in the project from the database
reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly report")
// Return the retrieved weekly report
return c.JSON(reports)
}

View file

@ -0,0 +1,45 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func GetUnsignedReports(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Params("projectName")
log.Info("Getting unsigned reports for")
if projectName == "" {
log.Info("Missing project name")
return c.Status(400).SendString("Missing project name")
}
// Get the project manager's ID
isProjectManager, err := db.GetDb(c).IsProjectManager(projectManagerUsername, projectName)
if err != nil {
log.Info("Failed to get project manager ID")
return c.Status(500).SendString("Failed to get project manager ID")
}
log.Info("User is Project Manager: ", isProjectManager)
// Call the database function to get the unsigned weekly reports
reports, err := db.GetDb(c).GetUnsignedWeeklyReports(projectName)
if err != nil {
log.Info("Error getting unsigned weekly reports:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning unsigned reports")
// Return the list of unsigned reports
return c.JSON(reports)
}

View file

@ -0,0 +1,65 @@
package reports
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// Handler for retrieving weekly report
func GetWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the request
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Query("projectName")
week := c.Query("week")
target_user := c.Query("targetUser") // The user whose report is being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get weekly report for: ", target_user)
if projectName == "" || week == "" {
log.Info("Missing project name or week number")
return c.Status(400).SendString("Missing project name or week number")
}
// Convert week to integer
weekInt, err := strconv.Atoi(week)
if err != nil {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
// If the token user is not an admin, check if the target user is the same as the token user
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !(pm || target_user == username) {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Call the database function to get the weekly report
report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt)
if err != nil {
log.Info("Error getting weekly report from db:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly report")
// Return the retrieved weekly report
return c.JSON(report)
}

View file

@ -0,0 +1,41 @@
package reports
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func SignReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
// Extract report ID from the path
reportId, err := strconv.Atoi(c.Params("reportId"))
if err != nil {
log.Info("Invalid report ID")
return c.Status(400).SendString("Invalid report ID")
}
// Get the project manager's ID
projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername)
if err != nil {
log.Info("Failed to get project manager ID for user: ", projectManagerUsername)
return c.Status(500).SendString("Failed to get project manager ID")
}
// Call the database function to sign the weekly report
err = db.GetDb(c).SignWeeklyReport(reportId, projectManagerID)
if err != nil {
log.Info("Error signing weekly report:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Project manager ID: ", projectManagerID, " signed report ID: ", reportId)
return c.Status(200).SendString("Weekly report signed successfully")
}

View file

@ -0,0 +1,56 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func GetStatistics(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name from query parameters
projectName := c.Query("projectName")
userNameParam := c.Query("userName")
log.Info(username, " trying to get statistics for project: ", projectName)
if projectName == "" {
log.Info("Missing project name")
return c.Status(400).SendString("Missing project name")
}
// Check if the user is a project manager
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
// Bail if the user is not a PM or checking its own statistics
if !pm && userNameParam != "" && userNameParam != username {
log.Info("Unauthorized access for user: ", username, "trying to access project: ", projectName, "statistics for user: ", userNameParam)
return c.Status(403).SendString("Unauthorized access")
}
if pm && userNameParam != "" {
username = userNameParam
}
// Retrieve statistics for the project from the database
statistics, err := db.GetDb(c).ReportStatistics(username, projectName)
if err != nil {
log.Error("Error getting statistics for project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning statistics")
// Return the retrieved statistics
return c.JSON(statistics)
}

View file

@ -0,0 +1,41 @@
package reports
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func SubmitWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
report := new(types.NewWeeklyReport)
if err := c.BodyParser(report); err != nil {
log.Info("Error parsing weekly report")
return c.Status(400).SendString(err.Error())
}
// Make sure all the fields of the report are valid
if report.Week < 1 || report.Week > 52 {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 {
log.Info("Invalid time report")
return c.Status(400).SendString("Invalid time report")
}
if err := db.GetDb(c).AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
log.Info("Error adding weekly report to db:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Weekly report added")
return c.Status(200).SendString("Time report added")
}

View file

@ -0,0 +1,41 @@
package reports
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func UnsignReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
// Extract report ID from the path
reportId, err := strconv.Atoi(c.Params("reportId"))
if err != nil {
log.Info("Invalid report ID")
return c.Status(400).SendString("Invalid report ID")
}
// Get the project manager's ID
projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername)
if err != nil {
log.Info("Failed to get project manager ID for user: ", projectManagerUsername)
return c.Status(500).SendString("Failed to get project manager ID")
}
// Call the database function to sign the weekly report
err = db.GetDb(c).UnsignWeeklyReport(reportId, projectManagerID)
if err != nil {
log.Info("Error Unsigning weekly report:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Project manager ID: ", projectManagerID, " unsigned report ID: ", reportId)
return c.Status(200).SendString("Weekly report unsigned successfully")
}

View file

@ -0,0 +1,44 @@
package reports
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func UpdateWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Parse the request body into an UpdateWeeklyReport struct
var updateReport types.UpdateWeeklyReport
if err := c.BodyParser(&updateReport); err != nil {
log.Info("Error parsing weekly report")
return c.Status(400).SendString(err.Error())
}
// Make sure all the fields of the report are valid
if updateReport.Week < 1 || updateReport.Week > 52 {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 {
log.Info("Invalid time report")
return c.Status(400).SendString("Invalid time report")
}
// Update the weekly report in the database
if err := db.GetDb(c).UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil {
log.Info("Error updating weekly report in db:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Weekly report updated")
return c.Status(200).SendString("Weekly report updated")
}

View file

@ -0,0 +1,44 @@
package users
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// ChangeUserName changes a user's username in the database
func ChangeUserName(c *fiber.Ctx) error {
// Check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
adminUsername := claims["name"].(string)
log.Info(adminUsername)
// Extract the necessary parameters from the request
data := new(types.StrNameChange)
if err := c.BodyParser(data); err != nil {
log.Info("Error parsing username")
return c.Status(400).SendString(err.Error())
}
// Check if the current user is an admin
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
if err != nil {
log.Warn("Error checking if admin:", err)
return c.Status(500).SendString(err.Error())
} else if !isAdmin {
log.Warn("Tried changing name when not admin")
return c.Status(401).SendString("You cannot change name unless you are an admin")
}
// Change the user's name in the database
if err := db.GetDb(c).ChangeUserName(data.PrevName, data.NewName); err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,42 @@
package users
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// ChangeUserPassword is a handler that changes the password of a user
func ChangeUserPassword(c *fiber.Ctx) error {
//Check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
admin := claims["name"].(string)
// Extract the necessary parameters from the request
username := c.Params("username")
newPassword := c.Query("newPassword")
// Check if user is site admin
issiteadmin, err := db.GetDb(c).IsSiteAdmin(admin)
if err != nil {
log.Warn("Error checking if siteadmin:", err)
return c.Status(500).SendString(err.Error())
} else if !issiteadmin {
log.Warn("User is not siteadmin")
return c.Status(401).SendString("User is not siteadmin")
}
// Perform the password change
err = db.GetDb(c).ChangeUserPassword(username, newPassword)
if err != nil {
log.Warn("Error changing password:", err)
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.Status(200).SendString("Password changed successfully")
}

View file

@ -0,0 +1,32 @@
package users
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
)
// Return the username of a user given their user id
func GetUserName(c *fiber.Ctx) error {
// Check the query params for userId
user_id_string := c.Query("userId")
if user_id_string == "" {
return c.Status(400).SendString("Missing user id")
}
// Convert to int
user_id, err := strconv.Atoi(user_id_string)
if err != nil {
return c.Status(400).SendString("Invalid user id")
}
// Get the username from the database
username, err := db.GetDb(c).GetUserName(user_id)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Send the nuclear launch codes to north korea
return c.JSON(fiber.Map{"username": username})
}

View file

@ -0,0 +1,22 @@
package users
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
func GetAllUsersProject(c *fiber.Ctx) error {
// Get all users from a project
projectName := c.Params("projectName")
users, err := db.GetDb(c).GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users from project:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("Returning all users")
// Return the list of users as JSON
return c.JSON(users)
}

View file

@ -0,0 +1,32 @@
package users
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
// @Summary ListsAllUsers
// @Description lists all users
// @Tags User
// @Produce json
// @Security JWT
// @Success 200 {array} string "Successfully returned all users"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /users/all [get]
//
// ListAllUsers returns a list of all users in the application database
func ListAllUsers(c *fiber.Ctx) error {
// Get all users from the database
users, err := db.GetDb(c).GetAllUsersApplication()
if err != nil {
log.Info("Error getting users from db:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("Returning all users")
// Return the list of users as JSON
return c.JSON(users)
}

View file

@ -0,0 +1,66 @@
package users
import (
"time"
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary Login
// @Description Logs in a user and returns a JWT token
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body types.NewUser true "User credentials"
// @Success 200 {object} types.Token "JWT token"
// @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /login [post]
//
// Login logs in a user and returns a JWT token
func Login(c *fiber.Ctx) error {
// The body type is identical to a NewUser
u := new(types.NewUser)
if err := c.BodyParser(u); err != nil {
log.Warn("Error parsing body")
return c.Status(400).SendString(err.Error())
}
log.Info("Username logging in:", u.Username)
if !db.GetDb(c).CheckUser(u.Username, u.Password) {
log.Info("User not found")
return c.SendStatus(fiber.StatusUnauthorized)
}
isAdmin, err := db.GetDb(c).IsSiteAdmin(u.Username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
// Create the Claims
claims := jwt.MapClaims{
"name": u.Username,
"admin": isAdmin,
"exp": time.Now().Add(time.Hour * 72).Unix(),
}
// Create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
log.Info("Token created for user:", u.Username)
// Generate encoded token and send it as response.
t, err := token.SignedString([]byte("secret"))
if err != nil {
log.Warn("Error signing token")
return c.SendStatus(fiber.StatusInternalServerError)
}
println("Successfully signed token for user:", u.Username)
return c.JSON(types.Token{Token: t})
}

View file

@ -0,0 +1,50 @@
package users
import (
"time"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary LoginRenews
// @Description Renews the users token.
// @Tags Auth
// @Produce json
// @Security JWT
// @Success 200 {object} types.Token "Successfully signed token for user"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /loginrenew [post]
//
// LoginRenew renews the users token
func LoginRenew(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
// Renewing the token means we trust whatever is already in the token
claims := user.Claims.(jwt.MapClaims)
// 72 hour expiration time
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
// Create token with old claims, but new expiration time
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"name": claims["name"],
"admin": claims["admin"],
"exp": claims["exp"],
})
// Sign it with top secret key
t, err := token.SignedString([]byte("secret"))
if err != nil {
log.Warn("Error signing token")
return c.SendStatus(fiber.StatusInternalServerError) // 500
}
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
return c.JSON(types.Token{Token: t})
}

View file

@ -0,0 +1,45 @@
package users
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
// @Summary PromoteToAdmin
// @Description Promote chosen user to site admin
// @Tags User
// @Accept json
// @Produce plain
// @Security JWT
// @Param NewUser body types.NewUser true "user info"
// @Success 200 {object} types.Token "Successfully promoted user"
// @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /promoteToAdmin [post]
//
// PromoteToAdmin promotes a user to a site admin
func PromoteToAdmin(c *fiber.Ctx) error {
// Extract the username from the request body
var newUser types.NewUser
if err := c.BodyParser(&newUser); err != nil {
return c.Status(400).SendString("Bad request")
}
username := newUser.Username
log.Info("Promoting user to admin:", username) // Debug print
// Promote the user to a site admin in the database
if err := db.GetDb(c).PromoteToAdmin(username); err != nil {
log.Info("Error promoting user to admin:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("User promoted to admin successfully:", username) // Debug print
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,38 @@
package users
import (
db "ttime/internal/database"
"ttime/internal/types"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
)
// @Summary Register
// @Description Register a new user
// @Tags Auth
// @Accept json
// @Produce plain
// @Param NewUser body types.NewUser true "User to register"
// @Success 200 {string} string "User added"
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /register [post]
//
// Register is a simple handler that registers a new user
func Register(c *fiber.Ctx) error {
u := new(types.NewUser)
if err := c.BodyParser(u); err != nil {
log.Warn("Error parsing body")
return c.Status(400).SendString(err.Error())
}
log.Info("Adding user:", u.Username)
if err := db.GetDb(c).AddUser(u.Username, u.Password); err != nil {
log.Warn("Error adding user:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("User added:", u.Username)
return c.Status(200).SendString("User added")
}

View file

@ -0,0 +1,43 @@
package users
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary UserDelete
// @Description UserDelete deletes a user from the database
// @Tags User
// @Accept json
// @Produce plain
// @Security JWT
// @Success 200 {string} string "User deleted"
// @Failure 403 {string} string "You can only delete yourself"
// @Failure 500 {string} string "Internal server error"
// @Failure 401 {string} string "Unauthorized"
// @Router /userdelete/{username} [delete]
//
// UserDelete deletes a user from the database
func UserDelete(c *fiber.Ctx) error {
// Read from path parameters
username := c.Params("username")
// Read username from Locals
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
if username == auth_username {
log.Info("User tried to delete itself")
return c.Status(403).SendString("You can't delete yourself")
}
if err := db.GetDb(c).RemoveUser(username); err != nil {
log.Warn("Error deleting user:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("User deleted:", username)
return c.Status(200).SendString("User deleted")
}

View file

@ -0,0 +1,97 @@
package types
// This is what should be submitted to the server, the username will be derived from the JWT token
type NewWeeklyReport struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName"`
// The week number
Week int `json:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime"`
// Total time spent on studying
StudyTime int `json:"studyTime"`
// Total time spent on testing
TestingTime int `json:"testingTime"`
}
type WeeklyReportList struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName" db:"project_name"`
// The week number
Week int `json:"week" db:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime" db:"development_time"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime" db:"meeting_time"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime" db:"admin_time"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime" db:"own_work_time"`
// Total time spent on studying
StudyTime int `json:"studyTime" db:"study_time"`
// Total time spent on testing
TestingTime int `json:"testingTime" db:"testing_time"`
// The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"`
}
type WeeklyReport struct {
// The ID of the report
ReportId int `json:"reportId" db:"report_id"`
// The user id of the user who submitted the report
UserId int `json:"userId" db:"user_id"`
// The name of the project, as it appears in the database
ProjectId int `json:"projectId" db:"project_id"`
// The week number
Week int `json:"week" db:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime" db:"development_time"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime" db:"meeting_time"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime" db:"admin_time"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime" db:"own_work_time"`
// Total time spent on studying
StudyTime int `json:"studyTime" db:"study_time"`
// Total time spent on testing
TestingTime int `json:"testingTime" db:"testing_time"`
// The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"`
}
type Statistics struct {
TotalDevelopmentTime int `json:"totalDevelopmentTime" db:"total_development_time"`
TotalMeetingTime int `json:"totalMeetingTime" db:"total_meeting_time"`
TotalAdminTime int `json:"totalAdminTime" db:"total_admin_time"`
TotalOwnWorkTime int `json:"totalOwnWorkTime" db:"total_own_work_time"`
TotalStudyTime int `json:"totalStudyTime" db:"total_study_time"`
TotalTestingTime int `json:"totalTestingTime" db:"total_testing_time"`
}
type UpdateWeeklyReport struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName"`
// The name of the user
UserName string `json:"userName"`
// The week number
Week int `json:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime"`
// Total time spent on studying
StudyTime int `json:"studyTime"`
// Total time spent on testing
TestingTime int `json:"testingTime"`
}

View file

@ -0,0 +1,29 @@
package types
// Project is a struct that holds the information about a project
type Project struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Owner string `json:"owner" db:"owner_user_id"`
}
// As it arrives from the client, Owner is derived from the JWT token
type NewProject struct {
Name string `json:"name"`
Description string `json:"description"`
}
// Used to change the role of a user in a project.
// If name is identical to the name contained in the token, the role can be changed.
// If the name is different, only a project manager can change the role.
type RoleChange struct {
UserName string `json:"username"`
Role string `json:"role" tstype:"'project_manager' | 'user'"`
Projectname string `json:"projectname"`
}
type NameChange struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}

View file

@ -0,0 +1,39 @@
package types
// User struct represents a user in the system
type User struct {
UserId string `json:"userId"`
Username string `json:"username"`
Password string `json:"password"`
}
// If the user needs to be served over the api, we dont want to send the password
// ToPublicUser converts a User to a PublicUser
func (u *User) ToPublicUser() (*PublicUser, error) {
return &PublicUser{
UserId: u.UserId,
Username: u.Username,
}, nil
}
// Should be used when registering, for example
type NewUser struct {
Username string `json:"username" example:"username123"`
Password string `json:"password" example:"password123"`
}
// PublicUser represents a user that is safe to send over the API (no password)
type PublicUser struct {
UserId string `json:"userId"`
Username string `json:"username"`
}
// wrapper type for token
type Token struct {
Token string `json:"token"`
}
type StrNameChange struct {
PrevName string `json:"prevName" db:"prevName"`
NewName string `json:"newName" db:"newName"`
}

View file

@ -0,0 +1,35 @@
package types
import "testing"
// NewUser returns a new User
func TestNewUser(t *testing.T) {
u := User{
UserId: "123",
Username: "test",
Password: "password",
}
if u.UserId != "123" {
t.Errorf("Expected user id to be 123, got %s", u.UserId)
}
if u.Username != "test" {
t.Errorf("Expected username to be test, got %s", u.Username)
}
if u.Password != "password" {
t.Errorf("Expected password to be password, got %s", u.Password)
}
}
func TestToPublicUser(t *testing.T) {
u := User{
UserId: "123",
Username: "test",
Password: "password",
}
_, err := u.ToPublicUser()
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
}

148
backend/main.go Normal file
View file

@ -0,0 +1,148 @@
package main
import (
"fmt"
"os"
_ "ttime/docs"
"ttime/internal/config"
"ttime/internal/database"
"ttime/internal/handlers/projects"
"ttime/internal/handlers/reports"
"ttime/internal/handlers/users"
"github.com/BurntSushi/toml"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/swagger"
jwtware "github.com/gofiber/contrib/jwt"
)
// @title TTime API
// @version 0.0.1
// @description This is the API for TTime, a time tracking application.
// @license.name AGPL
// @license.url https://www.gnu.org/licenses/agpl-3.0.html
// @securityDefinitions.apikey JWT
// @in header
// @name Authorization
// @description Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with "Bearer ".**
// @host localhost:8080
// @BasePath /api
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
/**
Main function for starting the server and initializing configurations.
Reads configuration from file, pretty prints it, connects to the database,
migrates it, and sets up routes for the server.
*/
func main() {
conf, err := config.ReadConfigFromFile("config.toml")
if err != nil {
conf = config.NewConfig()
_ = conf.WriteConfigToFile("config.toml")
}
// Pretty print the current config with toml
_ = toml.NewEncoder(os.Stdout).Encode(conf)
fmt.Printf("Starting server on http://localhost:%d\n", conf.Port)
fmt.Printf("For documentation, go to http://localhost:%d/swagger/index.html\n", conf.Port)
// Connect to the database
db := database.DbConnect(conf.DbPath)
// Migrate the database
if err = database.Migrate(db); err != nil {
fmt.Println("Error migrating database: ", err)
os.Exit(1)
}
// Migrate sample data, should not be used in production
if err = database.MigrateSampleData(db); err != nil {
fmt.Println("Error migrating sample data: ", err)
os.Exit(1)
}
// Create the server
server := fiber.New()
// We want some logs
server.Use(logger.New())
// Sets up db middleware, accessed as Local "db" key
server.Use(database.DbMiddleware(&db))
// Mounts the swagger documentation, this is available at /swagger/index.html
server.Get("/swagger/*", swagger.HandlerDefault)
// Mount our static files (Beware of the security implications of this!)
// This will likely be replaced by an embedded filesystem in the future
server.Static("/", "./static")
// Create a group for our API
api := server.Group("/api")
// Register our unprotected routes
api.Post("/register", users.Register)
api.Post("/login", users.Login)
// Every route from here on will require a valid
// JWT bearer token authentication in the header
server.Use(jwtware.New(jwtware.Config{
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
}))
// All user related routes
// userGroup := api.Group("/user") // Not currently in use
api.Get("/users/all", users.ListAllUsers)
api.Get("/project/getAllUsers", users.GetAllUsersProject)
api.Get("/username", users.GetUserName)
api.Post("/login", users.Login)
api.Post("/register", users.Register)
api.Post("/loginrenew", users.LoginRenew)
api.Post("/promoteToAdmin", users.PromoteToAdmin)
api.Put("/changeUserName", users.ChangeUserName)
api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches
api.Put("/changeUserPassword/:username", users.ChangeUserPassword)
// All project related routes
// projectGroup := api.Group("/project") // Not currently in use
api.Get("/getProjectTimes/:projectName", projects.GetProjectTimesHandler)
api.Get("/getUserProjects/:username", projects.GetUserProjects)
api.Get("/project/:projectId", projects.GetProject)
api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler)
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
api.Post("/project", projects.CreateProject)
api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
api.Put("/promoteToPm/:projectName", projects.PromoteToPm)
api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler)
api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject)
api.Delete("/removeProject/:projectName", projects.RemoveProject)
api.Delete("/project/:projectID", projects.DeleteProject)
api.Put("/changeProjectName/:projectName", projects.ChangeProjectName)
// All report related routes
// reportGroup := api.Group("/report") // Not currently in use
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
api.Get("/getStatistics", reports.GetStatistics)
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
api.Put("/signReport/:reportId", reports.SignReport)
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
api.Put("/unsignReport/:reportId", reports.UnsignReport)
api.Delete("/deleteReport/:reportId", reports.DeleteReport)
// Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port))
if err != nil {
fmt.Println("Error starting server: ", err)
}
}

View file

@ -1,50 +0,0 @@
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
);
CREATE TABLE IF NOT EXISTS time_reports (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id)
);
CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end
BEFORE INSERT ON time_reports
FOR EACH ROW
BEGIN
SELECT
CASE
WHEN NEW.start >= NEW.end THEN
RAISE (ABORT, 'start must be before end')
END;
END;
CREATE TABLE IF NOT EXISTS report_collection (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL,
date DATE NOT NULL,
signed_by INTEGER, -- NULL if not signed
FOREIGN KEY (signed_by) REFERENCES users (id)
);
CREATE TABLE IF NOT EXISTS user_roles (
user_id INTEGER NOT NULL,
project_id INTEGER NOT NULL,
role STRING NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
FOREIGN KEY (project_id) REFERENCES projects (id)
);

9
backend/tygo.yaml Normal file
View file

@ -0,0 +1,9 @@
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 */"

View file

@ -13,7 +13,6 @@ FROM docker.io/golang:alpine as go
RUN apk add gcompat
RUN apk add gcc
RUN apk add musl-dev
RUN apk add make
RUN apk add sqlite
WORKDIR /build
ADD backend/go.mod backend/go.sum ./
@ -24,10 +23,8 @@ RUN go mod download
# Add the source code
ADD backend .
RUN make migrate
# RUN go build -o server
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o ./server ./cmd/
RUN go build -o server
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o ./server ./main.go
# Strip the binary for a smaller image
RUN strip ./server
@ -37,6 +34,7 @@ FROM docker.io/alpine:latest as runner
RUN adduser -D nonroot
RUN addgroup nonroot nonroot
WORKDIR /app
RUN chown nonroot:nonroot /app
# Copy the frontend SPA build into public
COPY --from=client /build/dist static
@ -44,9 +42,6 @@ COPY --from=client /build/dist static
# Copy the server binary
COPY --from=go /build/server server
# Copy the database
COPY --from=go /build/db.sqlite3 db.sqlite3
# Expose port 8080
EXPOSE 8080
@ -54,4 +49,4 @@ EXPOSE 8080
USER nonroot:nonroot
# Run the server
CMD ["./server"]
CMD ["./server"]

View file

@ -9,7 +9,7 @@ module.exports = {
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'jest.config.cjs', 'goTypes.ts'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh', 'prettier'],
rules: {

2
frontend/.prettierignore Normal file
View file

@ -0,0 +1,2 @@
goTypes.ts
GenApi.ts

View file

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="src/assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>TTIME</title>
</head>
<body>
<div id="root"></div>

5
frontend/jest.config.cjs Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
}

File diff suppressed because it is too large Load diff

View file

@ -8,25 +8,38 @@
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"format": "prettier --config .prettierrc '**/*.ts' '**/*.tsx' '**/*.js' '**/*.json' --write"
"format": "prettier --config .prettierrc '**/*.ts' '**/*.tsx' '**/*.js' '**/*.json' --write",
"test": "jest"
},
"dependencies": {
"localforage": "^1.10.0",
"match-sorter": "^6.3.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2",
"sort-by": "^0.0.2"
},
"devDependencies": {
"@swc/core": "^1.4.2",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"jest": "^29.7.0",
"postcss": "^8.4.35",
"prettier": "3.2.5",
"react-test-renderer": "^18.2.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.2.2",
"vite": "^5.1.0"
}

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

1087
frontend/src/API/API.ts Normal file

File diff suppressed because it is too large Load diff

358
frontend/src/API/GenApi.ts Normal file
View file

@ -0,0 +1,358 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
export interface TypesNewUser {
/** @example "password123" */
password?: string;
/** @example "username123" */
username?: string;
}
export interface TypesToken {
token?: string;
}
export type QueryParamsType = Record<string | number, any>;
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
export interface FullRequestParams extends Omit<RequestInit, "body"> {
/** set parameter to `true` for call `securityWorker` for this request */
secure?: boolean;
/** request path */
path: string;
/** content type of request body */
type?: ContentType;
/** query params */
query?: QueryParamsType;
/** format of response (i.e. response.json() -> format: "json") */
format?: ResponseFormat;
/** request body */
body?: unknown;
/** base url */
baseUrl?: string;
/** request cancellation token */
cancelToken?: CancelToken;
}
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
export interface ApiConfig<SecurityDataType = unknown> {
baseUrl?: string;
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
customFetch?: typeof fetch;
}
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
data: D;
error: E;
}
type CancelToken = Symbol | string | number;
export enum ContentType {
Json = "application/json",
FormData = "multipart/form-data",
UrlEncoded = "application/x-www-form-urlencoded",
Text = "text/plain",
}
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "//localhost:8080/api";
private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private abortControllers = new Map<CancelToken, AbortController>();
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
private baseApiParams: RequestParams = {
credentials: "same-origin",
headers: {},
redirect: "follow",
referrerPolicy: "no-referrer",
};
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
Object.assign(this, apiConfig);
}
public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data;
};
protected encodeQueryParam(key: string, value: any) {
const encodedKey = encodeURIComponent(key);
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
}
protected addQueryParam(query: QueryParamsType, key: string) {
return this.encodeQueryParam(key, query[key]);
}
protected addArrayQueryParam(query: QueryParamsType, key: string) {
const value = query[key];
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
}
protected toQueryString(rawQuery?: QueryParamsType): string {
const query = rawQuery || {};
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
return keys
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
.join("&");
}
protected addQueryParams(rawQuery?: QueryParamsType): string {
const queryString = this.toQueryString(rawQuery);
return queryString ? `?${queryString}` : "";
}
private contentFormatters: Record<ContentType, (input: any) => any> = {
[ContentType.Json]: (input: any) =>
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
[ContentType.FormData]: (input: any) =>
Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
formData.append(
key,
property instanceof Blob
? property
: typeof property === "object" && property !== null
? JSON.stringify(property)
: `${property}`,
);
return formData;
}, new FormData()),
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
};
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
return {
...this.baseApiParams,
...params1,
...(params2 || {}),
headers: {
...(this.baseApiParams.headers || {}),
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
};
}
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
if (this.abortControllers.has(cancelToken)) {
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
return abortController.signal;
}
return void 0;
}
const abortController = new AbortController();
this.abortControllers.set(cancelToken, abortController);
return abortController.signal;
};
public abortRequest = (cancelToken: CancelToken) => {
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
abortController.abort();
this.abortControllers.delete(cancelToken);
}
};
public request = async <T = any, E = any>({
body,
secure,
path,
type,
query,
format,
baseUrl,
cancelToken,
...params
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
{};
const requestParams = this.mergeRequestParams(params, secureParams);
const queryString = query && this.toQueryString(query);
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
const responseFormat = format || requestParams.format;
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
...requestParams,
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
},
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
}).then(async (response) => {
const r = response as HttpResponse<T, E>;
r.data = null as unknown as T;
r.error = null as unknown as E;
const data = !responseFormat
? r
: await response[responseFormat]()
.then((data) => {
if (r.ok) {
r.data = data;
} else {
r.error = data;
}
return r;
})
.catch((e) => {
r.error = e;
return r;
});
if (cancelToken) {
this.abortControllers.delete(cancelToken);
}
if (!response.ok) throw data;
return data;
});
};
}
/**
* @title TTime API
* @version 0.0.1
* @license AGPL (https://www.gnu.org/licenses/agpl-3.0.html)
* @baseUrl //localhost:8080/api
* @externalDocs https://swagger.io/resources/open-api/
* @contact
*
* This is the API for TTime, a time tracking application.
*/
export class GenApi<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
login = {
/**
* @description Logs in a user and returns a JWT token
*
* @tags Auth
* @name LoginCreate
* @summary Login
* @request POST:/login
*/
loginCreate: (body: TypesNewUser, params: RequestParams = {}) =>
this.request<TypesToken, string>({
path: `/login`,
method: "POST",
body: body,
type: ContentType.Json,
format: "json",
...params,
}),
};
loginrenew = {
/**
* @description Renews the users token.
*
* @tags Auth
* @name LoginrenewCreate
* @summary LoginRenews
* @request POST:/loginrenew
* @secure
*/
loginrenewCreate: (params: RequestParams = {}) =>
this.request<TypesToken, string>({
path: `/loginrenew`,
method: "POST",
secure: true,
format: "json",
...params,
}),
};
promoteToAdmin = {
/**
* @description Promote chosen user to site admin
*
* @tags User
* @name PromoteToAdminCreate
* @summary PromoteToAdmin
* @request POST:/promoteToAdmin
* @secure
*/
promoteToAdminCreate: (NewUser: TypesNewUser, params: RequestParams = {}) =>
this.request<TypesToken, string>({
path: `/promoteToAdmin`,
method: "POST",
body: NewUser,
secure: true,
type: ContentType.Json,
...params,
}),
};
register = {
/**
* @description Register a new user
*
* @tags Auth
* @name RegisterCreate
* @summary Register
* @request POST:/register
*/
registerCreate: (NewUser: TypesNewUser, params: RequestParams = {}) =>
this.request<string, string>({
path: `/register`,
method: "POST",
body: NewUser,
type: ContentType.Json,
...params,
}),
};
userdelete = {
/**
* @description UserDelete deletes a user from the database
*
* @tags User
* @name UserdeleteDelete
* @summary UserDelete
* @request DELETE:/userdelete/{username}
* @secure
*/
userdeleteDelete: (username: string, params: RequestParams = {}) =>
this.request<string, string>({
path: `/userdelete/${username}`,
method: "DELETE",
secure: true,
type: ContentType.Json,
...params,
}),
};
users = {
/**
* @description lists all users
*
* @tags User
* @name GetUsers
* @summary ListsAllUsers
* @request GET:/users/all
* @secure
*/
getUsers: (params: RequestParams = {}) =>
this.request<string[], string>({
path: `/users/all`,
method: "GET",
secure: true,
format: "json",
...params,
}),
};
}

View file

@ -0,0 +1,3 @@
# API
This file contains the high-level API interface and implementation.

View file

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View file

@ -1,31 +0,0 @@
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import { CountButton } from "./Components/CountButton";
function App(): JSX.Element {
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<CountButton />
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App;

View file

@ -0,0 +1,35 @@
import { api } from "../API/API";
export interface AddMemberInfo {
userName: string;
projectName: string;
}
/**
* Tries to add a member to a project
* @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name
* @returns {Promise<void>}
*/
async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> {
if (props.memberToAdd.userName === "") {
alert("You must choose at least one user to add");
return;
}
try {
const response = await api.addUserToProject(
props.memberToAdd,
localStorage.getItem("accessToken") ?? "",
);
if (response.success) {
alert(`[${props.memberToAdd.userName}] added`);
} else {
alert(`[${props.memberToAdd.userName}] not added`);
console.error(response.message);
}
} catch (error) {
alert(`[${props.memberToAdd.userName}] not added`);
console.error("An error occurred during member add:", error);
}
}
export default AddMember;

View file

@ -0,0 +1,113 @@
import { useState } from "react";
import { api } from "../API/API";
import { NewProject } from "../Types/goTypes";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
import { useNavigate } from "react-router-dom";
import ProjectNameInput from "./Inputs/ProjectNameInput";
import DescriptionInput from "./Inputs/DescriptionInput";
import { alphanumeric } from "../Data/regex";
import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
/**
* Provides UI for adding a project to the system.
* @returns {JSX.Element} - Returns the component UI for adding a project
*/
function AddProject(): JSX.Element {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const navigate = useNavigate();
/**
* Tries to add a project to the system
*/
const handleCreateProject = async (): Promise<void> => {
if (
!alphanumeric.test(name) ||
name.length > projNameHighLimit ||
name.length < projNameLowLimit
) {
alert(
"Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)",
);
return;
}
if (description.length > projNameHighLimit) {
alert("Please provide valid description: \n-Max 100 characters");
return;
}
const project: NewProject = {
name: name.replace(/ /g, ""),
description: description.trim(),
};
try {
const response = await api.createProject(
project,
localStorage.getItem("accessToken") ?? "",
);
if (response.success) {
alert(`${project.name} added!`);
setDescription("");
setName("");
navigate("/admin");
} else {
alert("Project not added, name could be taken");
console.error(response.message);
}
} catch (error) {
alert("Project not added");
console.error(error);
}
};
return (
<div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => {
e.preventDefault();
void handleCreateProject();
}}
>
<img
src={Logo}
className="logo w-[7vw] self-center mb-10 mt-10"
alt="TTIME Logo"
/>
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Create a new project
</h3>
<ProjectNameInput
name={name}
onChange={(e) => {
e.preventDefault();
setName(e.target.value);
}}
/>
<div className="p-2"></div>
<DescriptionInput
desc={description}
onChange={(e) => {
e.preventDefault();
setDescription(e.target.value);
}}
placeholder={"Description (Optional)"}
/>
<div className="flex self-center mt-4 justify-between">
<Button
text="Create"
onClick={(): void => {
return;
}}
type="submit"
/>
</div>
</form>
<p className="text-center text-gray-500 text-xs"></p>
</div>
</div>
);
}
export default AddProject;

View file

@ -0,0 +1,125 @@
import { useEffect, useState } from "react";
import Button from "./Button";
import AddMember, { AddMemberInfo } from "./AddMember";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetAllUsers from "./GetAllUsers";
import InputField from "./InputField";
/**
* Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member
*/
function AddUserToProject(props: { projectName: string }): JSX.Element {
const [names, setNames] = useState<string[]>([]);
const [users, setUsers] = useState<string[]>([]);
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
const [search, setSearch] = useState("");
// Gets all users and project members for filtering
GetAllUsers({ setUsersProp: setUsers });
GetUsersInProject({
setUsersProp: setUsersProj,
projectName: props.projectName,
});
/*
* Filters the members from users so that users who are already
* members are not shown
*/
useEffect(() => {
setUsers((prevUsers) => {
const filteredUsers = prevUsers.filter(
(user) =>
!usersProj.some((projectUser) => projectUser.Username === user),
);
return filteredUsers;
});
}, [usersProj]);
// Attempts to add all of the selected users to the project
const handleAddClick = async (): Promise<void> => {
if (names.length === 0) {
alert("You have to choose at least one user to add");
return;
}
for (const name of names) {
const newMember: AddMemberInfo = {
userName: name,
projectName: props.projectName,
};
await AddMember({ memberToAdd: newMember });
}
setNames([]);
location.reload();
};
// Updates the names that have been selected
const handleUserClick = (user: string): void => {
setNames((prevNames): string[] => {
if (!prevNames.includes(user)) {
return [...prevNames, user];
}
return prevNames.filter((name) => name !== user);
});
};
return (
<div className="border-4 border-black bg-white flex flex-col items-center py-10 px-20 rounded-3xl content-center overflow-auto">
<h1 className="text-center font-bold text-[36px] pb-10">
{props.projectName}
</h1>
<p className="p-1 text-center font-bold text-[26px]">
Choose users to add:
</p>
<div>
<InputField
placeholder={"Search users"}
type={"Text"}
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
/>
<ul className="font-medium space-y-2 border-2 border-black mt-2 px-2 pb-2 rounded-2xl text-center overflow-auto h-[26vh] w-[34vh]">
<div></div>
{users
.filter((user) => {
return search.toLowerCase() === ""
? user
: user.toLowerCase().includes(search.toLowerCase());
})
.map((user) => (
<li
className={
names.includes(user)
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 transition-all hover:text-white hover:cursor-pointer"
}
key={user}
value={user}
onClick={() => {
handleUserClick(user);
}}
>
<span>{user}</span>
</li>
))}
</ul>
</div>
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
Number of users to be added: {names.length}
</p>
<div className="space-x-10 items-center">
<Button
text="Add"
onClick={(): void => {
void handleAddClick();
}}
type="button"
/>
</div>
</div>
);
}
export default AddUserToProject;

View file

@ -0,0 +1,71 @@
//Info: This component is used to display all the time reports for a project. It will display the week number,
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react";
import { WeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/**
* Renders a component that displays all the time reports for a specific project.
* @returns {JSX.Element} representing the component.
*/
function AllTimeReportsInProject(): JSX.Element {
const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
// Call getProjects when the component mounts
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setWeeklyReports(response.data ?? []);
} else {
console.error(response.message);
}
};
void getWeeklyReports();
}, [projectName]);
return (
<>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]">
{weeklyReports.map((newWeeklyReport, index) => (
<Link
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
key={index}
className="border-b-2 border-black w-full cursor-pointer hover:font-extrabold"
>
<div className="flex justify-between">
<h1>
<span className="font-bold">{"Week: "}</span>
{newWeeklyReport.week}
</h1>
<h1>
<span className="font-bold">{"Total Time: "}</span>
{newWeeklyReport.developmentTime +
newWeeklyReport.meetingTime +
newWeeklyReport.adminTime +
newWeeklyReport.ownWorkTime +
newWeeklyReport.studyTime +
newWeeklyReport.testingTime}{" "}
min
</h1>
<h1>
<span className="font-bold">{"Signed: "}</span>
{newWeeklyReport.signedBy ? "YES" : "NO"}
</h1>
</div>
</Link>
))}
</div>
</>
);
}
export default AllTimeReportsInProject;

View file

@ -0,0 +1,73 @@
//Info: This component is used to display all the time reports for a project. It will display the week number,
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react";
import { WeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/**
* Renders a component that displays all the time reports for a specific project.
* @returns {JSX.Element} representing the component.
*/
function AllTimeReportsInProject(): JSX.Element {
const { username } = useParams();
const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
token,
username ?? "",
);
console.log(response);
if (response.success) {
setWeeklyReports(response.data ?? []);
} else {
console.error(response.message);
}
};
void getWeeklyReports();
}, [projectName, username]);
return (
<>
<h1 className="text-[30px] font-bold">{username}&apos;s Time Reports</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]">
{weeklyReports.map((newWeeklyReport, index) => (
<Link
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
key={index}
className="border-b-2 border-black w-full hover:font-extrabold"
>
<div className="flex justify-between">
<h1>
<span className="font-bold">{"Week: "}</span>
{newWeeklyReport.week}
</h1>
<h1>
<span className="font-bold">{"Total Time: "}</span>
{newWeeklyReport.developmentTime +
newWeeklyReport.meetingTime +
newWeeklyReport.adminTime +
newWeeklyReport.ownWorkTime +
newWeeklyReport.studyTime +
newWeeklyReport.testingTime}{" "}
min
</h1>
<h1>
<span className="font-bold">{"Signed: "}</span>
{newWeeklyReport.signedBy ? "YES" : "NO"}
</h1>
</div>
</Link>
))}
</div>
</>
);
}
export default AllTimeReportsInProject;

View file

@ -0,0 +1,18 @@
import { Navigate } from "react-router-dom";
import React from "react";
interface AuthorizedRouteProps {
children: React.ReactNode;
isAuthorized: boolean;
}
export function AuthorizedRoute({
children,
isAuthorized,
}: AuthorizedRouteProps): JSX.Element {
if (!isAuthorized) {
return <Navigate to="/unauthorized" />;
}
return children as React.ReactElement;
}

View file

@ -0,0 +1,24 @@
//info: Back button component to navigate back to the previous page
import { useNavigate } from "react-router-dom";
/**
* Renders a back button component.
*
* @returns The JSX element representing the back button.
*/
function BackButton(): JSX.Element {
const navigate = useNavigate();
const goBack = (): void => {
navigate(-1);
};
return (
<button
onClick={goBack}
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
>
Back
</button>
);
}
export default BackButton;

View file

@ -0,0 +1,29 @@
//info: Background animation component to animate the background of loginpage
import { useEffect } from "react";
/**
* Renders a background animation component.
* This component pre-loads images and starts a background transition animation.
*/
const BackgroundAnimation = (): JSX.Element => {
useEffect(() => {
const images = [
"src/assets/1.jpg",
"src/assets/2.jpg",
"src/assets/3.jpg",
"src/assets/4.jpg",
];
// Pre-load images
for (const i of images) {
console.log(i);
}
// Start animation
document.body.style.animation = "backgroundTransition 30s infinite";
}, []);
return <></>;
};
export default BackgroundAnimation;

View file

@ -0,0 +1,30 @@
//info: Basic window component to display content and buttons of a page, inclduing header and footer
//content to insert is placed in the content prop, and buttons in the buttons prop
import Header from "./Header";
import Footer from "./Footer";
/**
* Renders a basic window component with a header, content, and footer.
*
* @param {Object} props - The component props.
* @param {React.ReactNode} props.content - The content to be rendered in the window.
* @param {React.ReactNode} props.buttons - The buttons to be rendered in the footer.
* @returns {JSX.Element} The rendered basic window component.
*/
function BasicWindow({
content,
buttons,
}: {
content: React.ReactNode;
buttons: React.ReactNode;
}): JSX.Element {
return (
<div className="font-sans flex flex-col h-screen bg-white border-2 border-black overflow-auto pt-[110px]">
<Header />
<div className="flex flex-col items-center flex-grow">{content}</div>
<Footer>{buttons}</Footer>
</div>
);
}
export default BasicWindow;

View file

@ -0,0 +1,30 @@
/**
* Button component to display a button with text and onClick function.
*
* @param {Object} props - The component props.
* @param {string} props.text - The text to display on the button.
* @param {Function} props.onClick - The function to run when the button is clicked.
* @param {"submit" | "button" | "reset"} props.type - The type of button.
* @returns {JSX.Element} The rendered Button component.
*/
function Button({
text,
onClick,
type,
}: {
text: string;
onClick: () => void;
type: "submit" | "button" | "reset";
}): JSX.Element {
return (
<button
onClick={onClick}
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
type={type}
>
{text}
</button>
);
}
export default Button;

View file

@ -0,0 +1,36 @@
import { APIResponse, api } from "../API/API";
/**
* Changes the name of a project
* @param {string} props.projectName - Current project name
* @param {string} props.newProjectName - New project name
* @returns {void} - Nothing
*/
export default function ChangeProjectName(props: {
projectName: string;
newProjectName: string;
}): void {
if (props.projectName === "" || props.projectName === props.newProjectName) {
alert("You have to give a new name\n\nName not changed");
return;
}
api
.changeProjectName(
props.projectName,
props.newProjectName,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Name changed successfully");
location.reload();
} else {
alert("Name not changed, name could be taken");
console.error(response.message);
}
})
.catch((error) => {
alert("Name not changed");
console.error("An error occurred during change:", error);
});
}

View file

@ -0,0 +1,37 @@
import { APIResponse, api } from "../API/API";
export interface ProjectRoleChange {
username: string;
role: "project_manager" | "member" | "";
projectname: string;
}
export default function ChangeRole(roleChangeInfo: ProjectRoleChange): void {
if (
roleChangeInfo.username === "" ||
roleChangeInfo.role === "" ||
roleChangeInfo.projectname === ""
) {
// FOR DEBUG
// console.log(roleChangeInfo.role + ": Role");
// console.log(roleChangeInfo.projectname + ": P-Name");
// console.log(roleChangeInfo.username + ": U-name");
alert("You have to select a role");
return;
}
api
.changeUserRole(roleChangeInfo, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Role changed successfully");
location.reload();
} else {
alert(response.message);
console.error(response.message);
}
})
.catch((error) => {
alert(error);
console.error("An error occurred during change:", error);
});
}

View file

@ -0,0 +1,76 @@
import { useState } from "react";
import Button from "./Button";
import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoleView(props: {
projectName: string;
username: string;
currentRole: string;
}): JSX.Element {
const [selectedRole, setSelectedRole] = useState<
"project_manager" | "member" | ""
>("");
const handleRoleChange = (
event: React.ChangeEvent<HTMLInputElement>,
): void => {
if (event.target.value === "member") {
setSelectedRole(event.target.value);
} else if (event.target.value === "project_manager") {
setSelectedRole(event.target.value);
}
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
console.log("Cur: " + props.currentRole + " " + "new: " + selectedRole);
event.preventDefault();
if (selectedRole === props.currentRole) {
alert(`Already ${props.currentRole}, nothing changed`);
return;
}
const roleChangeInfo: ProjectRoleChange = {
username: props.username,
projectname: props.projectName,
role: selectedRole,
};
ChangeRole(roleChangeInfo);
};
return (
<div className="overflow-auto">
<h1 className="font-bold text-[20px]">Select role:</h1>
<form onSubmit={handleSubmit}>
<div className="py-1 px-1 w-full self-start text-left font-medium overflow-auto border-2 border-black rounded-2xl">
<label className="hover:cursor-pointer hover:font-bold">
<input
type="radio"
value="project_manager"
checked={selectedRole === "project_manager"}
onChange={handleRoleChange}
className="m-2"
/>
Project manager
</label>
<br />
<label className="hover:cursor-pointer hover:font-bold">
<input
type="radio"
value="member"
checked={selectedRole === "member"}
onChange={handleRoleChange}
className="m-2 hover:cursor-pointer"
/>
Member
</label>
</div>
<Button
text="Change"
onClick={(): void => {
return;
}}
type="submit"
/>
</form>
</div>
);
}

View file

@ -0,0 +1,83 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import Button from "./Button";
export default function ChangeRoles(): JSX.Element {
const [selectedRole, setSelectedRole] = useState("");
const { username } = useParams();
const handleRoleChange = (
event: React.ChangeEvent<HTMLInputElement>,
): void => {
setSelectedRole(event.target.value);
};
// const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// event.preventDefault();
// const response = await api.changeRole(username, selectedRole, token);
// if (response.success) {
// console.log("Role changed successfully");
// } else {
// console.error("Failed to change role:", response.message);
// }
// };
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
Change roll for: {username}
</h1>
<form
className="text-[20px] font-bold border-4 border-black bg-white flex flex-col items-center justify-center min-h-[50vh] h-fit w-[30vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]"
onSubmit={undefined}
>
<div className="self-start">
<div>
<label>
<input
type="radio"
value="System Manager"
checked={selectedRole === "System Manager"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
System Manager
</label>
</div>
<div>
<label>
<input
type="radio"
value="Developer"
checked={selectedRole === "Developer"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
Developer
</label>
</div>
<div>
<label>
<input
type="radio"
value="Tester"
checked={selectedRole === "Tester"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
Tester
</label>
</div>
</div>
<Button
text="Save"
onClick={(): void => {
return;
}}
type="submit"
/>
</form>
</>
);
}

View file

@ -0,0 +1,36 @@
import { APIResponse, api } from "../API/API";
/**
* Changes the password of a user
* @param {string} props.username - The username of the user
* @param {string} props.newPassword - The new password
* @returns {void} - Nothing
*/
export default function ChangeUserPassword(props: {
username: string;
newPassword: string;
}): void {
if (props.username === localStorage.getItem("username")) {
alert("You cannot change admin password");
return;
}
api
.changeUserPassword(
props.username,
props.newPassword,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Password changed successfully");
location.reload();
} else {
alert("Password not changed");
console.error(response.message);
}
})
.catch((error) => {
alert("Password not changed");
console.error("An error occurred during change:", error);
});
}

View file

@ -0,0 +1,33 @@
import { APIResponse, api } from "../API/API";
import { StrNameChange } from "../Types/goTypes";
function ChangeUsername(props: { nameChange: StrNameChange }): void {
if (
props.nameChange.newName === "" ||
props.nameChange.newName === props.nameChange.prevName
) {
alert("You have to give a new name\n\nName not changed");
return;
}
if (props.nameChange.prevName === localStorage.getItem("username")) {
alert("You cannot change admin name");
return;
}
api
.changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Name changed successfully");
location.reload();
} else {
alert("Name not changed, name could be taken");
console.error(response.message);
}
})
.catch((error) => {
alert("Name not changed");
console.error("An error occurred during change:", error);
});
}
export default ChangeUsername;

View file

@ -1,38 +0,0 @@
import { useState, useEffect } from "react";
// Interface for the response from the server
// This should eventually reside in a dedicated file
interface CountResponse {
pressCount: number;
}
// Some constants for the button
const BUTTON_ENDPOINT = "/api/button";
// A simple button that counts how many times it's been pressed
export function CountButton(): JSX.Element {
const [count, setCount] = useState<number>(NaN);
// useEffect with a [] dependency array runs only once
useEffect(() => {
async function getCount(): Promise<void> {
const response = await fetch(BUTTON_ENDPOINT);
const data = (await response.json()) as CountResponse;
setCount(data.pressCount);
}
void getCount();
}, []);
// This is what runs on every button click
function press(): void {
async function pressPost(): Promise<void> {
const response = await fetch(BUTTON_ENDPOINT, { method: "POST" });
const data = (await response.json()) as CountResponse;
setCount(data.pressCount);
}
void pressPost();
}
// Return some JSX with the button and associated handler
return <button onClick={press}>count is {count}</button>;
}

View file

@ -0,0 +1,33 @@
import { api, APIResponse } from "../API/API";
/**
* Use to delete a project from the system
* @param {string} props.projectToDelete - The projectname of project to delete
* @returns {void} Nothing
* @example
* const exampleProjectName = "project";
* DeleteProject({ projectToDelete: exampleProjectName });
*/
function DeleteProject(props: { projectToDelete: string }): void {
api
.removeProject(
props.projectToDelete,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Project has been deleted!");
location.reload();
} else {
alert("Project has not been deleted");
console.error(response.message);
}
})
.catch((error) => {
alert("project has not been deleted");
console.error("An error occurred during deletion:", error);
});
}
export default DeleteProject;

View file

@ -0,0 +1,37 @@
import { User } from "../Types/goTypes";
import { api, APIResponse } from "../API/API";
/**
* Use to remove a user from the system
* @param {string} props.usernameToDelete - The username of user to remove
* @returns {boolean} True if removed, false if not
* @example
* const exampleUsername = "user";
* DeleteUser({ usernameToDelete: exampleUsername });
*/
function DeleteUser(props: { usernameToDelete: string }): boolean {
let removed = false;
api
.removeUser(
props.usernameToDelete,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<User>) => {
if (response.success) {
alert("User has been deleted!");
location.reload();
removed = true;
} else {
alert("User has not been deleted");
console.error(response.message);
}
})
.catch((error) => {
alert("User has not been deleted");
console.error("An error occurred during deletion:", error);
});
return removed;
}
export default DeleteUser;

View file

@ -0,0 +1,82 @@
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
import { WeeklyReport } from "../Types/goTypes";
function DisplayUserProject(): JSX.Element {
const { projectName } = useParams();
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
const [usernames, setUsernames] = useState<string[]>([]);
const token = localStorage.getItem("accessToken") ?? "";
useEffect(() => {
const getUnsignedReports = async (): Promise<void> => {
const response = await api.getUnsignedReportsInProject(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setUnsignedReports(response.data ?? []);
const usernamesPromises = (response.data ?? []).map((report) =>
api.getUsername(report.userId, token),
);
const usernamesResponses = await Promise.all(usernamesPromises);
const usernames = usernamesResponses.map(
(res) => (res.data as { username?: string }).username ?? "",
);
setUsernames(usernames);
} else {
console.error(response.message);
}
};
void getUnsignedReports();
}, [projectName, token]);
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
All Unsigned Reports In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{unsignedReports.map((unsignedReport: WeeklyReport, index: number) => (
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<span className="ml-6 mr-2 font-bold">Username:</span>
<h1>{usernames[index]}</h1>{" "}
<span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Total Time:</span>
<h1>
{unsignedReport.developmentTime +
unsignedReport.meetingTime +
unsignedReport.adminTime +
unsignedReport.ownWorkTime +
unsignedReport.studyTime +
unsignedReport.testingTime}
</h1>
<span className="ml-6 mr-2 font-bold">Signed:</span>
<h1>NO</h1>
</div>
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
>
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
View Report
</h1>
</Link>
</div>
</div>
</div>
</h1>
))}
</div>
</>
);
}
export default DisplayUserProject;

View file

@ -0,0 +1,58 @@
import { useState } from "react";
import { Project } from "../Types/goTypes";
import { useNavigate } from "react-router-dom";
import GetProjects from "./GetProjects";
import { api } from "../API/API";
/**
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
* @returns The JSX element representing the component.
*/
function DisplayUserProject(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]);
const navigate = useNavigate();
GetProjects({
setProjectsProp: setProjects,
username: localStorage.getItem("username") ?? "",
});
const handleProjectClick = async (projectName: string): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.checkIfProjectManager(projectName, token);
console.log(response.data);
if (response.success) {
if (
(response.data as unknown as { isProjectManager: boolean })
.isProjectManager
) {
navigate(`/PMProjectPage/${projectName}`);
} else {
navigate(`/project/${projectName}`);
}
} else {
// handle error
console.error(response.message);
}
};
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
{projects.map((project) => (
<div
onClick={() => void handleProjectClick(project.name)}
key={project.id}
>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
{project.name}
</h1>
</div>
))}
</div>
</>
);
}
export default DisplayUserProject;

View file

@ -0,0 +1,343 @@
import { useState, useEffect } from "react";
import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
/**
* Renders the component for editing a weekly report.
* @returns JSX.Element
*/
export default function GetWeeklyReport(): JSX.Element {
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? "";
const { projectName, fetchedWeek, signedOrUnsigned } = useParams<{
projectName: string;
fetchedWeek: string;
signedOrUnsigned: string;
}>();
const username = localStorage.getItem("userName") ?? "";
console.log(projectName, fetchedWeek, signedOrUnsigned);
useEffect(() => {
const fetchWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
projectName ?? "",
fetchedWeek ?? "",
token,
);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
userId: 0,
projectId: 0,
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchWeeklyReport();
}, [projectName, fetchedWeek, signedOrUnsigned, token]);
const handleUpdateWeeklyReport = async (): Promise<void> => {
const updateWeeklyReport: UpdateWeeklyReport = {
userName: username,
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.updateWeeklyReport(updateWeeklyReport, token);
};
const navigate = useNavigate();
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time Report</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form
onSubmit={(e) => {
if (week === 0) {
alert("Please enter a week number");
e.preventDefault();
return;
}
e.preventDefault();
void handleUpdateWeeklyReport();
alert("Changes submitted");
navigate(-1);
}}
>
<div className="flex flex-col items-center">
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
</div>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">
Activity
</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
onChange={(e) => {
if (e.target.value === "") {
setDevelopmentTime(0);
return;
} else {
setDevelopmentTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
onChange={(e) => {
if (e.target.value === "") {
setMeetingTime(0);
return;
} else {
setMeetingTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
onChange={(e) => {
if (e.target.value === "") {
setAdminTime(0);
return;
} else {
setAdminTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
onChange={(e) => {
if (e.target.value === "") {
setOwnWorkTime(0);
return;
} else {
setOwnWorkTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
onChange={(e) => {
if (e.target.value === "") {
setStudyTime(0);
return;
} else {
setStudyTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
onChange={(e) => {
if (e.target.value === "") {
setTestingTime(0);
return;
} else {
setTestingTime(parseInt(e.target.value));
}
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/>
</td>
</tr>
</tbody>
</table>
{signedOrUnsigned !== "signed" && (
<Button
text="Submit changes"
onClick={(): void => {
return;
}}
type="submit"
/>
)}
</div>
</form>
</div>
</>
);
}

Some files were not shown because too many files have changed in this diff Show more