chore: include static assets in binary

This commit is contained in:
Elias Schneider
2025-01-03 15:08:55 +01:00
parent ee885fbff5
commit 785200de61
44 changed files with 71 additions and 60 deletions

View File

@@ -33,9 +33,6 @@ COPY --from=frontend-builder /app/frontend/node_modules ./frontend/node_modules
COPY --from=frontend-builder /app/frontend/package.json ./frontend/package.json COPY --from=frontend-builder /app/frontend/package.json ./frontend/package.json
COPY --from=backend-builder /app/backend/pocket-id-backend ./backend/pocket-id-backend COPY --from=backend-builder /app/backend/pocket-id-backend ./backend/pocket-id-backend
COPY --from=backend-builder /app/backend/migrations ./backend/migrations
COPY --from=backend-builder /app/backend/email-templates ./backend/email-templates
COPY --from=backend-builder /app/backend/images ./backend/images
COPY ./scripts ./scripts COPY ./scripts ./scripts
RUN chmod +x ./scripts/*.sh RUN chmod +x ./scripts/*.sh

View File

@@ -3,8 +3,10 @@ package bootstrap
import ( import (
"github.com/stonith404/pocket-id/backend/internal/common" "github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/internal/utils" "github.com/stonith404/pocket-id/backend/internal/utils"
"github.com/stonith404/pocket-id/backend/resources"
"log" "log"
"os" "os"
"path"
"strings" "strings"
) )
@@ -12,7 +14,7 @@ import (
func initApplicationImages() { func initApplicationImages() {
dirPath := common.EnvConfig.UploadPath + "/application-images" dirPath := common.EnvConfig.UploadPath + "/application-images"
sourceFiles, err := os.ReadDir("./images") sourceFiles, err := resources.FS.ReadDir("images")
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
log.Fatalf("Error reading directory: %v", err) log.Fatalf("Error reading directory: %v", err)
} }
@@ -27,10 +29,10 @@ func initApplicationImages() {
if sourceFile.IsDir() || imageAlreadyExists(sourceFile.Name(), destinationFiles) { if sourceFile.IsDir() || imageAlreadyExists(sourceFile.Name(), destinationFiles) {
continue continue
} }
srcFilePath := "./images/" + sourceFile.Name() srcFilePath := path.Join("images", sourceFile.Name())
destFilePath := dirPath + "/" + sourceFile.Name() destFilePath := path.Join(dirPath, sourceFile.Name())
err := utils.CopyFile(srcFilePath, destFilePath) err := utils.CopyEmbeddedFileToDisk(srcFilePath, destFilePath)
if err != nil { if err != nil {
log.Fatalf("Error copying file: %v", err) log.Fatalf("Error copying file: %v", err)
} }

View File

@@ -7,7 +7,9 @@ import (
"github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/database"
postgresMigrate "github.com/golang-migrate/migrate/v4/database/postgres" postgresMigrate "github.com/golang-migrate/migrate/v4/database/postgres"
sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3" sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/stonith404/pocket-id/backend/internal/common" "github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/resources"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
@@ -42,20 +44,31 @@ func newDatabase() (db *gorm.DB) {
} }
// Run migrations // Run migrations
m, err := migrate.NewWithDatabaseInstance( if err := migrateDatabase(driver); err != nil {
"file://migrations/"+string(common.EnvConfig.DbProvider), log.Fatalf("failed to run migrations: %v", err)
"pocket-id", driver, }
)
return db
}
func migrateDatabase(driver database.Driver) error {
// Use the embedded migrations
source, err := iofs.New(resources.FS, "migrations/"+string(common.EnvConfig.DbProvider))
if err != nil { if err != nil {
log.Fatalf("failed to create migration instance: %v", err) return fmt.Errorf("failed to create embedded migration source: %v", err)
}
m, err := migrate.NewWithInstance("iofs", source, "pocket-id", driver)
if err != nil {
return fmt.Errorf("failed to create migration instance: %v", err)
} }
err = m.Up() err = m.Up()
if err != nil && !errors.Is(err, migrate.ErrNoChange) { if err != nil && !errors.Is(err, migrate.ErrNoChange) {
log.Fatalf("failed to apply migrations: %v", err) return fmt.Errorf("failed to apply migrations: %v", err)
} }
return db return nil
} }
func connectDatabase() (db *gorm.DB, err error) { func connectDatabase() (db *gorm.DB, err error) {

View File

@@ -2,7 +2,6 @@ package bootstrap
import ( import (
"log" "log"
"os"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -29,8 +28,7 @@ func initRouter(db *gorm.DB, appConfigService *service.AppConfigService) {
r.Use(gin.Logger()) r.Use(gin.Logger())
// Initialize services // Initialize services
templateDir := os.DirFS(common.EnvConfig.EmailTemplatesPath) emailService, err := service.NewEmailService(appConfigService, db)
emailService, err := service.NewEmailService(appConfigService, db, templateDir)
if err != nil { if err != nil {
log.Fatalf("Unable to create email service: %s", err) log.Fatalf("Unable to create email service: %s", err)
} }

View File

@@ -22,7 +22,6 @@ type EnvConfigSchema struct {
UploadPath string `env:"UPLOAD_PATH"` UploadPath string `env:"UPLOAD_PATH"`
Port string `env:"BACKEND_PORT"` Port string `env:"BACKEND_PORT"`
Host string `env:"HOST"` Host string `env:"HOST"`
EmailTemplatesPath string `env:"EMAIL_TEMPLATES_PATH"`
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"` MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"` GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
} }
@@ -36,7 +35,6 @@ var EnvConfig = &EnvConfigSchema{
AppURL: "http://localhost", AppURL: "http://localhost",
Port: "8080", Port: "8080",
Host: "localhost", Host: "localhost",
EmailTemplatesPath: "./email-templates",
MaxMindLicenseKey: "", MaxMindLicenseKey: "",
GeoLiteDBPath: "data/GeoLite2-City.mmdb", GeoLiteDBPath: "data/GeoLite2-City.mmdb",
} }

View File

@@ -10,7 +10,6 @@ import (
"github.com/stonith404/pocket-id/backend/internal/utils/email" "github.com/stonith404/pocket-id/backend/internal/utils/email"
"gorm.io/gorm" "gorm.io/gorm"
htemplate "html/template" htemplate "html/template"
"io/fs"
"mime/multipart" "mime/multipart"
"mime/quotedprintable" "mime/quotedprintable"
"net" "net"
@@ -26,13 +25,13 @@ type EmailService struct {
textTemplates map[string]*ttemplate.Template textTemplates map[string]*ttemplate.Template
} }
func NewEmailService(appConfigService *AppConfigService, db *gorm.DB, templateDir fs.FS) (*EmailService, error) { func NewEmailService(appConfigService *AppConfigService, db *gorm.DB) (*EmailService, error) {
htmlTemplates, err := email.PrepareHTMLTemplates(templateDir, emailTemplatesPaths) htmlTemplates, err := email.PrepareHTMLTemplates(emailTemplatesPaths)
if err != nil { if err != nil {
return nil, fmt.Errorf("prepare html templates: %w", err) return nil, fmt.Errorf("prepare html templates: %w", err)
} }
textTemplates, err := email.PrepareTextTemplates(templateDir, emailTemplatesPaths) textTemplates, err := email.PrepareTextTemplates(emailTemplatesPaths)
if err != nil { if err != nil {
return nil, fmt.Errorf("prepare html templates: %w", err) return nil, fmt.Errorf("prepare html templates: %w", err)
} }

View File

@@ -7,8 +7,10 @@ import (
"fmt" "fmt"
"github.com/fxamacker/cbor/v2" "github.com/fxamacker/cbor/v2"
"github.com/stonith404/pocket-id/backend/internal/model/types" "github.com/stonith404/pocket-id/backend/internal/model/types"
"github.com/stonith404/pocket-id/backend/resources"
"log" "log"
"os" "os"
"path/filepath"
"time" "time"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
@@ -245,11 +247,21 @@ func (s *TestService) ResetApplicationImages() error {
return err return err
} }
if err := utils.CopyDirectory("./images", common.EnvConfig.UploadPath+"/application-images"); err != nil { files, err := resources.FS.ReadDir("images")
log.Printf("Error copying directory: %v", err) if err != nil {
return err return err
} }
for _, file := range files {
srcFilePath := filepath.Join("images", file.Name())
destFilePath := filepath.Join(common.EnvConfig.UploadPath, "application-images", file.Name())
err := utils.CopyEmbeddedFileToDisk(srcFilePath, destFilePath)
if err != nil {
return err
}
}
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package email
import ( import (
"fmt" "fmt"
"github.com/stonith404/pocket-id/backend/resources"
htemplate "html/template" htemplate "html/template"
"io/fs" "io/fs"
"path" "path"
@@ -35,36 +36,37 @@ type pareseable[V any] interface {
ParseFS(fs.FS, ...string) (V, error) ParseFS(fs.FS, ...string) (V, error)
} }
func prepareTemplate[V pareseable[V]](template string, rootTemplate clonable[V], templateDir fs.FS, suffix string) (V, error) { func prepareTemplate[V pareseable[V]](templateFS fs.FS, template string, rootTemplate clonable[V], suffix string) (V, error) {
tmpl, err := rootTemplate.Clone() tmpl, err := rootTemplate.Clone()
if err != nil { if err != nil {
return *new(V), fmt.Errorf("clone root html template: %w", err) return *new(V), fmt.Errorf("clone root template: %w", err)
} }
filename := fmt.Sprintf("%s%s", template, suffix) filename := fmt.Sprintf("%s%s", template, suffix)
_, err = tmpl.ParseFS(templateDir, filename) templatePath := path.Join("email-templates", filename)
_, err = tmpl.ParseFS(templateFS, templatePath)
if err != nil { if err != nil {
return *new(V), fmt.Errorf("parsing html template '%s': %w", template, err) return *new(V), fmt.Errorf("parsing template '%s': %w", template, err)
} }
return tmpl, nil return tmpl, nil
} }
func PrepareTextTemplates(templateDir fs.FS, templates []string) (map[string]*ttemplate.Template, error) { func PrepareTextTemplates(templates []string) (map[string]*ttemplate.Template, error) {
components := path.Join(templateComponentsDir, "*_text.tmpl") components := path.Join("email-templates", "components", "*_text.tmpl")
rootTmpl, err := ttemplate.ParseFS(templateDir, components) rootTmpl, err := ttemplate.ParseFS(resources.FS, components)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse templates '%s': %w", components, err) return nil, fmt.Errorf("unable to parse templates '%s': %w", components, err)
} }
var textTemplates = make(map[string]*ttemplate.Template, len(templates)) textTemplates := make(map[string]*ttemplate.Template, len(templates))
for _, tmpl := range templates { for _, tmpl := range templates {
rootTmplClone, err := rootTmpl.Clone() rootTmplClone, err := rootTmpl.Clone()
if err != nil { if err != nil {
return nil, fmt.Errorf("clone root template: %w", err) return nil, fmt.Errorf("clone root template: %w", err)
} }
textTemplates[tmpl], err = prepareTemplate[*ttemplate.Template](tmpl, rootTmplClone, templateDir, "_text.tmpl") textTemplates[tmpl], err = prepareTemplate[*ttemplate.Template](resources.FS, tmpl, rootTmplClone, "_text.tmpl")
if err != nil { if err != nil {
return nil, fmt.Errorf("parse '%s': %w", tmpl, err) return nil, fmt.Errorf("parse '%s': %w", tmpl, err)
} }
@@ -73,21 +75,21 @@ func PrepareTextTemplates(templateDir fs.FS, templates []string) (map[string]*tt
return textTemplates, nil return textTemplates, nil
} }
func PrepareHTMLTemplates(templateDir fs.FS, templates []string) (map[string]*htemplate.Template, error) { func PrepareHTMLTemplates(templates []string) (map[string]*htemplate.Template, error) {
components := path.Join(templateComponentsDir, "*_html.tmpl") components := path.Join("email-templates", "components", "*_html.tmpl")
rootTmpl, err := htemplate.ParseFS(templateDir, components) rootTmpl, err := htemplate.ParseFS(resources.FS, components)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse templates '%s': %w", components, err) return nil, fmt.Errorf("unable to parse templates '%s': %w", components, err)
} }
var htmlTemplates = make(map[string]*htemplate.Template, len(templates)) htmlTemplates := make(map[string]*htemplate.Template, len(templates))
for _, tmpl := range templates { for _, tmpl := range templates {
rootTmplClone, err := rootTmpl.Clone() rootTmplClone, err := rootTmpl.Clone()
if err != nil { if err != nil {
return nil, fmt.Errorf("clone root template: %w", err) return nil, fmt.Errorf("clone root template: %w", err)
} }
htmlTemplates[tmpl], err = prepareTemplate[*htemplate.Template](tmpl, rootTmplClone, templateDir, "_html.tmpl") htmlTemplates[tmpl], err = prepareTemplate[*htemplate.Template](resources.FS, tmpl, rootTmplClone, "_html.tmpl")
if err != nil { if err != nil {
return nil, fmt.Errorf("parse '%s': %w", tmpl, err) return nil, fmt.Errorf("parse '%s': %w", tmpl, err)
} }

View File

@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"github.com/stonith404/pocket-id/backend/resources"
"io" "io"
"mime/multipart" "mime/multipart"
"os" "os"
@@ -28,27 +29,8 @@ func GetImageMimeType(ext string) string {
} }
} }
func CopyDirectory(srcDir, destDir string) error { func CopyEmbeddedFileToDisk(srcFilePath, destFilePath string) error {
files, err := os.ReadDir(srcDir) srcFile, err := resources.FS.Open(srcFilePath)
if err != nil {
return err
}
for _, file := range files {
srcFilePath := filepath.Join(srcDir, file.Name())
destFilePath := filepath.Join(destDir, file.Name())
err := CopyFile(srcFilePath, destFilePath)
if err != nil {
return err
}
}
return nil
}
func CopyFile(srcFilePath, destFilePath string) error {
srcFile, err := os.Open(srcFilePath)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -0,0 +1,8 @@
package resources
import "embed"
// Embedded file systems for the project
//go:embed email-templates images migrations
var FS embed.FS

View File

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

View File

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 434 B

View File

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 434 B