Skip to content

Commit

Permalink
feat: Implement Spotify Authentication (#1)
Browse files Browse the repository at this point in the history
* wip: spotify auth

* wip: Finalize spotify auth

* wip: log.Trace resolved itself lmao

* wip: Create http wrapper

* chore(ci): Add CI workflow for PRs

* fix(ci): Fix install step

* chore: Fix critical lint errors

* chore: Restructure Spotify package

* chore: Move web files to correct directory

* chore: Refactor globals to be service arguments

* chore: Restructure application code

* chore: Fix all (?) lint errors

* chore: Fix lint errors from new rules

* chore: Fix comment

* chore(request): Make PostForm use Post

* fix: Fix Spotify Settings template

* fix(spotify-auth): Fix refresh request

I missed that Spotify does not always return a new refresh token
when refreshing the access token which lead to the app saving
an empty refresh token the first time it was used.

* chore: Remove DB from spotify service
  • Loading branch information
beyerleinf authored Oct 12, 2024
1 parent c96c0ed commit 4751e26
Show file tree
Hide file tree
Showing 32 changed files with 974 additions and 171 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: "Build PR"
on:
pull_request:
branches:
- main
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.23"]
steps:
- name: "Checkout"
uses: actions/checkout@v4
- name: "Setup Go ${{ matrix.go-version }}"
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: "Install dependencies"
run: "go mod download"
- name: "Lint"
uses: golangci/golangci-lint-action@v6
with:
version: v1.60
- name: "Build"
run: "go build -o cmd/server/bin/server cmd/server/main.go"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
bin/

# Test binary, built with `go test -c`
*.test
Expand Down
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
linters:
enable:
- misspell
- perfsprint
- noctx
96 changes: 73 additions & 23 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,114 @@ package main

import (
"beyerleinf/spotify-backup/ent"
"beyerleinf/spotify-backup/internal/api/handler"
"beyerleinf/spotify-backup/internal/api/router"
"beyerleinf/spotify-backup/internal/config"
logger "beyerleinf/spotify-backup/pkg/log"
"beyerleinf/spotify-backup/internal/server/api/handler"
apiRouter "beyerleinf/spotify-backup/internal/server/api/router"
"beyerleinf/spotify-backup/internal/server/config"
uiHandler "beyerleinf/spotify-backup/internal/server/ui/handler"
uiRouter "beyerleinf/spotify-backup/internal/server/ui/router"
uiTmpl "beyerleinf/spotify-backup/internal/server/ui/template"
"beyerleinf/spotify-backup/pkg/logger"
"beyerleinf/spotify-backup/pkg/router"
"beyerleinf/spotify-backup/pkg/service/spotify"
"beyerleinf/spotify-backup/web"
"context"
"fmt"
"log"
"os"
"path/filepath"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
_ "github.com/lib/pq"
)

const storageDir = ".spotify-backup"

func main() {
slog := logger.New("main", logger.LevelInfo)
slogger := logger.New("main", logger.LevelInfo)

err := config.LoadConfig()
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalln("Failed to load config: %w", err)
panic(err)
}

slog.SetLogLevel(config.AppConfig.Server.LogLevel)
slogger.SetLogLevel(cfg.Server.LogLevel)

storageDir := createStorageDir(slogger)

dburl := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable",
config.AppConfig.Database.Host,
config.AppConfig.Database.Port,
config.AppConfig.Database.Username,
config.AppConfig.Database.DBName,
config.AppConfig.Database.Password,
dbURL := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable",
cfg.Database.Host,
cfg.Database.Port,
cfg.Database.Username,
cfg.Database.DBName,
cfg.Database.Password,
)

client, err := ent.Open("postgres", dburl)
client, err := ent.Open("postgres", dbURL)
if err != nil {
slog.Fatal("Failed opening connection to postgres", "err", err)
slogger.Fatal("Failed opening connection to postgres", "err", err)
panic(err)
}
defer client.Close()

if err := client.Schema.Create(context.Background()); err != nil {
slog.Fatal("Failed creating schema resources", "err", err)
slogger.Fatal("Failed creating schema resources", "err", err)
panic(err)
}

slog.Info("Connected to database")
slogger.Info("Connected to database")

healthHandler := handler.NewHealthHandler(client)
healthHandler := handler.NewHealthHandler(client, cfg)

e := echo.New()
e.HideBanner = true
e.HidePort = true
e.Use(logger.GetEchoLogger())
e.Use(middleware.Recover())

apiBase := e.Group("/api")
uiBase := e.Group("/ui")

router.SetupRoutes(apiBase,
apiRouter.HealthRoutes(healthHandler),
)

renderer, err := uiTmpl.NewRenderer(web.TemplatesFS)
if err != nil {
slogger.Fatal("Failed to initialize renderer", "err", err)
}

e.Renderer = renderer
e.StaticFS("/", web.StaticFS)

spotifyService := spotify.New(cfg, storageDir)

api := e.Group("/api")
spotifyHandler := uiHandler.NewSpotifyHandler(spotifyService, cfg)

router.SetupRoutes(api,
router.HealthRoutes(healthHandler),
router.SetupRoutes(uiBase,
uiRouter.SpotifyRoutes(spotifyHandler),
)

slog.Info(fmt.Sprintf("Starting server on [::]:%d", config.AppConfig.Server.Port))
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", config.AppConfig.Server.Port)))
slogger.Info(fmt.Sprintf("Starting server on [::]:%d", cfg.Server.Port))
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.Server.Port)))
}

func createStorageDir(slogger *logger.Logger) string {
homeDir, err := os.UserHomeDir()
if err != nil {
slogger.Error("Error getting home directory", "err", err)
}

dir := filepath.Join(homeDir, storageDir)

err = os.MkdirAll(dir, 0755)
if err != nil {
slogger.Fatal("Failed to create storage dir", "err", err)
panic(1)
}

slogger.Verbose(fmt.Sprintf("Using storage directory at %s.", dir))

return dir
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
Expand Down
20 changes: 0 additions & 20 deletions internal/api/router/health_routes.go

This file was deleted.

68 changes: 0 additions & 68 deletions internal/config/config.go

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,41 @@ package handler

import (
"beyerleinf/spotify-backup/ent"
"beyerleinf/spotify-backup/internal/config"
logger "beyerleinf/spotify-backup/pkg/log"
"beyerleinf/spotify-backup/internal/server/config"
"beyerleinf/spotify-backup/pkg/logger"
"context"
"fmt"
"net/http"

"github.com/labstack/echo/v4"
)

// A HealthHandler instance.
type HealthHandler struct {
slogger *logger.Logger
db *ent.Client
config *config.Config
}

func NewHealthHandler(db *ent.Client) *HealthHandler {
// NewHealthHandler creates a new instance of the [HealthHandler].
func NewHealthHandler(db *ent.Client, config *config.Config) *HealthHandler {
return &HealthHandler{
slogger: logger.New("health-check", config.AppConfig.Server.LogLevel),
slogger: logger.New("health-check", config.Server.LogLevel),
db: db,
config: config,
}
}

// GetHealthStatus checks the health status of various components and
// returns an API response.
func (h *HealthHandler) GetHealthStatus(c echo.Context) error {
db_err := h.testDBConnection()
dbErr := h.testDBConnection()

res := map[string]string{
"status": "ok",
}

if db_err != nil {
if dbErr != nil {
res["database"] = "err"
} else {
res["database"] = "ok"
Expand Down
22 changes: 22 additions & 0 deletions internal/server/api/router/health_routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package router

import (
"beyerleinf/spotify-backup/internal/server/api/handler"
"beyerleinf/spotify-backup/pkg/router"

"github.com/labstack/echo/v4"
)

// HealthRoutes returns all routes associated with the /health route.
func HealthRoutes(healthHandler *handler.HealthHandler) router.RouteGroup {
return router.RouteGroup{
Prefix: "/health",
Routes: []router.Route{
{
Method: echo.GET,
Path: "",
Handler: healthHandler.GetHealthStatus,
},
},
}
}
Loading

0 comments on commit 4751e26

Please sign in to comment.