Compare commits
43 commits
3e000358a7
...
4d23f8acea
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4d23f8acea | ||
![]() |
8c8e57cb50 | ||
![]() |
25fdf3bb9b | ||
![]() |
c4632104a8 | ||
![]() |
736cebe036 | ||
![]() |
9b67a580da | ||
![]() |
ad4d439887 | ||
![]() |
d06122864e | ||
![]() |
f8277617a9 | ||
![]() |
6acfdd36b2 | ||
![]() |
0b23c5f8f1 | ||
![]() |
ac6638b344 | ||
![]() |
394b283712 | ||
![]() |
02c9c502b8 | ||
![]() |
a7ebfe7110 | ||
![]() |
efc95e7fea | ||
![]() |
b52f13ee8b | ||
![]() |
d4a2a00f69 | ||
![]() |
ae0b0895b2 | ||
![]() |
13c12b424a | ||
![]() |
f75cb7e9fc | ||
![]() |
bb81a2973e | ||
![]() |
944f9b6ccc | ||
![]() |
12536d5e89 | ||
![]() |
2351a0cb4a | ||
![]() |
a77b388f8e | ||
![]() |
f5d5eee267 | ||
![]() |
553ba2c7c6 | ||
![]() |
eec3a45509 | ||
![]() |
5a85f2bf81 | ||
![]() |
7ed986e4eb | ||
![]() |
d7cf291836 | ||
![]() |
1c5cbd768d | ||
![]() |
bd434c3da7 | ||
![]() |
c9406b8e50 | ||
![]() |
9b9f6dd962 | ||
![]() |
18eefab292 | ||
![]() |
3adf0b7ef5 | ||
![]() |
a202eaa610 | ||
![]() |
acff254aa5 | ||
![]() |
3175c62e6d | ||
![]() |
ae9ee91bc4 | ||
![]() |
a22dcb9f4e |
26 changed files with 512 additions and 85 deletions
25
.github/workflows/sqlite3-migrations.yml
vendored
Normal file
25
.github/workflows/sqlite3-migrations.yml
vendored
Normal 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
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
|||
|
||||
bin
|
||||
db.sqlite3
|
||||
*.png
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
|
41
Makefile
Normal file
41
Makefile
Normal file
|
@ -0,0 +1,41 @@
|
|||
# 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 test
|
||||
cd frontend && npm run lint
|
||||
cd backend && make test
|
||||
cd backend && make lint
|
||||
|
||||
# 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!"
|
||||
|
||||
# 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
|
|
@ -59,7 +59,7 @@ 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.
|
||||
|
|
|
@ -13,7 +13,7 @@ MIGRATIONS_DIR = internal/database/migrations
|
|||
|
||||
# Build target
|
||||
build:
|
||||
$(GOBUILD) -o bin/server cmd/*.go
|
||||
$(GOBUILD) -o bin/server main.go
|
||||
|
||||
# Run target
|
||||
run: build
|
||||
|
@ -43,6 +43,7 @@ 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"; \
|
||||
|
@ -70,6 +71,31 @@ lint:
|
|||
# Default target
|
||||
default: build
|
||||
|
||||
# Generate swagger docs
|
||||
.PHONY: docs
|
||||
docs:
|
||||
swag init -outputTypes go
|
||||
|
||||
.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
|
||||
|
||||
# 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
|
||||
|
|
80
backend/docs/docs.go
Normal file
80
backend/docs/docs.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// 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": {
|
||||
"/api/register": {
|
||||
"post": {
|
||||
"description": "Register a new user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Register a new user",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User added",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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)
|
||||
}
|
|
@ -8,10 +8,36 @@ require (
|
|||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
)
|
||||
|
||||
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/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/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/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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
|
||||
github.com/gofiber/contrib/jwt v1.0.8 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/gofiber/contrib/jwt v1.0.8
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
)
|
||||
|
||||
// These are all for fiber
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y=
|
||||
github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U=
|
||||
github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4=
|
||||
github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA=
|
||||
github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI=
|
||||
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=
|
||||
|
@ -14,20 +31,28 @@ github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlg
|
|||
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/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=
|
||||
|
@ -38,10 +63,21 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
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/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=
|
||||
|
@ -50,9 +86,24 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g
|
|||
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/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/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=
|
||||
|
|
|
@ -2,9 +2,9 @@ package database
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
@ -12,11 +12,20 @@ import (
|
|||
|
||||
// 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
|
||||
}
|
||||
|
||||
// This struct is a wrapper type that holds the database connection
|
||||
|
@ -29,7 +38,11 @@ type Db struct {
|
|||
var scripts embed.FS
|
||||
|
||||
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
|
||||
const projectInsert = "INSERT INTO projects (name, description, user_id) SELECT ?, ?, id FROM users WHERE username = ?"
|
||||
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 = ""
|
||||
|
||||
// DbConnect connects to the database
|
||||
func DbConnect(dbpath string) Database {
|
||||
|
@ -48,6 +61,32 @@ 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)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
|
||||
var userid int
|
||||
userid, err := d.GetUserId(username)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var projectid int
|
||||
projectid, err2 := d.GetProjectId(projectname)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
_, err3 := d.Exec(addUserToProject, projectid, userid, role)
|
||||
return err3
|
||||
}
|
||||
|
||||
// func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
|
||||
|
||||
// }
|
||||
|
||||
// AddUser adds a user to the database
|
||||
func (d *Db) AddUser(username string, password string) error {
|
||||
_, err := d.Exec(userInsert, username, password)
|
||||
|
@ -60,9 +99,20 @@ func (d *Db) RemoveUser(username string) error {
|
|||
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)
|
||||
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) { // WIP, denna kan vara goof
|
||||
var id int
|
||||
err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname)
|
||||
return id, err
|
||||
}
|
||||
|
||||
|
@ -101,7 +151,6 @@ func (d *Db) Migrate(dirname string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("Executed SQL file:", file.Name())
|
||||
}
|
||||
|
||||
if tr.Commit() != nil {
|
||||
|
|
|
@ -74,3 +74,32 @@ func TestDbRemoveUser(t *testing.T) {
|
|||
t.Error("RemoveUser failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromoteToAdmin(t *testing.T) {
|
||||
db, err := setupState()
|
||||
if err != nil {
|
||||
t.Error("setupState failed:", err)
|
||||
}
|
||||
|
||||
err = db.AddUser("test", "password")
|
||||
if err != nil {
|
||||
t.Error("AddUser failed:", err)
|
||||
}
|
||||
|
||||
err = db.PromoteToAdmin("test")
|
||||
if err != nil {
|
||||
t.Error("PromoteToAdmin failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// func TestAddTimeReport(t *testing.T) {
|
||||
|
||||
// }
|
||||
|
||||
// func TestAddUserToProject(t *testing.T) {
|
||||
|
||||
// }
|
||||
|
||||
// func TestChangeUserRole(t *testing.T) {
|
||||
|
||||
// }
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
-- 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,
|
||||
userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
|
||||
|
@ -5,5 +9,6 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
password VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
-- Users are commonly searched by username and userId
|
||||
CREATE INDEX IF NOT EXISTS users_username_index ON users (username);
|
||||
CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId);
|
|
@ -3,9 +3,9 @@ CREATE TABLE IF NOT EXISTS projects (
|
|||
projectId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
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 (user_id);
|
||||
CREATE INDEX IF NOT EXISTS projects_user_id_index ON projects (owner_user_id);
|
|
@ -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 ('admin');
|
||||
INSERT OR IGNORE INTO project_role (p_role) VALUES ('member');
|
|
@ -1,16 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
user_id INTEGER NOT NULL,
|
||||
project_id INTEGER NOT NULL,
|
||||
role STRING NOT NULL, -- 'admin' or 'member'
|
||||
p_role TEXT NOT NULL, -- 'admin' or 'member'
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
FOREIGN KEY (project_id) REFERENCES projects (id)
|
||||
FOREIGN KEY (p_role) REFERENCES project_role (p_role)
|
||||
PRIMARY KEY (user_id, project_id)
|
||||
);
|
||||
|
||||
-- Make sure that the role is either 'admin' or 'member'
|
||||
CREATE TRIGGER IF NOT EXISTS user_role_admin_or_member
|
||||
BEFORE INSERT ON user_roles
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'Invalid role') WHERE NEW.role NOT IN ('admin', 'member');
|
||||
END;
|
||||
);
|
4
backend/internal/database/migrations/0060_site_admin.sql
Normal file
4
backend/internal/database/migrations/0060_site_admin.sql
Normal 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
|
||||
)
|
16
backend/internal/database/migrations/0070_salts.sql
Normal file
16
backend/internal/database/migrations/0070_salts.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
-- It is unclear weather this table will be used
|
||||
|
||||
-- Create the table to store hash salts
|
||||
CREATE TABLE salts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
salt TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Commented out for now, no time for good practices, which is atrocious
|
||||
-- Create a trigger to automatically generate a salt when inserting a new user record
|
||||
-- CREATE TRIGGER generate_salt_trigger
|
||||
-- AFTER INSERT ON users
|
||||
-- BEGIN
|
||||
-- INSERT INTO salts (salt) VALUES (randomblob(16));
|
||||
-- UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id;
|
||||
-- END;
|
|
@ -11,11 +11,11 @@ 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
|
||||
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
|
||||
// GetProject(c *fiber.Ctx) error // To get a specific project
|
||||
// UpdateProject(c *fiber.Ctx) error // To update a project
|
||||
|
@ -46,8 +46,19 @@ type GState struct {
|
|||
ButtonCount int
|
||||
}
|
||||
|
||||
// Register is a simple handler that registers a new user
|
||||
//
|
||||
// @Summary Register a new user
|
||||
// @Description Register a new user
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "User added"
|
||||
// @Failure 400 {string} string "Bad request"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /api/register [post]
|
||||
func (gs *GState) Register(c *fiber.Ctx) error {
|
||||
u := new(types.User)
|
||||
u := new(types.NewUser)
|
||||
if err := c.BodyParser(u); err != nil {
|
||||
return c.Status(400).SendString(err.Error())
|
||||
}
|
||||
|
@ -115,6 +126,7 @@ func (gs *GState) Login(c *fiber.Ctx) error {
|
|||
|
||||
// LoginRenew is a simple handler that renews the token
|
||||
func (gs *GState) LoginRenew(c *fiber.Ctx) error {
|
||||
// For testing: curl localhost:3000/restricted -H "Authorization: Bearer <token>"
|
||||
user := c.Locals("user").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||
|
@ -130,3 +142,24 @@ func (gs *GState) LoginRenew(c *fiber.Ctx) error {
|
|||
}
|
||||
return c.JSON(fiber.Map{"token": t})
|
||||
}
|
||||
|
||||
// CreateProject is a simple handler that creates a new project
|
||||
func (gs *GState) 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)
|
||||
p.Owner = claims["name"].(string)
|
||||
|
||||
if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil {
|
||||
return c.Status(500).SendString(err.Error())
|
||||
}
|
||||
|
||||
return c.Status(200).SendString("Project added")
|
||||
}
|
||||
|
|
21
backend/internal/types/project.go
Normal file
21
backend/internal/types/project.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
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"`
|
||||
}
|
||||
|
||||
// As it arrives from the client
|
||||
type NewProject struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
|
@ -16,6 +16,11 @@ func (u *User) ToPublicUser() (*PublicUser, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
type NewUser struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// PublicUser represents a user that is safe to send over the API (no password)
|
||||
type PublicUser struct {
|
||||
UserId string `json:"userId"`
|
||||
|
|
|
@ -1,18 +1,34 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
_ "ttime/docs"
|
||||
"ttime/internal/config"
|
||||
"ttime/internal/database"
|
||||
"ttime/internal/handlers"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/swagger"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
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
|
||||
|
||||
// @host localhost:8080
|
||||
// @BasePath /api
|
||||
|
||||
// @externalDocs.description OpenAPI
|
||||
// @externalDocs.url https://swagger.io/resources/open-api/
|
||||
|
||||
func main() {
|
||||
conf, err := config.ReadConfigFromFile("config.toml")
|
||||
if err != nil {
|
||||
|
@ -20,9 +36,11 @@ func main() {
|
|||
_ = conf.WriteConfigToFile("config.toml")
|
||||
}
|
||||
|
||||
// Pretty print the current config
|
||||
str, _ := json.MarshalIndent(conf, "", " ")
|
||||
fmt.Println(string(str))
|
||||
// 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)
|
||||
|
@ -31,6 +49,8 @@ func main() {
|
|||
// Create the server
|
||||
server := fiber.New()
|
||||
|
||||
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")
|
27
frontend/src/Pages/AdminPages/AdminMenuPage.tsx
Normal file
27
frontend/src/Pages/AdminPages/AdminMenuPage.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import BasicWindow from "../../Components/BasicWindow";
|
||||
|
||||
function AdminMenuPage(): JSX.Element {
|
||||
const content = (
|
||||
<>
|
||||
<h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</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]">
|
||||
<Link to="/admin-users-page">
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
Manage Users
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to="/admin-projects-page">
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
Manage Projects
|
||||
</h1>
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const buttons = <></>;
|
||||
|
||||
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
|
||||
}
|
||||
export default AdminMenuPage;
|
|
@ -1,36 +0,0 @@
|
|||
import reactLogo from "../assets/react.svg";
|
||||
import viteLogo from "/vite.svg";
|
||||
import "../index.css";
|
||||
import { CountButton } from "../Components/CountButton";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* The home page of the application
|
||||
* @returns {JSX.Element} The home page
|
||||
*/
|
||||
export default function HomePage(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
|
||||
<img src={viteLogo} className="logo h-32 p-5" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src={reactLogo}
|
||||
className="logo react h-32 p-5"
|
||||
alt="React logo"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className="card flex flex-col items-center space-y-4">
|
||||
<CountButton />
|
||||
<Link to="/settings">To Settings</Link>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import "../index.css";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* The settings page of the application
|
||||
* @returns {JSX.Element} The settings page
|
||||
*/
|
||||
export default function SettingsPage(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<h1>Very Fancy Settings Page</h1>
|
||||
<div className="card">
|
||||
<Link to="/">To Home</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4 KiB |
|
@ -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 AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
|
||||
|
||||
// This is where the routes are mounted
|
||||
const router = createBrowserRouter([
|
||||
|
@ -20,6 +21,10 @@ const router = createBrowserRouter([
|
|||
path: "/project",
|
||||
element: <UserProjectPage />,
|
||||
},
|
||||
{
|
||||
path: "/admin-menu",
|
||||
element: <AdminMenuPage />,
|
||||
},
|
||||
]);
|
||||
|
||||
// Semi-hacky way to get the root element
|
||||
|
|
15
go.work.sum
Normal file
15
go.work.sum
Normal file
|
@ -0,0 +1,15 @@
|
|||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
Loading…
Reference in a new issue