mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-19 21:28:10 +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
|
# Multi-stage Dockerfile for FlowReader
|
||||||
FROM golang:1.22-alpine AS builder
|
|
||||||
|
|
||||||
|
# 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
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
|
COPY go.mod go.sum ./
|
||||||
# Copy go mod files
|
|
||||||
COPY go.mod go.sum* ./
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
# Copy the built frontend from Step 1
|
||||||
# Build the application
|
COPY --from=web-builder /app/web/dist ./web/dist
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server
|
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
|
FROM alpine:3.19
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install wget for healthcheck
|
|
||||||
RUN apk add --no-cache wget ca-certificates
|
RUN apk add --no-cache wget ca-certificates
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /server /app/server
|
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
|
EXPOSE 8080
|
||||||
|
|
||||||
# Run the application
|
|
||||||
CMD ["/app/server"]
|
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
|
// Create server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: ":" + cfg.Port,
|
Addr: ":" + cfg.Port,
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
module github.com/michael/flowreader
|
module github.com/michael/flowreader
|
||||||
|
|
||||||
go 1.22
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.11
|
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/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 (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // 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/css v1.0.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // 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/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
golang.org/x/crypto v0.24.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
)
|
)
|
||||||
@@ -6,6 +6,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"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.
|
// 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 {
|
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
||||||
// Check if schema_migrations table exists
|
// Check if users table exists
|
||||||
var exists bool
|
var exists bool
|
||||||
err := pool.QueryRow(ctx, `
|
err := pool.QueryRow(ctx, `
|
||||||
SELECT EXISTS (
|
SELECT EXISTS (
|
||||||
SELECT FROM information_schema.tables
|
SELECT FROM information_schema.tables
|
||||||
WHERE table_schema = 'public'
|
WHERE table_schema = 'public'
|
||||||
AND table_name = 'schema_migrations'
|
AND table_name = 'users'
|
||||||
)
|
)
|
||||||
`).Scan(&exists)
|
`).Scan(&exists)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking schema_migrations: %w", err)
|
return fmt.Errorf("checking users table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if exists {
|
||||||
log.Println("Migrations table not found. Run 'make migrate-up' to apply migrations.")
|
log.Println("Database already initialized")
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user