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"]
|
CMD ["node", "server.js"]
|
||||||
|
|
||||||
# Default production stage (for Dokku - runs API)
|
# Default production stage (for Dokku - runs API + Admin)
|
||||||
FROM go-base AS production
|
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
|
EXPOSE 5000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||||
CMD curl -f http://localhost:${PORT:-5000}/api/health/ || exit 1
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"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
|
// Proxy admin panel requests to Next.js server
|
||||||
setupStaticFiles(router)
|
setupAdminProxy(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupStaticFiles configures serving the admin panel static files
|
// setupAdminProxy configures reverse proxy to the Next.js admin panel
|
||||||
func setupStaticFiles(router *gin.Engine) {
|
func setupAdminProxy(router *gin.Engine) {
|
||||||
// Determine the static files directory
|
// Get admin panel URL from env, default to localhost:3000
|
||||||
// Check multiple possible locations
|
adminURL := os.Getenv("ADMIN_PANEL_URL")
|
||||||
possiblePaths := []string{
|
if adminURL == "" {
|
||||||
"admin/out", // Development: from project root
|
adminURL = "http://127.0.0.1:3000"
|
||||||
"./admin/out", // Development: relative
|
|
||||||
"/app/admin/out", // Docker: absolute path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var staticDir string
|
target, err := url.Parse(adminURL)
|
||||||
for _, path := range possiblePaths {
|
if err != nil {
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
staticDir = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if staticDir == "" {
|
|
||||||
// Admin panel not built yet, skip static file serving
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve static files at /admin/*
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
router.GET("/admin/*filepath", func(c *gin.Context) {
|
|
||||||
filePath := c.Param("filepath")
|
|
||||||
|
|
||||||
// Clean the path
|
// Handle all /admin/* requests
|
||||||
filePath = strings.TrimPrefix(filePath, "/")
|
router.Any("/admin/*filepath", func(c *gin.Context) {
|
||||||
if filePath == "" {
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
filePath = "index.html"
|
})
|
||||||
}
|
|
||||||
|
|
||||||
fullPath := filepath.Join(staticDir, filePath)
|
// Also handle /admin without trailing path
|
||||||
|
router.Any("/admin", func(c *gin.Context) {
|
||||||
// Check if file exists
|
c.Redirect(http.StatusMovedPermanently, "/admin/")
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user