Browse Source

un-SSR this b→þ©ħ

master
Gisle Aune 2 years ago
parent
commit
f57149c0d0
  1. 130
      .drone.yml
  2. 2
      Dockerfile
  3. 140
      cmd/stufflog3-local/main.go
  4. 43
      cmd/stufflog3/main.go
  5. 174
      frontend/package-lock.json
  6. 1
      frontend/package.json
  7. 5
      frontend/src/app.d.ts
  8. 68
      frontend/src/hooks.ts
  9. 0
      frontend/src/lib/assets/background.jpg
  10. 6
      frontend/src/lib/clients/sl3.ts
  11. 4
      frontend/src/lib/components/contexts/ItemListContext.svelte
  12. 4
      frontend/src/lib/components/contexts/ItemMultiListContext.svelte
  13. 4
      frontend/src/lib/components/contexts/ProjectContext.svelte
  14. 4
      frontend/src/lib/components/contexts/ProjectListContext.svelte
  15. 6
      frontend/src/lib/components/contexts/ScopeListContext.svelte
  16. 4
      frontend/src/lib/components/contexts/SprintListContext.svelte
  17. 4
      frontend/src/lib/modals/DeletionModal.svelte
  18. 6
      frontend/src/lib/modals/ItemAcquireModal.svelte
  19. 10
      frontend/src/lib/modals/ItemCreateModal.svelte
  20. 9
      frontend/src/lib/modals/ProjectCreateEditModal.svelte
  21. 12
      frontend/src/lib/modals/RequirementCreateModal.svelte
  22. 8
      frontend/src/lib/modals/ScopeCreateUpdateModal.svelte
  23. 16
      frontend/src/lib/modals/SprintCreateUpdateModal.svelte
  24. 11
      frontend/src/lib/modals/StatCreateEditModal.svelte
  25. 9
      frontend/src/routes/[scope=prettyid]/__layout.svelte
  26. 4
      frontend/src/routes/[scope=prettyid]/history/[interval].svelte
  27. 16
      frontend/src/routes/[scope=prettyid]/overview.svelte
  28. 4
      frontend/src/routes/[scope=prettyid]/projects/[project=prettyid].svelte
  29. 4
      frontend/src/routes/[scope=prettyid]/sprints/[interval].svelte
  30. 32
      frontend/src/routes/__layout.svelte
  31. 4
      frontend/src/routes/api/[...any].ts
  32. 16
      frontend/src/routes/index.svelte
  33. 28
      frontend/src/routes/login.svelte
  34. 12
      frontend/svelte.config.js
  35. 35
      frontend/tsconfig.json
  36. 2
      go.mod
  37. 48
      serverless.yml

130
.drone.yml

@ -4,38 +4,106 @@ kind: pipeline
type: docker
steps:
- name: go-test
image: golang:1.18
- name: backend-build
image: golang:1.19
depends_on: []
commands:
- go mod download
- CGO_ENABLED=0 go build -ldflags "-w -s" -o build/api/handler cmd/stufflog2-lambda/main.go
- name: backend-test
image: golang:1.19
depends_on: []
commands:
- go generate ./...
- go test -v ./...
- name: docker-backend-tag
image: plugins/docker
settings:
auto_tag: true
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/stufflog3/backend
registry: r.vmaple.dev
when:
event:
- tag
- name: backend-migrate
image: golang:1.19
depends_on: []
environment:
DB_CONNECT:
from_secret: DB_CONNECT
commands:
- go get -u github.com/pressly/goose/...
- cd scripts/goose-mysql/
- goose postgres "$DB_CONNECT" up
- name: backend-deploy
image: node:18.3.0
depends_on:
- backend-build
- backend-test
- backend-migrate
environment:
STUFFLOG3_AWS_CLIENT_ID:
from_secret: STUFFLOG3_AWS_CLIENT_ID
STUFFLOG3_AWS_CLIENT_SECRET:
from_secret: STUFFLOG3_AWS_CLIENT_SECRET
STUFFLOG3_AWS_POOL_CLIENT_ID:
from_secret: STUFFLOG3_AWS_POOL_CLIENT_ID
STUFFLOG3_AWS_POOL_CLIENT_SECRET:
from_secret: STUFFLOG3_AWS_POOL_CLIENT_SECRET
STUFFLOG3_AWS_POOL_ID:
from_secret: STUFFLOG3_AWS_POOL_ID
STUFFLOG3_AWS_REGION:
from_secret: STUFFLOG3_AWS_REGION
STUFFLOG3_MYSQL_HOST:
from_secret: STUFFLOG3_MYSQL_HOST
STUFFLOG3_MYSQL_PASSWORD:
from_secret: STUFFLOG3_MYSQL_PASSWORD
STUFFLOG3_MYSQL_PORT:
from_secret: STUFFLOG3_MYSQL_PORT
STUFFLOG3_MYSQL_SCHEMA:
from_secret: STUFFLOG3_MYSQL_SCHEMA
STUFFLOG3_MYSQL_USERNAME:
from_secret: STUFFLOG3_MYSQL_USERNAME
DOMAIN_NAME:
from_secret: DOMAIN_NAME
CERTIFICATE_ARN:
from_secret: CERTIFICATE_ARN
CERTIFICATE_NAME:
from_secret: CERTIFICATE_NAME
HOSTED_ZONE_ID:
from_secret: HOSTED_ZONE_ID
S3_WEBUI_BUCKET:
from_secret: S3_WEBUI_BUCKET
commands:
- apt-get update > /dev/null 2>&1
- apt-get -y install awscli zip > /dev/null 2>&1
- npm install -g serverless > /dev/null 2>&1
- npm install -g serverless-domain-manager > /dev/null 2>&1
- npm install -g serverless-apigateway-service-proxy > /dev/null 2>&1
- serverless deploy
- name: frontend-build
image: node:18.3.0
depends_on: []
environment:
VITE_STUFFLOG3_AWS_POOL_REGION:
from_secret: VITE_STUFFLOG3_AWS_POOL_REGION
VITE_STUFFLOG3_AWS_POOL_ID:
from_secret: VITE_STUFFLOG3_AWS_POOL_ID
VITE_STUFFLOG3_AWS_POOL_PUBLIC_CLIENT_ID:
from_secret: VITE_STUFFLOG3_AWS_POOL_PUBLIC_CLIENT_ID
commands:
- cd frontend
- npm install
- npm run build
- cp static/* build/
- name: docker-frontend-tag
image: plugins/docker
settings:
dockerfile: ./frontend/Dockerfile
context: ./frontend
auto_tag: true
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/stufflog3/frontend
registry: r.vmaple.dev
when:
event:
- tag
- name: frontend-deploy
image: amazon/aws-cli:latest
depends_on:
- frontend-build
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
AWS_DEFAULT_REGION:
from_secret: aws_region
S3_WEBUI_BUCKET:
from_secret: s3_webui_bucket
commands:
- cd frontend/build
- aws s3 sync . s3://$S3_WEBUI_BUCKET

2
Dockerfile

@ -8,7 +8,7 @@ COPY . .
# Build server and tools (CGO disabled to make them 100% static)
ENV CGO_ENABLED 0
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/stufflog3-local ./cmd/stufflog3-local
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/stufflog3 ./cmd/stufflog3
## 2. Distribute
# Use alpine linux

140
cmd/stufflog3-local/main.go

@ -1,140 +0,0 @@
package main
import (
"git.aiterp.net/stufflog3/stufflog3/ports/cognitoauth"
"git.aiterp.net/stufflog3/stufflog3/ports/httpapi"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql"
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/sprints"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
"github.com/gin-gonic/gin"
"log"
"os"
"os/signal"
"strconv"
"syscall"
)
func main() {
db, err := mysql.Connect(
os.Getenv("STUFFLOG3_MYSQL_HOST"),
envInt("STUFFLOG3_MYSQL_PORT"),
os.Getenv("STUFFLOG3_MYSQL_USERNAME"),
os.Getenv("STUFFLOG3_MYSQL_PASSWORD"),
os.Getenv("STUFFLOG3_MYSQL_SCHEMA"),
)
if err != nil {
log.Println("Failed to open database:", err)
os.Exit(1)
}
cognitoAuth, err := cognitoauth.New(
os.Getenv("STUFFLOG3_AWS_REGION"),
os.Getenv("STUFFLOG3_AWS_CLIENT_ID"),
os.Getenv("STUFFLOG3_AWS_CLIENT_SECRET"),
os.Getenv("STUFFLOG3_AWS_POOL_ID"),
os.Getenv("STUFFLOG3_AWS_POOL_CLIENT_ID"),
os.Getenv("STUFFLOG3_AWS_POOL_CLIENT_SECRET"),
)
if err != nil {
log.Println("Failed to setup cognito:", err)
os.Exit(1)
}
authService := &auth.Service{
Provider: cognitoAuth,
}
scopesService := &scopes.Service{
Auth: authService,
Repository: db.Scopes(),
StatsLister: db.Stats(),
}
statsService := &stats.Service{
Scopes: scopesService,
Repository: db.Stats(),
}
itemsService := &items.Service{
Auth: authService,
Scopes: scopesService,
Stats: statsService,
Repository: db.Items(),
RequirementFetcher: db.Projects(),
ProjectFetcher: db.Projects(),
}
projectsService := &projects.Service{
Auth: authService,
Scopes: scopesService,
Stats: statsService,
Items: itemsService,
Repository: db.Projects(),
}
sprintsService := &sprints.Service{
Scopes: scopesService,
Items: itemsService,
Projects: projectsService,
Repository: db.Sprints(),
}
server := gin.New()
httpapi.Auth(server.Group("/api/v1/auth"), authService)
apiV1 := server.Group("/api/v1")
if os.Getenv("STUFFLOG3_USE_DUMMY_USER") == "true" {
log.Println("Using dummy UUID")
apiV1.Use(httpapi.DummyMiddleware(authService, "c11230be-4912-4313-83b0-410a248b5bd1"))
} else {
apiV1.Use(httpapi.JwtValidatorMiddleware(authService))
}
apiV1Scopes := apiV1.Group("/scopes")
httpapi.Scopes(apiV1Scopes, scopesService)
apiV1ScopesSub := apiV1Scopes.Group("/:scope_id")
apiV1ScopesSub.Use(httpapi.ScopeMiddleware(scopesService, authService))
httpapi.Projects(apiV1ScopesSub.Group("/projects"), projectsService)
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
apiV1AllScopes := apiV1.Group("/all-scopes")
apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService))
httpapi.ItemsAllScopes(apiV1AllScopes.Group("/items"), itemsService)
httpapi.SprintsAllScopes(apiV1AllScopes.Group("/sprints"), sprintsService)
exitSignal := make(chan os.Signal)
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
errCh := make(chan error)
go func() {
err := server.Run(":8239")
if err != nil {
errCh <- err
}
}()
select {
case sig := <-exitSignal:
{
log.Println("Received signal", sig)
os.Exit(0)
}
case err := <-errCh:
{
log.Println("Server run failed:", err)
os.Exit(2)
}
}
}
func envInt(key string) int {
value := os.Getenv(key)
if value == "" {
return 0
}
intValue, err := strconv.Atoi(value)
if err != nil {
return 0
}
return intValue
}

43
cmd/stufflog3-lambda/main.go → cmd/stufflog3/main.go

@ -2,6 +2,7 @@ package main
import (
"context"
"flag"
"git.aiterp.net/stufflog3/stufflog3/ports/cognitoauth"
"git.aiterp.net/stufflog3/stufflog3/ports/httpapi"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql"
@ -17,10 +18,16 @@ import (
"github.com/gin-gonic/gin"
"log"
"os"
"os/signal"
"strconv"
"syscall"
)
var flagLocal = flag.Bool("local", false, "If set, run locally")
func main() {
flag.Parse()
db, err := mysql.Connect(
os.Getenv("STUFFLOG3_MYSQL_HOST"),
envInt("STUFFLOG3_MYSQL_PORT"),
@ -99,13 +106,39 @@ func main() {
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
apiV1AllScopes := apiV1.Group("/all-scopes")
apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService))
httpapi.ItemsAllScopes(apiV1AllScopes, itemsService)
httpapi.ItemsAllScopes(apiV1AllScopes.Group("/items"), itemsService)
httpapi.SprintsAllScopes(apiV1AllScopes.Group("/sprints"), sprintsService)
ginLambda := ginadapter.New(server)
lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return ginLambda.ProxyWithContext(ctx, request)
})
if *flagLocal {
exitSignal := make(chan os.Signal)
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
errCh := make(chan error)
go func() {
err := server.Run(":8239")
if err != nil {
errCh <- err
}
}()
select {
case sig := <-exitSignal:
{
log.Println("Received signal", sig)
os.Exit(0)
}
case err := <-errCh:
{
log.Println("Server run failed:", err)
os.Exit(2)
}
}
} else {
ginLambda := ginadapter.New(server)
lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return ginLambda.ProxyWithContext(ctx, request)
})
}
}
func envInt(key string) int {

174
frontend/package-lock.json

@ -10,11 +10,11 @@
"devDependencies": {
"@aws-amplify/auth": "^4.5.6",
"@aws-amplify/core": "^4.5.6",
"@djverrall/sveltekit-lambda-adapter": "^0.0.14",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@nikso/adapter-serverless": "^0.1.0",
"@sveltejs/adapter-auto": "next",
"@sveltejs/adapter-node": "^1.0.0-next.78",
"@sveltejs/adapter-static": "^1.0.0-next.43",
"@sveltejs/kit": "next",
"@types/cookie": "^0.5.1",
"@types/marked": "^4.0.3",
@ -7224,34 +7224,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@djverrall/sveltekit-lambda-adapter": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@djverrall/sveltekit-lambda-adapter/-/sveltekit-lambda-adapter-0.0.14.tgz",
"integrity": "sha512-kjg3du9tS9aFAoLMiD/MH36exky9tg27zw1aG8VUY3M5xL6g0nbstULPBZYwSkVFcBXupVLbBkWihFXCzNqxgQ==",
"dev": true,
"dependencies": {
"mime-types": "^2.1.35",
"node-fetch": "^3.2.3"
}
},
"node_modules/@djverrall/sveltekit-lambda-adapter/node_modules/node-fetch": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
"dev": true,
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz",
@ -8494,6 +8466,12 @@
"tiny-glob": "^0.2.9"
}
},
"node_modules/@sveltejs/adapter-static": {
"version": "1.0.0-next.43",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-1.0.0-next.43.tgz",
"integrity": "sha512-PAgSp1GA8HkYE4p30/sBvJme2nefhcTBJafqQdMNoUksWZF2WzuL8OEO8wa9ndE6cghcGk3j6Ve0Oskg/wtTOw==",
"dev": true
},
"node_modules/@sveltejs/adapter-vercel": {
"version": "1.0.0-next.58",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-vercel/-/adapter-vercel-1.0.0-next.58.tgz",
@ -11054,15 +11032,6 @@
"node": ">=0.10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
"dev": true,
"engines": {
"node": ">= 12"
}
},
"node_modules/dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
@ -12733,29 +12702,6 @@
"pend": "~1.2.0"
}
},
"node_modules/fetch-blob": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@ -13014,18 +12960,6 @@
"node": ">= 0.12"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dev": true,
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/formidable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
@ -16721,25 +16655,6 @@
"node": ">= 0.10.5"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -21633,15 +21548,6 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -28179,29 +28085,6 @@
"to-fast-properties": "^2.0.0"
}
},
"@djverrall/sveltekit-lambda-adapter": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@djverrall/sveltekit-lambda-adapter/-/sveltekit-lambda-adapter-0.0.14.tgz",
"integrity": "sha512-kjg3du9tS9aFAoLMiD/MH36exky9tg27zw1aG8VUY3M5xL6g0nbstULPBZYwSkVFcBXupVLbBkWihFXCzNqxgQ==",
"dev": true,
"requires": {
"mime-types": "^2.1.35",
"node-fetch": "^3.2.3"
},
"dependencies": {
"node-fetch": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
"dev": true,
"requires": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
}
}
}
},
"@fortawesome/fontawesome-common-types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz",
@ -29231,6 +29114,12 @@
"tiny-glob": "^0.2.9"
}
},
"@sveltejs/adapter-static": {
"version": "1.0.0-next.43",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-1.0.0-next.43.tgz",
"integrity": "sha512-PAgSp1GA8HkYE4p30/sBvJme2nefhcTBJafqQdMNoUksWZF2WzuL8OEO8wa9ndE6cghcGk3j6Ve0Oskg/wtTOw==",
"dev": true
},
"@sveltejs/adapter-vercel": {
"version": "1.0.0-next.58",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-vercel/-/adapter-vercel-1.0.0-next.58.tgz",
@ -31327,12 +31216,6 @@
"assert-plus": "^1.0.0"
}
},
"data-uri-to-buffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
"dev": true
},
"dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
@ -32591,16 +32474,6 @@
"pend": "~1.2.0"
}
},
"fetch-blob": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
"dev": true,
"requires": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
}
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@ -32789,15 +32662,6 @@
"mime-types": "^2.1.12"
}
},
"formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dev": true,
"requires": {
"fetch-blob": "^3.1.2"
}
},
"formidable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
@ -35822,12 +35686,6 @@
"minimatch": "^3.0.2"
}
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"dev": true
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -39702,12 +39560,6 @@
"defaults": "^1.0.3"
}
},
"web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"dev": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

1
frontend/package.json

@ -17,6 +17,7 @@
"@nikso/adapter-serverless": "^0.1.0",
"@sveltejs/adapter-auto": "next",
"@sveltejs/adapter-node": "^1.0.0-next.78",
"@sveltejs/adapter-static": "^1.0.0-next.43",
"@sveltejs/kit": "next",
"@types/cookie": "^0.5.1",
"@types/marked": "^4.0.3",

5
frontend/src/app.d.ts

@ -10,14 +10,11 @@ declare namespace App {
}
// interface Platform {}
interface Session {
user?: import("$lib/models/user").default
idToken?: string
req?: any
groupOptions?: import("$lib/models/items").GroupItemsOptions
amplifyConfig: any
}
interface Stuff {
idToken?: string,
title?: string,
scope?: import("$lib/models/scope").default
}

68
frontend/src/hooks.ts

@ -1,67 +1,3 @@
import cookie from 'cookie';
import { Amplify, withSSRContext } from 'aws-amplify';
import type { GetSession, Handle } from '@sveltejs/kit';
Amplify.configure({
Auth: {
region: process.env.STUFFLOG3_AWS_POOL_REGION,
userPoolId: process.env.STUFFLOG3_AWS_POOL_ID,
userPoolWebClientId: process.env.STUFFLOG3_AWS_POOL_PUBLIC_CLIENT_ID,
},
ssr: true,
});
export const getSession: GetSession = ({ locals, request }) => {
return {
idToken: locals.idToken,
user: locals.user,
req: { headers: { cookie: request.headers.get("Cookie") } },
amplifyConfig: {
region: process.env.STUFFLOG3_AWS_POOL_REGION,
userPoolId: process.env.STUFFLOG3_AWS_POOL_ID,
userPoolWebClientId: process.env.STUFFLOG3_AWS_POOL_PUBLIC_CLIENT_ID,
},
};
};
export const handle: Handle = async({ event, resolve }) => {
const {Auth} = withSSRContext({
req: { headers: { cookie: event.request.headers.get("Cookie") } }
});
const newCookies = {};
try {
const curr = await Auth.currentAuthenticatedUser();
event.locals.idToken = curr.signInUserSession?.idToken?.jwtToken;
if (event.locals.idToken != null) {
event.locals.user = {
id: curr.signInUserSession.idToken.payload["sub"],
name: curr.signInUserSession.idToken.payload["cognito:username"],
}
}
const store = curr?.storage?.store || {};
const cookies = cookie.parse(event.request.headers.get("Cookie"));
for (const key in store) {
if (store[key] != cookies[key]) {
newCookies[key] = store[key]
}
}
} catch(err) {
console.warn(err)
}
const response = await resolve(event);
for (const key in newCookies) {
response.headers.append("Set-Cookie", cookie.serialize(key, newCookies[key], {
path: "/",
maxAge: 8640000,
expires: new Date(Date.now() + 8640000000),
}));
}
return response;
export async function handle({ event, resolve }) {
return resolve(event, { ssr: false });
}

0
frontend/static/background.jpg → frontend/src/lib/assets/background.jpg

Before

Width: 1920  |  Height: 1080  |  Size: 112 KiB

After

Width: 1920  |  Height: 1080  |  Size: 112 KiB

6
frontend/src/lib/clients/sl3.ts

@ -25,7 +25,7 @@ export default class SL3APIClient {
if (typeof(root) === "string") {
this.root = root || "";
} else if (root === true) {
this.root = process.env.STUFFLOG3_API;
this.root = import.meta.env.VITE_STUFFLOG3_API;
} else {
this.root = ""
}
@ -180,6 +180,6 @@ export default class SL3APIClient {
}
}
export function sl3(fetcher: FetchImpl) {
return new SL3APIClient(fetcher);
export function sl3(fetcher: FetchImpl, idToken?: string) {
return new SL3APIClient(fetcher, idToken ? Promise.resolve(idToken) : null);
}

4
frontend/src/lib/components/contexts/ItemListContext.svelte

@ -25,12 +25,14 @@
import { getScopeContext } from "./ScopeContext.svelte";
import type Item from "$lib/models/item";
import type { ItemFilter, ItemGroup } from "$lib/models/item";
import { getStores } from "$app/stores";
export let items: Item[];
export let groups: ItemGroup[];
export let filter: ItemFilter;
const {scope} = getScopeContext();
const {page} = getStores();
let itemsWritable = writable<Item[]>(items);
let groupsWritable = writable<ItemGroup[]>(groups);
@ -49,7 +51,7 @@
}
try {
const res = await sl3(fetch).listItems($scope.id, filter, true, {});
const res = await sl3(fetch, $page.stuff.idToken).listItems($scope.id, filter, true, {});
itemsWritable.set(res.items);
groupsWritable.set(res.groups);
} catch(_) {}

4
frontend/src/lib/components/contexts/ItemMultiListContext.svelte

@ -24,8 +24,10 @@
import { getScopeContext } from "./ScopeContext.svelte";
import deepEqual from "$lib/utils/object";
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
const {scope} = getScopeContext();
const {page} = getStores();
export let lists: Record<string, Item[]>
export let filters: Record<string, ItemFilter>
@ -43,7 +45,7 @@
loading = {...loading, [key]: true};
try {
const items = await sl3(fetch).listItems(global ? "ALL" : $scope.id, filter)
const items = await sl3(fetch, $page.stuff.idToken).listItems(global ? "ALL" : $scope.id, filter)
listsWritable.update(l => ({...l, [key.replace("Filter", "Items")]: items}));
} catch(_err) {
// TODO: Use err

4
frontend/src/lib/components/contexts/ProjectContext.svelte

@ -22,10 +22,12 @@
import { getContext, setContext } from "svelte";
import { sl3 } from "$lib/clients/sl3";
import { getScopeContext } from "./ScopeContext.svelte";
import { getStores } from "$app/stores";
export let project: Project;
const {scope} = getScopeContext();
const {page} = getStores();
let projectWritable = writable<Project>(project);
let lastSet = project;
@ -42,7 +44,7 @@
}
try {
const newProject = await sl3(fetch).findProject($scope.id, project.id)
const newProject = await sl3(fetch, $page.stuff.idToken).findProject($scope.id, project.id)
projectWritable.set(newProject);
} catch(_) {}

4
frontend/src/lib/components/contexts/ProjectListContext.svelte

@ -22,10 +22,12 @@
import { sl3 } from "$lib/clients/sl3";
import { getScopeContext } from "./ScopeContext.svelte";
import type { ProjectEntry } from "$lib/models/project";
import { getStores } from "$app/stores";
export let projects: ProjectEntry[];
const {scope} = getScopeContext();
const {page} = getStores();
let projectWritable = writable<ProjectEntry[]>(projects);
let loading = false;
@ -42,7 +44,7 @@
}
try {
const newProjects = await sl3(fetch).listProjects($scope.id)
const newProjects = await sl3(fetch, $page.stuff.idToken).listProjects($scope.id)
projectWritable.set(newProjects);
} catch(_) {}

6
frontend/src/lib/components/contexts/ScopeListContext.svelte

@ -20,11 +20,13 @@
import { readable, writable, type Readable } from "svelte/store";
import { getContext, setContext } from "svelte";
import { sl3 } from "$lib/clients/sl3";
import { getScopeContext } from "./ScopeContext.svelte";
import type Scope from "$lib/models/scope";
import { getStores } from "$app/stores";
export let scopes: Scope[];
const {page} = getStores();
let scopesWritable = writable<Scope[]>(scopes);
let loading = false;
let lastSet = scopes;
@ -40,7 +42,7 @@
}
try {
const newScopes = await sl3(fetch).listScopes()
const newScopes = await sl3(fetch, $page.stuff.idToken).listScopes()
scopesWritable.set(newScopes);
} catch(_) {}

4
frontend/src/lib/components/contexts/SprintListContext.svelte

@ -24,6 +24,7 @@
import type Sprint from "$lib/models/sprint";
import parseInterval from "$lib/utils/timeinterval";
import { getTimeContext } from "./TimeContext.svelte";
import { getStores } from "$app/stores";
export let sprints: Sprint[];
export let intervalString: string;
@ -31,6 +32,7 @@
const {scope} = getScopeContext();
const {now} = getTimeContext();
const {page} = getStores();
let sprintsWritable = writable<Sprint[]>(sprints);
let loading = false;
@ -47,7 +49,7 @@
}
try {
const newSprints = await sl3(fetch).listSprints(global ? "ALL" : $scope.id, parseInterval(intervalString, $now));
const newSprints = await sl3(fetch, $page.stuff.idToken).listSprints(global ? "ALL" : $scope.id, parseInterval(intervalString, $now));
sprintsWritable.set(newSprints);
} catch(_) {}

4
frontend/src/lib/modals/DeletionModal.svelte

@ -7,6 +7,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
@ -28,6 +29,7 @@
const {reloadSprintList} = getSprintListContext();
const {reloadItemList} = getItemListContext();
const {reloadItemLists} = getItemMultiListContext();
const {page} = getStores();
let endpoint: string;
@ -125,7 +127,7 @@
loading = true;
try {
const res = await sl3(fetch).fetch("DELETE", endpoint);
const res = await sl3(fetch, $page.stuff.idToken).fetch("DELETE", endpoint);
if (navigate) {
if (noun === "Project") {

6
frontend/src/lib/modals/ItemAcquireModal.svelte

@ -1,6 +1,7 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
@ -18,6 +19,7 @@
const {reloadItemList} = getItemListContext();
const {reloadSprintList} = getSprintListContext();
const {reloadItemLists} = getItemMultiListContext();
const {page} = getStores();
let item: Partial<ItemInput>
let itemId: number
@ -60,7 +62,7 @@
loading = true;
try {
await sl3(fetch).updateItem(scopeId, itemId, {
await sl3(fetch, $page.stuff.idToken).updateItem(scopeId, itemId, {
...item,
acquiredTime: new Date(item.acquiredTime).toISOString(),
});

10
frontend/src/lib/modals/ItemCreateModal.svelte

@ -1,6 +1,7 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
@ -11,7 +12,7 @@
import { getScopeListContext } from "$lib/components/contexts/ScopeListContext.svelte";
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte";
import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte";
import StatInput from "$lib/components/controls/StatInput.svelte";
import type Item from "$lib/models/item";
import type { ItemInput } from "$lib/models/item";
@ -27,6 +28,7 @@ import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte
const {reloadItemList} = getItemListContext();
const {reloadSprintList} = getSprintListContext();
const {reloadItemLists} = getItemMultiListContext();
const {page} = getStores();
let item: ItemInput
let itemId: number
@ -135,7 +137,7 @@ import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte
}
}
await sl3(fetch).createItem(scopeId, submission);
await sl3(fetch, $page.stuff.idToken).createItem(scopeId, submission);
break;
case "Edit":
if (!submission.acquiredTime) {
@ -146,7 +148,7 @@ import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte
}
submission.stats = statDiff(currentStats, submission.stats)
await sl3(fetch).updateItem(scopeId, itemId, submission);
await sl3(fetch, $page.stuff.idToken).updateItem(scopeId, itemId, submission);
break;
}

9
frontend/src/lib/modals/ProjectCreateEditModal.svelte

@ -1,8 +1,8 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { goto } from "$app/navigation";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
@ -19,6 +19,7 @@ import { goto } from "$app/navigation";
const {scope} = getScopeContext();
const {reloadProject} = getProjectContext();
const {reloadProjectList} = getProjectListContext();
const {page} = getStores();
let project: ProjectInput
let projectId: number
@ -75,11 +76,11 @@ import { goto } from "$app/navigation";
try {
switch (op) {
case "Create":
const newProject = await sl3(fetch).createProject($scope.id, project);
const newProject = await sl3(fetch, $page.stuff.idToken).createProject($scope.id, project);
projectId = newProject.id
break;
case "Edit":
await sl3(fetch).updateProject($scope.id, projectId, project);
await sl3(fetch, $page.stuff.idToken).updateProject($scope.id, projectId, project);
break;
}

12
frontend/src/lib/modals/RequirementCreateModal.svelte

@ -1,6 +1,7 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
@ -8,14 +9,15 @@
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
import StatInput from "$lib/components/controls/StatInput.svelte";
import StatusSelect from "$lib/components/controls/StatusSelect.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import type { ProjectEntry, Requirement, RequirementInput } from "$lib/models/project";
import Status from "$lib/models/status";
import { statDiff } from "$lib/utils/stat";
import { statDiff } from "$lib/utils/stat";
const {currentModal, closeModal} = getModalContext();
const {scope} = getScopeContext();
const {project, reloadProject} = getProjectContext();
const {page} = getStores();
let requirement: RequirementInput
let projectId: number
@ -88,7 +90,7 @@ import { statDiff } from "$lib/utils/stat";
try {
switch (op) {
case "Create":
await sl3(fetch).createRequirement($scope.id, projectId, requirement);
await sl3(fetch, $page.stuff.idToken).createRequirement($scope.id, projectId, requirement);
break;
case "Edit":
const submission: Partial<RequirementInput> = {
@ -96,7 +98,7 @@ import { statDiff } from "$lib/utils/stat";
stats: statDiff(oldStats, requirement.stats, -1)
};
await sl3(fetch).updateRequirement($scope.id, projectId, requirementId, submission);
await sl3(fetch, $page.stuff.idToken).updateRequirement($scope.id, projectId, requirementId, submission);
break;
}

8
frontend/src/lib/modals/ScopeCreateUpdateModal.svelte

@ -1,6 +1,7 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
@ -12,6 +13,7 @@
const {currentModal, closeModal} = getModalContext();
const {updateScope} = getScopeContext();
const {reloadScopeList} = getScopeListContext();
const {page} = getStores();
let scope: ScopeInput
let scopeId: number
@ -67,11 +69,11 @@
try {
switch (op) {
case "Create":
await sl3(fetch).createScope(scope);
await sl3(fetch, $page.stuff.idToken).createScope(scope);
await reloadScopeList();
break;
case "Edit":
const res = await sl3(fetch).updateScope(scopeId, scope);
const res = await sl3(fetch, $page.stuff.idToken).updateScope(scopeId, scope);
updateScope(res);
break;
}

16
frontend/src/lib/modals/SprintCreateUpdateModal.svelte

@ -1,6 +1,7 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
@ -13,12 +14,13 @@
import type Scope from "$lib/models/scope";
import type Sprint from "$lib/models/sprint";
import { SprintKind, type SprintInput, type SprintInputPart } from "$lib/models/sprint";
import { formatFormTime } from "$lib/utils/date";
import { partsDiff } from "$lib/utils/sprint";
import { formatFormTime } from "$lib/utils/date";
import { partsDiff } from "$lib/utils/sprint";
const {currentModal, closeModal} = getModalContext();
const {scope} = getScopeContext();
const {reloadSprintList} = getSprintListContext();
const {page} = getStores();
let sprint: SprintInput
let sprintId: number
@ -110,16 +112,16 @@ import { partsDiff } from "$lib/utils/sprint";
try {
switch (op) {
case "Create":
await sl3(fetch).createSprint(scopeId, submission);
await sl3(fetch, $page.stuff.idToken).createSprint(scopeId, submission);
break;
case "Edit":
await sl3(fetch).updateSprint(scopeId, sprintId, submission);
await sl3(fetch, $page.stuff.idToken).updateSprint(scopeId, sprintId, submission);
const {added, removed} = partsDiff(oldParts, submission.parts)
for (const part of added) {
await sl3(fetch).upsertSprintPart(scopeId, sprintId, part).catch(() => {});
await sl3(fetch, $page.stuff.idToken).upsertSprintPart(scopeId, sprintId, part).catch(() => {});
}
for (const part of removed) {
await sl3(fetch).deleteSprintPart(scopeId, sprintId, part).catch(() => {});
await sl3(fetch, $page.stuff.idToken).deleteSprintPart(scopeId, sprintId, part).catch(() => {});
}
break;

11
frontend/src/lib/modals/StatCreateEditModal.svelte

@ -1,19 +1,20 @@
<script lang="ts">
import { sl3 } from "$lib/clients/sl3";
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import type { Requirement } from "$lib/models/project";
import type Scope from "$lib/models/scope";
import type { StatInput } from "$lib/models/stat";
import type Stat from "$lib/models/stat";
import { getAllContexts } from "svelte";
const {currentModal, closeModal} = getModalContext();
const {scope, upsertStat} = getScopeContext();
const {page} = getStores();
let stat: StatInput
let statId: number
@ -76,11 +77,11 @@ import Checkbox from "$lib/components/layout/Checkbox.svelte";
try {
switch (op) {
case "Create":
const createdStat = await sl3(fetch).createStat($scope.id, stat);
const createdStat = await sl3(fetch, $page.stuff.idToken).createStat($scope.id, stat);
upsertStat(createdStat);
break;
case "Edit":
const editedStat = await sl3(fetch).updateStat($scope.id, statId, {
const editedStat = await sl3(fetch, $page.stuff.idToken).updateStat($scope.id, statId, {
...stat,
allowedAmounts: void(0),
});

9
frontend/src/routes/[scope=prettyid]/__layout.svelte

@ -4,11 +4,14 @@
import { sl3 } from "$lib/clients/sl3";
import type { ProjectEntry } from "$lib/models/project";
export const load: Load = async({ fetch, params }) => {
export const load: Load = async({ fetch, params, stuff }) => {
const scopeId = parseInt(params.scope.split("-")[0]);
const client = sl3(fetch, stuff.idToken);
const scope = await sl3(fetch).findScope(scopeId);
const projects = await sl3(fetch).listProjects(scopeId);
const [scope, projects] = await Promise.all([
client.findScope(scopeId),
client.listProjects(scopeId),
]);
return {
props: { projects, scope },

4
frontend/src/routes/[scope=prettyid]/history/[interval].svelte

@ -6,7 +6,7 @@
import type Item from "$lib/models/item";
import type { ItemFilter, ItemGroup } from "$lib/models/item";
export const load: Load = async({ fetch, params, session }) => {
export const load: Load = async({ fetch, params, session, stuff }) => {
const scopeId = parseInt(params.scope.split("-")[0]);
const interval = parseInterval(params.interval, new Date());
@ -15,7 +15,7 @@
acquiredTime: interval,
scheduledDate: datesOf(interval),
};
const {items, groups} = await sl3(fetch).listItems(scopeId, filter, true, session.groupOptions);
const {items, groups} = await sl3(fetch, stuff.idToken).listItems(scopeId, filter, true, session.groupOptions);
return {
stuff: { title: "History" },

16
frontend/src/routes/[scope=prettyid]/overview.svelte

@ -17,16 +17,18 @@
}
}
export const load: Load = async({params, url, fetch}) => {
export const load: Load = async({params, stuff, fetch}) => {
const scopeId = parseInt(params.scope.split("-")[0]);
const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date());
const client = sl3(fetch, stuff.idToken);
const scheduledItems = (await sl3(fetch).listItems(scopeId, scheduledFilter));
const acquiredItems = (await sl3(fetch).listItems(scopeId, acquiredFilter));
const looseItems = (await sl3(fetch).listItems(scopeId, looseFilter));
const sprints = await sl3(fetch).listSprints(scopeId);
const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date());
const [scheduledItems, acquiredItems, looseItems, sprints] = await Promise.all([
client.listItems(scopeId, scheduledFilter),
client.listItems(scopeId, acquiredFilter),
client.listItems(scopeId, looseFilter),
client.listSprints(scopeId),
]);
return {
stuff: { title: "Overview" },

4
frontend/src/routes/[scope=prettyid]/projects/[project=prettyid].svelte

@ -3,11 +3,11 @@
import type Project from "$lib/models/project";
import type { Load } from "@sveltejs/kit/types/internal";
export const load: Load = async({params, fetch}) => {
export const load: Load = async({params, fetch, stuff}) => {
const scopeId = parseInt(params.scope.split("-")[0]);
const projectId = parseInt(params.project.split("-")[0]);
const project = await sl3(fetch).findProject(scopeId, projectId);
const project = await sl3(fetch, stuff.idToken).findProject(scopeId, projectId);
return {
stuff: { title: project.name },

4
frontend/src/routes/[scope=prettyid]/sprints/[interval].svelte

@ -2,7 +2,7 @@
import type { Load } from "@sveltejs/kit/types/internal";
import { sl3 } from "$lib/clients/sl3";
export const load: Load = async({ fetch, params }) => {
export const load: Load = async({ fetch, params, stuff }) => {
const scopeId = parseInt(params.scope.split("-")[0]);
let interval = parseInterval(params.interval, new Date());
@ -11,7 +11,7 @@
interval = {min: new Date("2000-01-01T00:00:00Z"), max: new Date("2187-01-01T00:00:00Z")}
}
const sprints = await sl3(fetch).listSprints(scopeId, interval);
const sprints = await sl3(fetch, stuff.idToken).listSprints(scopeId, interval);
return {
stuff: {

32
frontend/src/routes/__layout.svelte

@ -1,13 +1,35 @@
<script lang="ts" context="module">
import type { Load } from "@sveltejs/kit/types/internal";
import { Auth, Amplify } from "aws-amplify";
import backgroundImage from "$lib/assets/background.jpg";
export const load: Load = async({ fetch, session, url }) => {
if (session.user == null && !url.pathname.startsWith("/login")) {
Amplify.configure({
Auth: {
region: import.meta.env.VITE_STUFFLOG3_AWS_POOL_REGION,
userPoolId: import.meta.env.VITE_STUFFLOG3_AWS_POOL_ID,
userPoolWebClientId: import.meta.env.VITE_STUFFLOG3_AWS_POOL_PUBLIC_CLIENT_ID,
},
ssr: false,
});
export const load: Load = async({ url }) => {
let idToken: string | null = null;
try {
const sess = await Auth.currentSession();
idToken = sess?.getIdToken()?.getJwtToken();
} catch(err) {
console.warn(err);
}
if (idToken == null && !url.pathname.startsWith("/login")) {
return { status: 302, redirect: "/login" };
}
return { props: {} };
return { props: {}, stuff: { idToken } };
}
export const ssr = false;
</script>
<script lang="ts">
@ -20,11 +42,13 @@
let opacity = 0.175;
$: opacity = $page.url.pathname === "/" ? 0.3 : 0.2;
export const ssr = false;
</script>
<ModalContext>
<TimeContext>
<Background initialOrientation="landscape" opacity={opacity} />
<Background src={backgroundImage} initialOrientation="landscape" opacity={opacity} />
<slot></slot>
</TimeContext>
</ModalContext>

4
frontend/src/routes/api/[...any].ts

@ -1,11 +1,13 @@
import type { RequestHandler } from "@sveltejs/kit";
export const get: RequestHandler = async({ request, url, params, locals }) => {
const proxyUrl = `${process.env.STUFFLOG3_API}/api/${params.any}${url.search}`;
const proxyUrl = `${import.meta.env.VITE_STUFFLOG3_API}/api/${params.any}${url.search}`;
const headers = {};
if (locals.idToken != null) {
headers["Authorization"] = `Bearer ${locals.idToken}`;
} else if (request.headers.get("authorization") != null) {
headers["Authorization"] = request.headers.get("authorization");
}
const res = await fetch(proxyUrl, {

16
frontend/src/routes/index.svelte

@ -18,15 +18,19 @@
}
}
export const load: Load = async({ fetch }) => {
const scopes = await sl3(fetch).listScopes();
export const load: Load = async({ fetch, stuff }) => {
const client = sl3(fetch, stuff.idToken);
const scopes = await client.listScopes();
const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date());
const scheduledItems = (await sl3(fetch).listItems("ALL", scheduledFilter));
const acquiredItems = (await sl3(fetch).listItems("ALL", acquiredFilter));
const looseItems = (await sl3(fetch).listItems("ALL", looseFilter));
const sprints = await sl3(fetch).listSprints("ALL");
const [scheduledItems, acquiredItems, looseItems, sprints] = await Promise.all([
client.listItems("ALL", scheduledFilter),
client.listItems("ALL", acquiredFilter),
client.listItems("ALL", looseFilter),
client.listSprints("ALL"),
]);
return {
props: { scopes, scheduledItems, acquiredItems, looseItems, sprints }

28
frontend/src/routes/login.svelte

@ -1,28 +1,9 @@
<script lang="ts" context="module">
import type { Load } from "@sveltejs/kit/types/internal";
export const load: Load = async({ fetch, session, url }) => {
if (session.user != null) {
return { status: 302, redirect: "/" };
}
return { props: {} };
}
</script>
<script lang="ts">
import { Amplify, Auth } from 'aws-amplify';
import { Auth } from 'aws-amplify';
import { goto } from '$app/navigation';
import Modal from '$lib/components/common/Modal.svelte';
import ModalBody from '$lib/components/common/ModalBody.svelte';
import { session } from '$app/stores';
Amplify.configure({
Auth: $session.amplifyConfig,
ssr: true,
});
let username = '';
let password = '';
let error: string;
@ -35,12 +16,6 @@
try {
const res = await Auth.signIn(username, password);
$session.user = {
id: res.attributes.sub,
name: res.username,
};
$session.idToken = res.signInUserSession.idToken.jwtToken;
goto('/', {replaceState: true});
} catch (err) {
error = err?.toString() || err;
@ -48,7 +23,6 @@
loading = false;
}
</script>
<form on:submit|preventDefault={handleSignIn}>

12
frontend/svelte.config.js

@ -1,5 +1,5 @@
import preprocess from 'svelte-preprocess';
import adapter from '@sveltejs/adapter-node';
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -8,7 +8,15 @@ const config = {
preprocess: preprocess(),
kit: {
adapter: adapter(),
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: "index.html"
}),
prerender: {
enabled: true,
},
vite: {
resolve: {

35
frontend/tsconfig.json

@ -1,36 +1,3 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
/**
TypeScript doesn't know about import usages in the template because it only sees the
script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
*/
"preserveValueImports": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
"extends": "./.svelte-kit/tsconfig.json"
}

2
go.mod

@ -4,7 +4,6 @@ go 1.18
require (
github.com/Masterminds/squirrel v1.5.2
github.com/aws/aws-lambda-go v1.19.1
github.com/aws/aws-sdk-go v1.44.27
github.com/awslabs/aws-lambda-go-api-proxy v0.13.3
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
@ -16,6 +15,7 @@ require (
)
require (
github.com/aws/aws-lambda-go v1.19.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect

48
serverless.yml

@ -7,7 +7,7 @@ provider:
runtime: go1.x
memorySize: 512
stage: prod
region: ${env:AWS_DEFAULT_REGION}
region: ${env:STUFFLOG3_AWS_REGION}
tags:
Name: Stufflog3
Type: Application
@ -27,7 +27,6 @@ provider:
STUFFLOG3_MYSQL_PORT: ${env:STUFFLOG3_MYSQL_PORT}
STUFFLOG3_MYSQL_SCHEMA: ${env:STUFFLOG3_MYSQL_SCHEMA}
STUFFLOG3_MYSQL_USERNAME: ${env:STUFFLOG3_MYSQL_USERNAME}
STUFFLOG3_USE_DUMMY_USER: ${env:STUFFLOG3_USE_DUMMY_USER}
functions:
handleApi:
@ -41,17 +40,6 @@ functions:
method: ANY
path: /api/{proxy+}
timeout: 30
handleBFF:
runtime: nodejs16.x
handler: svelte.handler
package:
include:
- ./build/frontend/lambda/**
events:
- http:
method: ANY
path: /{proxy+}
timeout: 30
package:
individually: true
@ -63,6 +51,40 @@ plugins:
- serverless-apigateway-service-proxy
custom:
webuiBucket: ${env:S3_WEBUI_BUCKET}
apiGatewayServiceProxies:
- s3:
path: /_app/{myPath+}
method: get
action: GetObject
bucket: ${self:custom.webuiBucket}
roleArn: ${self:provider.role}
requestParameters:
'integration.request.path.myPath': 'method.request.path.myPath'
'integration.request.path.object': 'method.request.path.myPath'
'integration.request.header.cache-control': "'public, max-age=86400, immutable'"
- s3:
path: /favicon.png
method: get
action: GetObject
bucket: ${self:custom.webuiBucket}
roleArn: ${self:provider.role}
key: favicon.png
requestParameters:
'integration.request.path.myPath': 'method.request.path.myPath'
'integration.request.path.object': 'method.request.path.myPath'
'integration.request.header.cache-control': "'public, max-age=86400, immutable'"
- s3:
path: /{myPath+}
method: get
action: GetObject
bucket: ${self:custom.webuiBucket}
roleArn: ${self:provider.role}
key: index.html
requestParameters:
'integration.request.header.cache-control': "'public, max-age=86400, immutable'"
customDomain:
domainName: ${env:DOMAIN_NAME}
basePath: ''

Loading…
Cancel
Save