mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-11 17:28:05 +02:00
fix(backend): solve 500 errors with auto-migrations and standardized filenames
This commit is contained in:
1 parent
8ed1782de6
commit
502b76c5d6
6 files changed
+83
-36
No files matched your search
+19
-20
@@ -1,34 +1,33 @@
|
||||
# Build stage
|
||||
FROM golang:1.22-alpine AS builder
|
||||
# Multi-stage Dockerfile for FlowReader
|
||||
|
||||
# Step 1: Build the React Frontend
|
||||
FROM node:20-alpine AS web-builder
|
||||
WORKDIR /app/web
|
||||
COPY web/package*.json ./
|
||||
RUN npm install
|
||||
COPY web/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Step 2: Build the Go Backend
|
||||
FROM golang:1.24-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum* ./
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
# Copy the built frontend from Step 1
|
||||
COPY --from=web-builder /app/web/dist ./web/dist
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server
|
||||
|
||||
# Final stage
|
||||
# Step 3: Final Production Image
|
||||
FROM alpine:3.19
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install wget for healthcheck
|
||||
RUN apk add --no-cache wget ca-certificates
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=builder /server /app/server
|
||||
# Copy the static files for the Go server to serve
|
||||
COPY --from=builder /app/web/dist /app/web/dist
|
||||
# Copy migration files for auto-migration
|
||||
COPY --from=builder /app/migrations /app/migrations
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the application
|
||||
CMD ["/app/server"]
|
||||
@@ -139,6 +139,21 @@ func main() {
|
||||
})
|
||||
})
|
||||
|
||||
// Serve Static Files (Frontend)
|
||||
staticPath := "./web/dist"
|
||||
if _, err := os.Stat(staticPath); err == nil {
|
||||
fs := http.FileServer(http.Dir(staticPath))
|
||||
r.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// If the file exists, serve it, otherwise serve index.html (for SPA routing)
|
||||
path := staticPath + r.URL.Path
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
http.ServeFile(w, r, staticPath+"/index.html")
|
||||
return
|
||||
}
|
||||
fs.ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// Create server
|
||||
srv := &http.Server{
|
||||
Addr: ":" + cfg.Port,
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
module github.com/michael/flowreader
|
||||
|
||||
go 1.22
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgx/v5 v5.5.2
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/mmcdole/gofeed v1.3.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/mmcdole/gofeed v1.3.0 // indirect
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
)
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
@@ -36,26 +41,53 @@ func Connect(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
|
||||
}
|
||||
|
||||
// RunMigrations executes pending database migrations.
|
||||
// Note: For production, use golang-migrate CLI. This is for development convenience.
|
||||
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
||||
// Check if schema_migrations table exists
|
||||
// Check if users table exists
|
||||
var exists bool
|
||||
err := pool.QueryRow(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'schema_migrations'
|
||||
AND table_name = 'users'
|
||||
)
|
||||
`).Scan(&exists)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking schema_migrations: %w", err)
|
||||
return fmt.Errorf("checking users table: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
log.Println("Migrations table not found. Run 'make migrate-up' to apply migrations.")
|
||||
if exists {
|
||||
log.Println("Database already initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("Database migrations verified")
|
||||
log.Println("Initializing database and applying migrations...")
|
||||
|
||||
// Read migration files
|
||||
entries, err := os.ReadDir("./migrations")
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading migrations dir: %w", err)
|
||||
}
|
||||
|
||||
var upFiles []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".up.sql") {
|
||||
upFiles = append(upFiles, entry.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(upFiles)
|
||||
|
||||
for _, file := range upFiles {
|
||||
log.Printf("Applying migration: %s", file)
|
||||
content, err := os.ReadFile(filepath.Join("./migrations", file))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading migration %s: %w", file, err)
|
||||
}
|
||||
|
||||
if _, err := pool.Exec(ctx, string(content)); err != nil {
|
||||
return fmt.Errorf("executing migration %s: %w", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("All migrations applied successfully")
|
||||
return nil
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user