diff --git a/backend/.dockerignore b/.dockerignore similarity index 100% rename from backend/.dockerignore rename to .dockerignore diff --git a/.gitignore b/.gitignore index d6a94e8..be32e29 100644 --- a/.gitignore +++ b/.gitignore @@ -197,9 +197,8 @@ coverage/ bruno/collection.bru # Generated static assets (SvelteKit build output) -backend/cmd/server/static/ +cmd/server/static/ # Built binaries main -backend/main -backend/server +server diff --git a/backend/Dockerfile b/Dockerfile similarity index 54% rename from backend/Dockerfile rename to Dockerfile index c651f83..b3578ab 100644 --- a/backend/Dockerfile +++ b/Dockerfile @@ -6,20 +6,17 @@ WORKDIR /app # Install sqlc RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest -# Copy backend source code -COPY backend/ ./backend/ +# Copy source code +COPY . . # Download dependencies -RUN cd backend && go mod tidy +RUN go mod tidy # Generate sqlc code -RUN cd backend && sqlc generate - -# Copy templates (updated for new homepage) -COPY backend/templates ./backend/templates +RUN sqlc generate # Build the application -RUN cd backend && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ../main ./cmd/server +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server # Final stage FROM alpine:latest @@ -31,10 +28,10 @@ WORKDIR /root/ COPY --from=builder /app/main . # Copy migrations (if needed for initialization) -COPY --from=builder /app/backend/migrations ./migrations +COPY --from=builder /app/migrations ./migrations # Copy templates -COPY --from=builder /app/backend/templates ./templates +COPY --from=builder /app/templates ./templates # Expose port EXPOSE 8765 diff --git a/README.md b/README.md index 2179f23..3586324 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# 📚 Bookmann +# 📚 Shelf -A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailwind CSS (fully integrated into a single service) featuring multiple beautiful dark themes with Tokyo Night as default. +A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailwind CSS featuring multiple beautiful dark themes with Tokyo Night as default. Smart folder scanning with real-time monitoring and rich EPUB metadata extraction. ## ✨ Features @@ -35,7 +35,7 @@ A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailw docker-compose up --build ``` -3. Access the application at http://localhost:8765 +3. Access Shelf at http://localhost:8765 ### Database @@ -46,16 +46,15 @@ PostgreSQL runs on port 5432 with default credentials: ## Development -### Backend +### Development ```bash -cd backend go mod tidy go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate go run cmd/server/main.go ``` -The application uses Go HTML templates for server-side rendering with HTMX for dynamic interactions. Templates are located in `backend/templates/`. +The application uses Go HTML templates for server-side rendering with HTMX for dynamic interactions. Templates are located in `templates/`. **✨ Enhanced Features:** - **Authentication**: Login/Register forms with multiple theme support at `/login` and `/register` @@ -140,18 +139,20 @@ Use the included Bruno collection in the `bruno/` directory for testing the API: ``` . -├── backend/ -│ ├── cmd/server/ # Application entry point -│ ├── internal/ -│ │ ├── config/ # Configuration management -│ │ ├── database/ # Database connection and queries -│ │ ├── handlers/ # HTTP handlers (auth + ebooks) -│ │ └── services/ # Business logic services (ebook scanner) -│ ├── migrations/ # Database migrations -│ ├── templates/ # Go HTML templates with HTMX -│ └── sqlc.yaml # sqlc configuration +├── cmd/server/ # Application entry point +├── internal/ +│ ├── config/ # Configuration management +│ ├── database/ # Database connection and queries +│ ├── handlers/ # HTTP handlers (auth + ebooks) +│ └── services/ # Business logic services (ebook scanner) +├── migrations/ # Database migrations +├── templates/ # Go HTML templates with HTMX ├── bruno/ # Bruno API testing collection -├── docker-compose.yml +├── Dockerfile # Docker build configuration +├── docker-compose.yml # Docker Compose setup +├── go.mod # Go module definition +├── go.sum # Go module checksums +├── sqlc.yaml # SQL code generation config └── README.md ``` diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go deleted file mode 100644 index 531016a..0000000 --- a/backend/cmd/server/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "bookmann/internal/config" - "bookmann/internal/database" - "bookmann/internal/handlers" - "html/template" - "io" - "log" - "net/http" - "strings" - - "github.com/go-playground/validator/v10" - jwtgo "github.com/golang-jwt/jwt" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" -) - -type TemplateRenderer struct { - templates *template.Template -} - -func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - return t.templates.ExecuteTemplate(w, name, data) -} - -// CustomValidator wraps the go-playground validator -type CustomValidator struct { - validator *validator.Validate -} - -func (cv *CustomValidator) Validate(i interface{}) error { - return cv.validator.Struct(i) -} - -func main() { - cfg := config.LoadConfig() - - pool, err := database.NewConnection(cfg.DatabaseURL()) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - defer pool.Close() - - queries := database.New(pool) - - e := echo.New() - - // Set up validator - e.Validator = &CustomValidator{validator: validator.New()} - - // Set up templates - renderer := &TemplateRenderer{ - templates: template.Must(template.ParseGlob("templates/*.html")), - } - e.Renderer = renderer - - // Middleware - e.Use(middleware.Logger()) - e.Use(middleware.Recover()) - e.Use(middleware.CORS()) - - // Auth routes (no auth required) - auth := handlers.NewAuthHandler(queries, cfg.JWTSecret) - e.POST("/api/auth/register", auth.Register) - e.POST("/api/auth/login", auth.Login) - - // JWT middleware for protected routes - jwtMiddleware := middleware.JWTWithConfig(middleware.JWTConfig{ - SigningKey: []byte(cfg.JWTSecret), - ContextKey: "user", - SuccessHandler: func(c echo.Context) { - token := c.Get("user").(*jwtgo.Token) - claims := token.Claims.(jwtgo.MapClaims) - c.Set("user_id", claims["user_id"]) - }, - }) - - // Protected routes - protected := e.Group("/api", jwtMiddleware) - protected.GET("/auth/profile", auth.GetProfile) - protected.POST("/auth/ebook-folders", auth.AddEbookFolder) - protected.GET("/auth/ebook-folders", auth.GetEbookFolders) - protected.DELETE("/auth/ebook-folders/:folderPath", auth.DeleteEbookFolder) - protected.GET("/users", auth.ListUsers) - - // Routes - handlers.SetupRoutes(protected, queries) - - // Static files - e.Static("/static", "static") - - // Page routes - e.GET("/", func(c echo.Context) error { - // Check if user has valid JWT token - tokenString := c.Request().Header.Get("Authorization") - if tokenString != "" && strings.HasPrefix(tokenString, "Bearer ") { - tokenString = strings.TrimPrefix(tokenString, "Bearer ") - token, err := jwtgo.Parse(tokenString, func(token *jwtgo.Token) (interface{}, error) { - return []byte(cfg.JWTSecret), nil - }) - if err == nil && token.Valid { - // User is authenticated, get user info and serve dashboard - claims := token.Claims.(jwtgo.MapClaims) - userID := claims["user_id"].(string) - - user, err := queries.GetUser(c.Request().Context(), pgtype.UUID{Bytes: uuid.MustParse(userID), Valid: true}) - if err == nil { - return c.Render(http.StatusOK, "dashboard.html", map[string]interface{}{ - "User": map[string]string{ - "ID": uuid.UUID(user.ID.Bytes).String(), - "Username": user.Username, - "Email": user.Email, - }, - }) - } - } - } - // User not authenticated or token invalid, serve landing page - return c.Render(http.StatusOK, "index.html", nil) - }) - e.GET("/login", func(c echo.Context) error { - return c.Render(http.StatusOK, "login.html", nil) - }) - e.GET("/register", func(c echo.Context) error { - return c.Render(http.StatusOK, "register.html", nil) - }) - - // Start server - log.Printf("Starting server on port %s", cfg.ServerPort) - e.Logger.Fatal(e.Start(":" + cfg.ServerPort)) -} diff --git a/docker-compose.yml b/docker-compose.yml index d7c054a..a19cab4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,14 @@ version: "3.8" services: db: image: postgres:15-alpine - container_name: bookmann_db + container_name: shelf_db environment: POSTGRES_DB: ebookdb POSTGRES_USER: postgres POSTGRES_PASSWORD: ${DBPASS} volumes: - postgres_data:/var/lib/postgresql/data - - ./backend/migrations:/docker-entrypoint-initdb.d + - ./migrations:/docker-entrypoint-initdb.d ports: - "5432:5432" healthcheck: @@ -21,11 +21,11 @@ services: env_file: - .env - backend: + app: build: context: . - dockerfile: ./backend/Dockerfile - container_name: bookmann + dockerfile: ./Dockerfile + container_name: shelf environment: DATABASE_HOST: db DATABASE_PORT: 5432 @@ -40,7 +40,7 @@ services: db: condition: service_healthy volumes: - - ./backend/uploads:/app/uploads + - ./uploads:/app/uploads healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8765/"] interval: 30s diff --git a/backend/go.mod b/go.mod similarity index 98% rename from backend/go.mod rename to go.mod index 80a7958..6e35106 100644 --- a/backend/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module bookmann +module shelf go 1.25 diff --git a/backend/go.sum b/go.sum similarity index 100% rename from backend/go.sum rename to go.sum diff --git a/backend/internal/config/config.go b/internal/config/config.go similarity index 100% rename from backend/internal/config/config.go rename to internal/config/config.go diff --git a/backend/internal/database/connection.go b/internal/database/connection.go similarity index 100% rename from backend/internal/database/connection.go rename to internal/database/connection.go diff --git a/backend/internal/database/db.go b/internal/database/db.go similarity index 100% rename from backend/internal/database/db.go rename to internal/database/db.go diff --git a/backend/internal/database/models.go b/internal/database/models.go similarity index 100% rename from backend/internal/database/models.go rename to internal/database/models.go diff --git a/backend/internal/database/querier.go b/internal/database/querier.go similarity index 100% rename from backend/internal/database/querier.go rename to internal/database/querier.go diff --git a/backend/internal/database/queries.sql.go b/internal/database/queries.sql.go similarity index 100% rename from backend/internal/database/queries.sql.go rename to internal/database/queries.sql.go diff --git a/backend/internal/database/queries/queries.sql b/internal/database/queries/queries.sql similarity index 100% rename from backend/internal/database/queries/queries.sql rename to internal/database/queries/queries.sql diff --git a/backend/internal/handlers/auth.go b/internal/handlers/auth.go similarity index 99% rename from backend/internal/handlers/auth.go rename to internal/handlers/auth.go index cb28537..5bf2d83 100644 --- a/backend/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -1,7 +1,7 @@ package handlers import ( - "bookmann/internal/database" + "shelf/internal/database" "fmt" "net/http" "time" diff --git a/backend/internal/handlers/ebook.go b/internal/handlers/ebook.go similarity index 99% rename from backend/internal/handlers/ebook.go rename to internal/handlers/ebook.go index 4298973..e2c0402 100644 --- a/backend/internal/handlers/ebook.go +++ b/internal/handlers/ebook.go @@ -1,8 +1,8 @@ package handlers import ( - "bookmann/internal/database" - "bookmann/internal/services" + "shelf/internal/database" + "shelf/internal/services" "context" "net/http" "strconv" diff --git a/backend/internal/services/ebook_scanner.go b/internal/services/ebook_scanner.go similarity index 99% rename from backend/internal/services/ebook_scanner.go rename to internal/services/ebook_scanner.go index 62b183e..cfc462e 100644 --- a/backend/internal/services/ebook_scanner.go +++ b/internal/services/ebook_scanner.go @@ -1,7 +1,7 @@ package services import ( - "bookmann/internal/database" + "shelf/internal/database" "context" "fmt" "io/fs" diff --git a/backend/migrations/001_create_tables.up.sql b/migrations/001_create_tables.up.sql similarity index 100% rename from backend/migrations/001_create_tables.up.sql rename to migrations/001_create_tables.up.sql diff --git a/backend/migrations/002_add_ratings.up.sql b/migrations/002_add_ratings.up.sql similarity index 100% rename from backend/migrations/002_add_ratings.up.sql rename to migrations/002_add_ratings.up.sql diff --git a/backend/migrations/003_add_ebook_metadata_fields.up.sql b/migrations/003_add_ebook_metadata_fields.up.sql similarity index 100% rename from backend/migrations/003_add_ebook_metadata_fields.up.sql rename to migrations/003_add_ebook_metadata_fields.up.sql diff --git a/backend/migrations/004_add_ebook_folder_path.up.sql b/migrations/004_add_ebook_folder_path.up.sql similarity index 100% rename from backend/migrations/004_add_ebook_folder_path.up.sql rename to migrations/004_add_ebook_folder_path.up.sql diff --git a/backend/migrations/005_create_user_ebook_folders.up.sql b/migrations/005_create_user_ebook_folders.up.sql similarity index 100% rename from backend/migrations/005_create_user_ebook_folders.up.sql rename to migrations/005_create_user_ebook_folders.up.sql diff --git a/backend/migrations/006_drop_ebook_folder_path.up.sql b/migrations/006_drop_ebook_folder_path.up.sql similarity index 100% rename from backend/migrations/006_drop_ebook_folder_path.up.sql rename to migrations/006_drop_ebook_folder_path.up.sql diff --git a/backend/sqlc.yaml b/sqlc.yaml similarity index 100% rename from backend/sqlc.yaml rename to sqlc.yaml diff --git a/backend/static/placeholder-book.svg b/static/placeholder-book.svg similarity index 100% rename from backend/static/placeholder-book.svg rename to static/placeholder-book.svg diff --git a/backend/templates/base.html b/templates/base.html similarity index 100% rename from backend/templates/base.html rename to templates/base.html diff --git a/backend/templates/dashboard.html b/templates/dashboard.html similarity index 100% rename from backend/templates/dashboard.html rename to templates/dashboard.html diff --git a/backend/templates/index.html b/templates/index.html similarity index 100% rename from backend/templates/index.html rename to templates/index.html diff --git a/backend/templates/login.html b/templates/login.html similarity index 100% rename from backend/templates/login.html rename to templates/login.html diff --git a/backend/templates/register.html b/templates/register.html similarity index 100% rename from backend/templates/register.html rename to templates/register.html