Compare commits

...

51 commits

Author SHA1 Message Date
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
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
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
dDogge
0bd1fc5397 Merge remote-tracking branch 'refs/remotes/gh/dev' into dev 2024-03-14 14:31:01 +01:00
dDogge
2b491ed798 Clean up 2024-03-14 14:29:37 +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
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
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
Imbus
8db4ff2ec7 Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-13 16:17:02 +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
685a40e5d1 Containerfile fix for new path to main 2024-03-09 15:07:42 +01:00
24 changed files with 770 additions and 106 deletions

7
.gitignore vendored
View file

@ -6,8 +6,13 @@
*.dylib
bin
database.txt
plantuml.jar
db.sqlite3
*.png
diagram.puml
backend/*.png
backend/*.jpg
backend/*.svg
# Test binary, built with `go test -c`
*.test

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.
@ -62,12 +90,19 @@ You should consult the [WSL documentation](https://docs.microsoft.com/en-us/wind
wsl --install -d Ubuntu-22.04 # To get a somewhat recent version of Go
```
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

@ -27,6 +27,10 @@ clean:
$(GOCLEAN)
rm -rf bin
rm -f db.sqlite3
rm -f diagram*
rm -f plantuml.jar
rm -f erd.png
rm -f config.toml
# Test target
test: db.sqlite3
@ -54,6 +58,9 @@ migrate:
db.sqlite3:
make migrate
dbdump:
sqlite3 $(DB_FILE) .dump > database.txt
backup:
mkdir -p backups
sqlite3 $(DB_FILE) .dump | gzip -9 > ./backups/BACKUP_$(DB_FILE)_$(shell date +"%Y-%m-%d_%H:%M:%S").sql.gz
@ -95,6 +102,18 @@ 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"

View file

@ -4,34 +4,33 @@ 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/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.2.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-openapi/jsonpointer v0.20.3 // indirect
github.com/go-openapi/jsonreference v0.20.5 // indirect
github.com/go-openapi/spec v0.20.15 // indirect
github.com/go-openapi/swag v0.22.10 // indirect
github.com/gofiber/swagger v1.0.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/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // 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
github.com/swaggo/swag v1.16.3 // indirect
github.com/urfave/cli/v2 v2.27.1 // indirect
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)
require (

View file

@ -4,17 +4,12 @@ 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/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28=
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk=
github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM=
github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI=
@ -27,27 +22,27 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
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.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI=
github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo=
github.com/gofiber/fiber/v2 v2.52.2/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/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
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=
@ -63,47 +58,55 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
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/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
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.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
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=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
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.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
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

@ -5,27 +5,30 @@ import (
"os"
"path/filepath"
"time"
"ttime/internal/types"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
_ "modernc.org/sqlite"
)
// 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
RemoveUser(username string) error
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
Migrate(dirname string) error
// AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error
// ChangeUserRole(username string, projectname string, role string) error
// AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error
// ChangeUserRole(username string, projectname string, role string) error
GetProjectId(projectname string) (int, error)
AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error
AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error)
GetAllUsersApplication() ([]string, error)
GetProjectsForUser(username string) ([]types.Project, error)
GetAllProjects() ([]types.Project, error)
GetUserRole(username string, projectname string) (string, error)
}
// This struct is a wrapper type that holds the database connection
@ -34,20 +37,44 @@ type Db struct {
*sqlx.DB
}
type UserProjectMember struct {
Username string `db:"username"`
UserRole string `db:"p_role"`
}
//go:embed migrations
var scripts embed.FS
// TODO: Possibly break these out into separate files bundled with the embed package?
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP
const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)" // WIP
// const changeUserRole = ""
const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO time_reports (project_id, user_id, start, end)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup), ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `
SELECT
projects.id,
projects.name,
projects.description,
projects.owner_user_id
FROM
projects
JOIN
user_roles ON projects.id = user_roles.project_id
JOIN
users ON user_roles.user_id = users.id
WHERE
users.username = ?;`
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
// Open the database
db, err := sqlx.Connect("sqlite3", dbpath)
db, err := sqlx.Connect("sqlite", dbpath)
if err != nil {
panic(err)
}
@ -61,8 +88,20 @@ func DbConnect(dbpath string) Database {
return &Db{db}
}
func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP
_, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false)
func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) {
var projects []types.Project
err := d.Select(&projects, getProjectsForUser, username)
return projects, err
}
func (d *Db) GetAllProjects() ([]types.Project, error) {
var projects []types.Project
err := d.Select(&projects, "SELECT * FROM projects")
return projects, err
}
func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP
_, err := d.Exec(addTimeReport, userName, projectName, start, end)
return err
}
@ -79,13 +118,32 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
panic(err2)
}
_, err3 := d.Exec(addUserToProject, projectid, userid, role)
_, err3 := d.Exec(addUserToProject, userid, projectid, role)
return err3
}
// func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
// }
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
_, err3 := d.Exec(changeUserRole, role, userid, projectid)
return err3
}
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 {
@ -110,9 +168,9 @@ func (d *Db) GetUserId(username string) (int, error) {
return id, err
}
func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof
func (d *Db) GetProjectId(projectname string) (int, error) {
var id int
err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname)
err := d.Get(&id, "SELECT id FROM projects WHERE name = ?", projectname)
return id, err
}
@ -122,6 +180,69 @@ func (d *Db) AddProject(name string, description string, username string) error
return err
}
func (d *Db) GetAllUsersProject(projectname string) ([]UserProjectMember, error) {
// Define the SQL query to fetch users and their roles for a given project
query := `
SELECT u.username, ur.p_role
FROM users u
INNER JOIN user_roles ur ON u.id = ur.user_id
INNER JOIN projects p ON ur.project_id = p.id
WHERE p.name = ?
`
// Execute the query
rows, err := d.Queryx(query, projectname)
if err != nil {
return nil, err
}
defer rows.Close()
// Iterate over the rows and populate the result slice
var users []UserProjectMember
for rows.Next() {
var user UserProjectMember
if err := rows.StructScan(&user); err != nil {
return nil, err
}
users = append(users, user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
// GetAllUsersApplication retrieves all usernames from the database
func (d *Db) GetAllUsersApplication() ([]string, error) {
// Define the SQL query to fetch all usernames
query := `
SELECT username FROM users
`
// Execute the query
rows, err := d.Queryx(query)
if err != nil {
return nil, err
}
defer rows.Close()
// Iterate over the rows and populate the result slice
var usernames []string
for rows.Next() {
var username string
if err := rows.Scan(&username); err != nil {
return nil, err
}
usernames = append(usernames, username)
}
if err := rows.Err(); err != nil {
return nil, err
}
return usernames, nil
}
// Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory
func (d *Db) Migrate(dirname string) error {

View file

@ -2,6 +2,7 @@ package database
import (
"testing"
"time"
)
// Tests are not guaranteed to be sequential
@ -92,14 +93,253 @@ func TestPromoteToAdmin(t *testing.T) {
}
}
// func TestAddTimeReport(t *testing.T) {
func TestAddTimeReport(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// }
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// func TestAddUserToProject(t *testing.T) {
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
// }
var now = time.Now()
var then = now.Add(time.Hour)
// func TestChangeUserRole(t *testing.T) {
err = db.AddTimeReport("testproject", "testuser", now, then)
if err != nil {
t.Error("AddTimeReport failed:", err)
}
}
// }
func TestAddUserToProject(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
var now = time.Now()
var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", now, then)
if err != nil {
t.Error("AddTimeReport failed:", err)
}
err = db.AddUserToProject("testuser", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
}
func TestChangeUserRole(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
err = db.AddUserToProject("testuser", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
role, err := db.GetUserRole("testuser", "testproject")
if err != nil {
t.Error("GetUserRole failed:", err)
}
if role != "user" {
t.Error("GetUserRole failed: expected user, got", role)
}
err = db.ChangeUserRole("testuser", "testproject", "admin")
if err != nil {
t.Error("ChangeUserRole failed:", err)
}
role, err = db.GetUserRole("testuser", "testproject")
if err != nil {
t.Error("GetUserRole failed:", err)
}
if role != "admin" {
t.Error("GetUserRole failed: expected admin, got", role)
}
}
func TestGetAllUsersProject(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser1", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddUser("testuser2", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser1")
if err != nil {
t.Error("AddProject failed:", err)
}
err = db.AddUserToProject("testuser1", "testproject", "project_manager")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
err = db.AddUserToProject("testuser2", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
users, err := db.GetAllUsersProject("testproject")
if err != nil {
t.Error("GetAllUsersProject failed:", err)
}
// Check if both users are returned with their roles
if len(users) != 2 {
t.Errorf("Expected 2 users, got %d", len(users))
}
// Check if testuser1 has project manager role
foundProjectManager := false
for _, user := range users {
if user.Username == "testuser1" && user.UserRole == "project_manager" {
foundProjectManager = true
break
}
}
if !foundProjectManager {
t.Error("Project Manager user not found")
}
// Check if testuser2 has user role
foundUser := false
for _, user := range users {
if user.Username == "testuser2" && user.UserRole == "user" {
foundUser = true
break
}
}
if !foundUser {
t.Error("User user not found")
}
}
func TestGetAllUsersApplication(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser1", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddUser("testuser2", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
users, err := db.GetAllUsersApplication()
if err != nil {
t.Error("GetAllUsersApplication failed:", err)
}
// Check if both users are returned
if len(users) != 2 {
t.Errorf("Expected 2 users, got %d", len(users))
}
// Check if the test users are included in the list
foundTestUser1 := false
foundTestUser2 := false
for _, user := range users {
if user == "testuser1" {
foundTestUser1 = true
}
if user == "testuser2" {
foundTestUser2 = true
}
}
if !foundTestUser1 {
t.Error("testuser1 not found")
}
if !foundTestUser2 {
t.Error("testuser2 not found")
}
}
func TestGetProjectsForUser(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
err = db.AddUserToProject("testuser", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
projects1, err := db.GetAllProjects()
if err != nil {
t.Error("GetAllProjects failed:", err)
}
if len(projects1) != 1 {
t.Error("GetAllProjects failed: expected 1, got", len(projects1))
}
projects, err := db.GetProjectsForUser("testuser")
if err != nil {
t.Error("GetProjectsForUser failed:", err)
}
if len(projects) != 1 {
t.Error("GetProjectsForUser failed: expected 1, got", len(projects))
}
}

View file

@ -1,11 +1,9 @@
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY,
projectId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT NOT NULL,
owner_user_id INTEGER NOT NULL,
FOREIGN KEY (owner_user_id) REFERENCES users (id)
);
CREATE INDEX IF NOT EXISTS projects_projectId_index ON projects (projectId);
CREATE INDEX IF NOT EXISTS projects_user_id_index ON projects (owner_user_id);

View file

@ -1,10 +1,11 @@
CREATE TABLE IF NOT EXISTS time_reports (
id INTEGER PRIMARY KEY,
reportId TEXT DEFAULT (HEX(RANDOMBLOB(6))) NOT NULL UNIQUE,
project_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end

View file

@ -5,5 +5,5 @@ CREATE TABLE IF NOT EXISTS project_role (
);
-- Insert the possible roles a user can have in a project.
INSERT OR IGNORE INTO project_role (p_role) VALUES ('admin');
INSERT OR IGNORE INTO project_role (p_role) VALUES ('project_manager');
INSERT OR IGNORE INTO project_role (p_role) VALUES ('member');

View file

@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS user_roles (
user_id INTEGER NOT NULL,
project_id INTEGER NOT NULL,
p_role TEXT NOT NULL, -- 'admin' or 'member'
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)

View file

@ -11,12 +11,12 @@ import (
// The actual interface that we will use
type GlobalState interface {
Register(c *fiber.Ctx) error // To register a new user
UserDelete(c *fiber.Ctx) error // To delete a user
Login(c *fiber.Ctx) error // To get the token
LoginRenew(c *fiber.Ctx) error // To renew the token
CreateProject(c *fiber.Ctx) error // To create a new project
// GetProjects(c *fiber.Ctx) error // To get all projects
Register(c *fiber.Ctx) error // To register a new user
UserDelete(c *fiber.Ctx) error // To delete a user
Login(c *fiber.Ctx) error // To get the token
LoginRenew(c *fiber.Ctx) error // To renew the token
CreateProject(c *fiber.Ctx) error // To create a new project
GetUserProjects(c *fiber.Ctx) error // To get all projects
// GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project
// DeleteProject(c *fiber.Ctx) error // To delete a project
@ -33,6 +33,9 @@ type GlobalState interface {
// SignCollection(c *fiber.Ctx) error // To sign a collection
GetButtonCount(c *fiber.Ctx) error // For demonstration purposes
IncrementButtonCount(c *fiber.Ctx) error // For demonstration purposes
ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database
ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project
ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project
}
// "Constructor"
@ -163,3 +166,62 @@ func (gs *GState) CreateProject(c *fiber.Ctx) error {
return c.Status(200).SendString("Project added")
}
// GetUserProjects returns all projects that the user is a member of
func (gs *GState) GetUserProjects(c *fiber.Ctx) error {
// First we get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Then dip into the database to get the projects
projects, err := gs.Db.GetProjectsForUser(username)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a json serialized list of projects
return c.JSON(projects)
}
// ListAllUsers is a handler that returns a list of all users in the application database
func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
// Get all users from the database
users, err := gs.Db.GetAllUsersApplication()
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Return the list of users as JSON
return c.JSON(users)
}
func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error {
// Extract the project name from the request parameters or body
projectName := c.Params("projectName")
// Get all users associated with the project from the database
users, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Return the list of users as JSON
return c.JSON(users)
}
// ProjectRoleChange is a handler that changes a user's role within a project
func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
// Extract the necessary parameters from the request
username := c.Params("username")
projectName := c.Params("projectName")
role := c.Params("role")
// Change the user's role within the project in the database
if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -1,16 +1,11 @@
package types
import (
"time"
)
// 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"`
Created time.Time `json:"created" db:"created"`
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

View file

@ -11,7 +11,6 @@ import (
"github.com/BurntSushi/toml"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "github.com/mattn/go-sqlite3"
jwtware "github.com/gofiber/contrib/jwt"
)
@ -69,8 +68,10 @@ func main() {
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
}))
server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject)
// Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port))

View file

@ -27,7 +27,7 @@ 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 CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o ./server ./main.go
# Strip the binary for a smaller image
RUN strip ./server
@ -54,4 +54,4 @@ EXPOSE 8080
USER nonroot:nonroot
# Run the server
CMD ["./server"]
CMD ["./server"]

57
frontend/src/API/API.ts Normal file
View file

@ -0,0 +1,57 @@
import { NewProject, Project } from "../Types/Project";
import { NewUser, User } from "../Types/Users";
// Defines all the methods that an instance of the API must implement
interface API {
/** Register a new user */
registerUser(user: NewUser): Promise<User>;
/** Remove a user */
removeUser(username: string): Promise<User>;
/** Create a project */
createProject(project: NewProject): Promise<Project>;
/** Renew the token */
renewToken(token: string): Promise<string>;
}
// Export an instance of the API
export const api: API = {
async registerUser(user: NewUser): Promise<User> {
return fetch("/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
}).then((res) => res.json() as Promise<User>);
},
async removeUser(username: string): Promise<User> {
return fetch("/api/userdelete", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(username),
}).then((res) => res.json() as Promise<User>);
},
async createProject(project: NewProject): Promise<Project> {
return fetch("/api/project", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(project),
}).then((res) => res.json() as Promise<Project>);
},
async renewToken(token: string): Promise<string> {
return fetch("/api/loginrenew", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
}).then((res) => res.json() as Promise<string>);
},
};

View file

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

View file

@ -0,0 +1,74 @@
import { useState } from "react";
import { NewUser } from "../Types/Users";
import { api } from "../API/API";
export default function Register(): JSX.Element {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleRegister = async (): Promise<void> => {
const newUser: NewUser = { userName: username, password };
await api.registerUser(newUser); // TODO: Handle errors
};
return (
<div>
<div className="w-full max-w-xs">
<form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
onSubmit={(e) => {
e.preventDefault();
void handleRegister();
}}
>
<h3 className="pb-2">Register new user</h3>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="username"
>
Username
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="username"
type="text"
placeholder="Username"
value={username}
onChange={(e) => {
setUsername(e.target.value);
}}
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="password"
>
Password
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
id="password"
type="password"
placeholder="Choose your password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</div>
<div className="flex items-center justify-between">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Register
</button>
</div>
</form>
<p className="text-center text-gray-500 text-xs"></p>
</div>
</div>
);
}

View file

@ -69,6 +69,14 @@ function LoginPage(): JSX.Element {
}}
/>
</Link>
<Link to="/register">
<Button
text="Register new user"
onClick={(): void => {
return;
}}
/>
</Link>
</div>
</div>
</>

View file

@ -0,0 +1,13 @@
export interface Project {
id: number;
name: string;
description: string;
owner: string;
created: string; // This is a date
}
export interface NewProject {
name: string;
description: string;
owner: string;
}

View file

@ -0,0 +1,4 @@
# Types
This directory contains TypeScript type definitions for the frontend.
This is primarily used by, but not limited to, the API responses and requests.

View file

@ -0,0 +1,11 @@
// This is how the API responds
export interface User {
id: number;
userName: string;
}
// Used to create a new user
export interface NewUser {
userName: string;
password: string;
}

View file

@ -5,6 +5,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import LoginPage from "./Pages/LoginPage.tsx";
import YourProjectsPage from "./Pages/YourProjectsPage.tsx";
import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx";
import Register from "./Components/Register.tsx";
import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
@ -52,6 +53,14 @@ const router = createBrowserRouter([
path: "/project",
element: <UserProjectPage />,
},
{
path: "/register",
element: <Register />,
},
{
path: "/admin-menu",
element: <AdminMenuPage />,
},
{
path: "/project-page",
element: <UserViewTimeReportsPage />,

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "TTime",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}