chore: include static assets in binary
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
8
backend/resources/files.go
Normal 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
|
||||||
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 434 B After Width: | Height: | Size: 434 B |
|
Before Width: | Height: | Size: 434 B After Width: | Height: | Size: 434 B |