admin reachable on server
This commit is contained in:
42
Dockerfile
42
Dockerfile
@@ -101,9 +101,45 @@ ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
# Default production stage (for Dokku - runs API)
|
||||
FROM go-base AS production
|
||||
# Default production stage (for Dokku - runs API + Admin)
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache ca-certificates tzdata curl
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy Go binaries
|
||||
COPY --from=builder /app/api /app/api
|
||||
COPY --from=builder /app/worker /app/worker
|
||||
|
||||
# Copy templates directory
|
||||
COPY --from=builder /app/templates /app/templates
|
||||
|
||||
# Copy migrations and seeds
|
||||
COPY --from=builder /app/migrations /app/migrations
|
||||
COPY --from=builder /app/seeds /app/seeds
|
||||
|
||||
# Copy admin panel standalone build
|
||||
COPY --from=admin-builder /app/.next/standalone /app/admin
|
||||
COPY --from=admin-builder /app/.next/static /app/admin/.next/static
|
||||
COPY --from=admin-builder /app/public /app/admin/public
|
||||
|
||||
# Copy start script
|
||||
COPY start.sh /app/start.sh
|
||||
RUN chmod +x /app/start.sh
|
||||
|
||||
# Create uploads directory
|
||||
RUN mkdir -p /app/uploads && chown -R app:app /app
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${PORT:-5000}/api/health/ || exit 1
|
||||
CMD ["/app/api"]
|
||||
|
||||
CMD ["/app/start.sh"]
|
||||
|
||||
@@ -2,9 +2,9 @@ package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@@ -252,75 +252,32 @@ func SetupRoutes(router *gin.Engine, db *gorm.DB, cfg *config.Config, deps *Depe
|
||||
}
|
||||
}
|
||||
|
||||
// Serve admin panel static files
|
||||
setupStaticFiles(router)
|
||||
// Proxy admin panel requests to Next.js server
|
||||
setupAdminProxy(router)
|
||||
}
|
||||
|
||||
// setupStaticFiles configures serving the admin panel static files
|
||||
func setupStaticFiles(router *gin.Engine) {
|
||||
// Determine the static files directory
|
||||
// Check multiple possible locations
|
||||
possiblePaths := []string{
|
||||
"admin/out", // Development: from project root
|
||||
"./admin/out", // Development: relative
|
||||
"/app/admin/out", // Docker: absolute path
|
||||
// setupAdminProxy configures reverse proxy to the Next.js admin panel
|
||||
func setupAdminProxy(router *gin.Engine) {
|
||||
// Get admin panel URL from env, default to localhost:3000
|
||||
adminURL := os.Getenv("ADMIN_PANEL_URL")
|
||||
if adminURL == "" {
|
||||
adminURL = "http://127.0.0.1:3000"
|
||||
}
|
||||
|
||||
var staticDir string
|
||||
for _, path := range possiblePaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
staticDir = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if staticDir == "" {
|
||||
// Admin panel not built yet, skip static file serving
|
||||
target, err := url.Parse(adminURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Serve static files at /admin/*
|
||||
router.GET("/admin/*filepath", func(c *gin.Context) {
|
||||
filePath := c.Param("filepath")
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
// Clean the path
|
||||
filePath = strings.TrimPrefix(filePath, "/")
|
||||
if filePath == "" {
|
||||
filePath = "index.html"
|
||||
}
|
||||
// Handle all /admin/* requests
|
||||
router.Any("/admin/*filepath", func(c *gin.Context) {
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
fullPath := filepath.Join(staticDir, filePath)
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
// For SPA routing, serve index.html for non-existent paths
|
||||
// But only if it's not a file request (no extension or .html)
|
||||
ext := filepath.Ext(filePath)
|
||||
if ext == "" || ext == ".html" {
|
||||
// Try to find the specific page's index.html
|
||||
pagePath := filepath.Join(staticDir, filePath, "index.html")
|
||||
if _, err := os.Stat(pagePath); err == nil {
|
||||
c.File(pagePath)
|
||||
return
|
||||
}
|
||||
// Fall back to root index.html
|
||||
c.File(filepath.Join(staticDir, "index.html"))
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// If it's a directory, serve index.html
|
||||
info, _ := os.Stat(fullPath)
|
||||
if info.IsDir() {
|
||||
indexPath := filepath.Join(fullPath, "index.html")
|
||||
if _, err := os.Stat(indexPath); err == nil {
|
||||
c.File(indexPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.File(fullPath)
|
||||
// Also handle /admin without trailing path
|
||||
router.Any("/admin", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/admin/")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user