mirror of
https://github.com/sstent/garminsync-go.git
synced 2025-12-06 08:01:52 +00:00
working build - with ui
This commit is contained in:
11
Dockerfile
11
Dockerfile
@@ -24,8 +24,8 @@ RUN CGO_ENABLED=1 GOOS=linux go build -o garminsync .
|
||||
# Runtime stage
|
||||
FROM alpine:3.18
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
# Install runtime dependencies (wget needed for healthcheck)
|
||||
RUN apk add --no-cache ca-certificates tzdata wget sqlite
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
@@ -33,14 +33,13 @@ WORKDIR /app
|
||||
# Copy binary from builder
|
||||
COPY --from=builder /app/garminsync /app/garminsync
|
||||
|
||||
# Copy templates
|
||||
COPY internal/web/templates ./internal/web/templates
|
||||
# Copy web directory (frontend assets)
|
||||
COPY web ./web
|
||||
|
||||
# Set timezone and environment
|
||||
ENV TZ=UTC \
|
||||
DATA_DIR=/data \
|
||||
DB_PATH=/data/garmin.db \
|
||||
TEMPLATE_DIR=/app/internal/web/templates
|
||||
DB_PATH=/data/garmin.db
|
||||
|
||||
# Create data volume and set permissions
|
||||
RUN mkdir /data && chown nobody:nobody /data
|
||||
|
||||
@@ -6,12 +6,8 @@ services:
|
||||
container_name: garminsync
|
||||
ports:
|
||||
- "8888:8888"
|
||||
environment:
|
||||
- GARMIN_EMAIL=${GARMIN_EMAIL}
|
||||
- GARMIN_PASSWORD=${GARMIN_PASSWORD}
|
||||
- DATA_DIR=/data
|
||||
- DB_PATH=/data/garmin.db
|
||||
- TEMPLATE_DIR=/app/internal/web/templates
|
||||
env_file:
|
||||
- .env # Use the root .env file
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./internal/web/templates:/app/internal/web/templates
|
||||
|
||||
@@ -2,6 +2,7 @@ package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -15,7 +16,6 @@ type WebHandler struct {
|
||||
db *database.SQLiteDB
|
||||
syncer *sync.SyncService
|
||||
garmin *garmin.Client
|
||||
templates map[string]interface{} // Placeholder for template handling
|
||||
}
|
||||
|
||||
func NewWebHandler(db *database.SQLiteDB, syncer *sync.SyncService, garmin *garmin.Client) *WebHandler {
|
||||
@@ -23,30 +23,22 @@ func NewWebHandler(db *database.SQLiteDB, syncer *sync.SyncService, garmin *garm
|
||||
db: db,
|
||||
syncer: syncer,
|
||||
garmin: garmin,
|
||||
templates: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebHandler) LoadTemplates(templateDir string) error {
|
||||
// For now, just return nil - templates will be handled later
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *WebHandler) RegisterRoutes(router *gin.Engine) {
|
||||
router.GET("/", h.Index)
|
||||
func (h *WebHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/stats", h.GetStats)
|
||||
router.GET("/activities", h.ActivityList)
|
||||
router.GET("/activities/:id", h.ActivityDetail)
|
||||
router.POST("/sync", h.Sync)
|
||||
}
|
||||
|
||||
func (h *WebHandler) Index(c *gin.Context) {
|
||||
func (h *WebHandler) GetStats(c *gin.Context) {
|
||||
stats, err := h.db.GetStats()
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get stats"})
|
||||
return
|
||||
}
|
||||
|
||||
// Placeholder for template rendering
|
||||
c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
@@ -60,7 +52,7 @@ func (h *WebHandler) ActivityList(c *gin.Context) {
|
||||
|
||||
activities, err := h.db.GetActivities(limit, offset)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get activities"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,13 +62,13 @@ func (h *WebHandler) ActivityList(c *gin.Context) {
|
||||
func (h *WebHandler) ActivityDetail(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid activity ID"})
|
||||
return
|
||||
}
|
||||
|
||||
activity, err := h.db.GetActivity(id)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Activity not found"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,11 +76,12 @@ func (h *WebHandler) ActivityDetail(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *WebHandler) Sync(c *gin.Context) {
|
||||
err := h.syncer.Sync(context.Background())
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
err := h.syncer.Sync(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("Sync error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "sync_started", "message": "Sync started in background"})
|
||||
}
|
||||
|
||||
41
main.go
41
main.go
@@ -84,14 +84,7 @@ func (app *App) init() error {
|
||||
|
||||
// Setup HTTP server
|
||||
webHandler := web.NewWebHandler(app.db, app.syncService, app.garmin)
|
||||
templateDir := os.Getenv("TEMPLATE_DIR")
|
||||
if templateDir == "" {
|
||||
templateDir = "./internal/web/templates"
|
||||
}
|
||||
if err := webHandler.LoadTemplates(templateDir); err != nil {
|
||||
return fmt.Errorf("failed to load templates: %w", err)
|
||||
}
|
||||
|
||||
// We've removed template loading since we're using static frontend
|
||||
app.server = &http.Server{
|
||||
Addr: ":8888",
|
||||
Handler: app.setupRoutes(webHandler),
|
||||
@@ -183,13 +176,39 @@ func initDatabase() (*database.SQLiteDB, error) {
|
||||
func (app *App) setupRoutes(webHandler *web.WebHandler) http.Handler {
|
||||
router := gin.Default()
|
||||
|
||||
// Add middleware
|
||||
router.Use(gin.Logger()) // Log all requests
|
||||
router.Use(gin.Recovery()) // Recover from any panics
|
||||
|
||||
// Enable CORS for development
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Content-Type")
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
// Serve static files
|
||||
router.Static("/static", "./web/static")
|
||||
router.LoadHTMLFiles("web/index.html")
|
||||
|
||||
// API routes
|
||||
api := router.Group("/api")
|
||||
webHandler.RegisterRoutes(api)
|
||||
|
||||
// Serve main page
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.HTML(200, "index.html", nil)
|
||||
})
|
||||
|
||||
// Health check
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "OK")
|
||||
})
|
||||
|
||||
// Register web routes
|
||||
webHandler.RegisterRoutes(router)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user