197 lines
5.1 KiB
Go
197 lines
5.1 KiB
Go
package comicverse
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"code.capytal.cc/capytal/comicverse/assets"
|
|
"code.capytal.cc/capytal/comicverse/internals/joinedfs"
|
|
"code.capytal.cc/capytal/comicverse/repository"
|
|
"code.capytal.cc/capytal/comicverse/router"
|
|
"code.capytal.cc/capytal/comicverse/service"
|
|
"code.capytal.cc/capytal/comicverse/templates"
|
|
"code.capytal.cc/loreddev/x/tinyssert"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
)
|
|
|
|
func New(cfg Config, opts ...Option) (http.Handler, error) {
|
|
app := &app{
|
|
db: cfg.DB,
|
|
s3: cfg.S3,
|
|
bucket: cfg.Bucket,
|
|
privateKey: cfg.PrivateKey,
|
|
publicKey: cfg.PublicKey,
|
|
|
|
assets: assets.Files(),
|
|
templates: templates.Templates(),
|
|
developmentMode: false,
|
|
ctx: context.Background(),
|
|
|
|
assert: tinyssert.New(),
|
|
logger: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelError})),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(app)
|
|
}
|
|
|
|
if app.db == nil {
|
|
return nil, errors.New("database interface must not be nil")
|
|
}
|
|
if app.s3 == nil {
|
|
return nil, errors.New("s3 client must not be nil")
|
|
}
|
|
if app.privateKey == nil || len(app.privateKey) == 0 {
|
|
return nil, errors.New("private key client must not be nil")
|
|
}
|
|
if app.publicKey == nil || len(app.publicKey) == 0 {
|
|
return nil, errors.New("public key client must not be nil")
|
|
}
|
|
if app.bucket == "" {
|
|
return nil, errors.New("bucket must not be a empty string")
|
|
}
|
|
|
|
if app.assets == nil {
|
|
return nil, errors.New("static files must not be a nil interface")
|
|
}
|
|
if app.templates == nil {
|
|
return nil, errors.New("templates must not be a nil interface")
|
|
}
|
|
|
|
if app.ctx == nil {
|
|
return nil, errors.New("context must not be a nil interface")
|
|
}
|
|
|
|
if app.logger == nil {
|
|
return nil, errors.New("logger must not be a nil interface")
|
|
}
|
|
|
|
if app.assert == nil {
|
|
return nil, errors.New("assertions must not be a nil interface")
|
|
}
|
|
|
|
return app, app.setup()
|
|
}
|
|
|
|
type Config struct {
|
|
DB *sql.DB
|
|
S3 *s3.Client
|
|
Bucket string
|
|
PrivateKey ed25519.PrivateKey // TODO: Put this inside a service so we can easily rotate keys
|
|
PublicKey ed25519.PublicKey
|
|
}
|
|
|
|
type Option func(*app)
|
|
|
|
func WithContext(ctx context.Context) Option {
|
|
return func(app *app) { app.ctx = ctx }
|
|
}
|
|
|
|
func WithAssets(f fs.FS) Option {
|
|
return func(app *app) { app.assets = joinedfs.Join(f, app.assets) }
|
|
}
|
|
|
|
func WithTemplates(t templates.ITemplate) Option {
|
|
return func(app *app) { app.templates = t }
|
|
}
|
|
|
|
func WithAssertions(a tinyssert.Assertions) Option {
|
|
return func(app *app) { app.assert = a }
|
|
}
|
|
|
|
func WithLogger(l *slog.Logger) Option {
|
|
return func(app *app) { app.logger = l }
|
|
}
|
|
|
|
func WithDevelopmentMode() Option {
|
|
return func(app *app) { app.developmentMode = true }
|
|
}
|
|
|
|
type app struct {
|
|
db *sql.DB
|
|
s3 *s3.Client
|
|
bucket string
|
|
privateKey ed25519.PrivateKey
|
|
publicKey ed25519.PublicKey
|
|
|
|
ctx context.Context
|
|
|
|
assets fs.FS
|
|
templates templates.ITemplate
|
|
developmentMode bool
|
|
|
|
handler http.Handler
|
|
|
|
assert tinyssert.Assertions
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func (app *app) setup() error {
|
|
app.assert.NotNil(app.db)
|
|
app.assert.NotNil(app.s3)
|
|
app.assert.NotZero(app.bucket)
|
|
app.assert.NotNil(app.ctx)
|
|
app.assert.NotNil(app.assets)
|
|
app.assert.NotNil(app.logger)
|
|
|
|
userRepository, err := repository.NewUser(app.ctx, app.db, app.logger.WithGroup("repository.user"), app.assert)
|
|
if err != nil {
|
|
return fmt.Errorf("app: failed to start user repository: %w", err)
|
|
}
|
|
|
|
tokenRepository, err := repository.NewToken(app.ctx, app.db, app.logger.WithGroup("repository.token"), app.assert)
|
|
if err != nil {
|
|
return fmt.Errorf("app: failed to start token repository: %w", err)
|
|
}
|
|
|
|
publicationRepository, err := repository.NewPublication(app.ctx, app.db, app.logger.WithGroup("repository.publication"), app.assert)
|
|
if err != nil {
|
|
return fmt.Errorf("app: failed to start publication repository: %w", err)
|
|
}
|
|
|
|
permissionRepository, err := repository.NewPermissions(app.ctx, app.db, app.logger.WithGroup("repository.permission"), app.assert)
|
|
if err != nil {
|
|
return fmt.Errorf("app: failed to start permission repository: %w", err)
|
|
}
|
|
|
|
userService := service.NewUser(userRepository, app.logger.WithGroup("service.user"), app.assert)
|
|
tokenService := service.NewToken(service.TokenConfig{
|
|
PrivateKey: app.privateKey,
|
|
PublicKey: app.publicKey,
|
|
Repository: tokenRepository,
|
|
Logger: app.logger.WithGroup("service.token"),
|
|
Assertions: app.assert,
|
|
})
|
|
publicationService := service.NewPublication(publicationRepository, permissionRepository, app.logger.WithGroup("service.publication"), app.assert)
|
|
|
|
app.handler, err = router.New(router.Config{
|
|
UserService: userService,
|
|
TokenService: tokenService,
|
|
PublicationService: publicationService,
|
|
|
|
Templates: app.templates,
|
|
DisableCache: app.developmentMode,
|
|
Assets: app.assets,
|
|
|
|
Assertions: app.assert,
|
|
Logger: app.logger.WithGroup("router"),
|
|
})
|
|
if err != nil {
|
|
return errors.Join(errors.New("unable to initiate router"), err)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (app *app) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
app.assert.NotNil(app.handler)
|
|
app.handler.ServeHTTP(w, r)
|
|
}
|