From f57149c0d0b36418e0bc9c0d16451f0023a4d6ec Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sun, 2 Oct 2022 11:37:26 +0200 Subject: [PATCH] =?UTF-8?q?un-SSR=20this=20b=E2=86=92=C3=BE=C2=A9=C4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .drone.yml | 130 +++++++++---- Dockerfile | 2 +- cmd/stufflog3-local/main.go | 140 -------------- cmd/{stufflog3-lambda => stufflog3}/main.go | 43 ++++- frontend/package-lock.json | 174 ++---------------- frontend/package.json | 1 + frontend/src/app.d.ts | 5 +- frontend/src/hooks.ts | 68 +------ .../{static => src/lib/assets}/background.jpg | Bin frontend/src/lib/clients/sl3.ts | 6 +- .../contexts/ItemListContext.svelte | 4 +- .../contexts/ItemMultiListContext.svelte | 4 +- .../components/contexts/ProjectContext.svelte | 4 +- .../contexts/ProjectListContext.svelte | 4 +- .../contexts/ScopeListContext.svelte | 6 +- .../contexts/SprintListContext.svelte | 4 +- frontend/src/lib/modals/DeletionModal.svelte | 4 +- .../src/lib/modals/ItemAcquireModal.svelte | 6 +- .../src/lib/modals/ItemCreateModal.svelte | 10 +- .../lib/modals/ProjectCreateEditModal.svelte | 9 +- .../lib/modals/RequirementCreateModal.svelte | 12 +- .../lib/modals/ScopeCreateUpdateModal.svelte | 8 +- .../lib/modals/SprintCreateUpdateModal.svelte | 16 +- .../src/lib/modals/StatCreateEditModal.svelte | 11 +- .../routes/[scope=prettyid]/__layout.svelte | 9 +- .../history/[interval].svelte | 4 +- .../routes/[scope=prettyid]/overview.svelte | 16 +- .../projects/[project=prettyid].svelte | 4 +- .../sprints/[interval].svelte | 4 +- frontend/src/routes/__layout.svelte | 32 +++- frontend/src/routes/api/[...any].ts | 4 +- frontend/src/routes/index.svelte | 16 +- frontend/src/routes/login.svelte | 28 +-- frontend/svelte.config.js | 12 +- frontend/tsconfig.json | 35 +--- go.mod | 2 +- serverless.yml | 48 +++-- 37 files changed, 332 insertions(+), 553 deletions(-) delete mode 100644 cmd/stufflog3-local/main.go rename cmd/{stufflog3-lambda => stufflog3}/main.go (80%) rename frontend/{static => src/lib/assets}/background.jpg (100%) diff --git a/.drone.yml b/.drone.yml index c6a053e..ea9639d 100644 --- a/.drone.yml +++ b/.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 diff --git a/Dockerfile b/Dockerfile index 429ae47..6873ba4 100644 --- a/Dockerfile +++ b/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 diff --git a/cmd/stufflog3-local/main.go b/cmd/stufflog3-local/main.go deleted file mode 100644 index b877820..0000000 --- a/cmd/stufflog3-local/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/stufflog3-lambda/main.go b/cmd/stufflog3/main.go similarity index 80% rename from cmd/stufflog3-lambda/main.go rename to cmd/stufflog3/main.go index 3c3220d..63efefb 100644 --- a/cmd/stufflog3-lambda/main.go +++ b/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 { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 003d36f..4d90d47 100644 --- a/frontend/package-lock.json +++ b/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", diff --git a/frontend/package.json b/frontend/package.json index f5ec9ef..84b2966 100644 --- a/frontend/package.json +++ b/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", diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index 120d184..0451a58 100644 --- a/frontend/src/app.d.ts +++ b/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 } diff --git a/frontend/src/hooks.ts b/frontend/src/hooks.ts index 0734214..3d02b56 100644 --- a/frontend/src/hooks.ts +++ b/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 }); } diff --git a/frontend/static/background.jpg b/frontend/src/lib/assets/background.jpg similarity index 100% rename from frontend/static/background.jpg rename to frontend/src/lib/assets/background.jpg diff --git a/frontend/src/lib/clients/sl3.ts b/frontend/src/lib/clients/sl3.ts index f431873..19f84f7 100644 --- a/frontend/src/lib/clients/sl3.ts +++ b/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); } \ No newline at end of file diff --git a/frontend/src/lib/components/contexts/ItemListContext.svelte b/frontend/src/lib/components/contexts/ItemListContext.svelte index 0771248..4bd06b8 100644 --- a/frontend/src/lib/components/contexts/ItemListContext.svelte +++ b/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(items); let groupsWritable = writable(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(_) {} diff --git a/frontend/src/lib/components/contexts/ItemMultiListContext.svelte b/frontend/src/lib/components/contexts/ItemMultiListContext.svelte index 3bea870..58c9192 100644 --- a/frontend/src/lib/components/contexts/ItemMultiListContext.svelte +++ b/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 export let filters: Record @@ -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 diff --git a/frontend/src/lib/components/contexts/ProjectContext.svelte b/frontend/src/lib/components/contexts/ProjectContext.svelte index 228c474..1503584 100644 --- a/frontend/src/lib/components/contexts/ProjectContext.svelte +++ b/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); 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(_) {} diff --git a/frontend/src/lib/components/contexts/ProjectListContext.svelte b/frontend/src/lib/components/contexts/ProjectListContext.svelte index 2c30e5f..544a66d 100644 --- a/frontend/src/lib/components/contexts/ProjectListContext.svelte +++ b/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(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(_) {} diff --git a/frontend/src/lib/components/contexts/ScopeListContext.svelte b/frontend/src/lib/components/contexts/ScopeListContext.svelte index 19af979..391464b 100644 --- a/frontend/src/lib/components/contexts/ScopeListContext.svelte +++ b/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(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(_) {} diff --git a/frontend/src/lib/components/contexts/SprintListContext.svelte b/frontend/src/lib/components/contexts/SprintListContext.svelte index 1ef2c77..32e031b 100644 --- a/frontend/src/lib/components/contexts/SprintListContext.svelte +++ b/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(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(_) {} diff --git a/frontend/src/lib/modals/DeletionModal.svelte b/frontend/src/lib/modals/DeletionModal.svelte index c332be5..c97d3a1 100644 --- a/frontend/src/lib/modals/DeletionModal.svelte +++ b/frontend/src/lib/modals/DeletionModal.svelte @@ -7,6 +7,7 @@ - + \ No newline at end of file diff --git a/frontend/src/routes/api/[...any].ts b/frontend/src/routes/api/[...any].ts index c2cc09f..8e757e4 100644 --- a/frontend/src/routes/api/[...any].ts +++ b/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, { diff --git a/frontend/src/routes/index.svelte b/frontend/src/routes/index.svelte index 0bc9631..b4685c8 100644 --- a/frontend/src/routes/index.svelte +++ b/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 } diff --git a/frontend/src/routes/login.svelte b/frontend/src/routes/login.svelte index 48eb7c8..a83e3d1 100644 --- a/frontend/src/routes/login.svelte +++ b/frontend/src/routes/login.svelte @@ -1,28 +1,9 @@ - - -
diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js index 1ee17ff..6524d59 100644 --- a/frontend/svelte.config.js +++ b/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: { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 7b5a0dc..d1deb76 100644 --- a/frontend/tsconfig.json +++ b/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" } diff --git a/go.mod b/go.mod index 39bbd56..b5cf4d0 100644 --- a/go.mod +++ b/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 diff --git a/serverless.yml b/serverless.yml index 2e2a7e5..fa27de3 100644 --- a/serverless.yml +++ b/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: ''