refactor: restructure project from bookmann to shelf

- Rename project from 'bookmann' to 'shelf'
- Move all backend/ contents to root level (flatten structure)
- Update Go module name from 'bookmann' to 'shelf'
- Update all import paths to use new 'shelf' module
- Update Dockerfile to work without backend/ subdirectory
- Update docker-compose.yml to use new structure and rename containers
- Update .gitignore for new file paths
- Update README.md with new project name and structure
- Regenerate database code with new module imports
This commit is contained in:
john-okeefe committed 2026-01-23 09:08:04 -05:00
1 parent 152ed27200
commit 4318f8624b
31 files changed
+38 -175

No files matched your search

File renamed without changes.
+2 -3
View File
@@ -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
+7 -10
View File
@@ -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
+18 -17
View File
@@ -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
```
-134
View File
@@ -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))
}
+6 -6
View File
@@ -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
+1 -1
View File
@@ -1,4 +1,4 @@
module bookmann
module shelf
go 1.25
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
package handlers
import (
"bookmann/internal/database"
"shelf/internal/database"
"fmt"
"net/http"
"time"
@@ -1,8 +1,8 @@
package handlers
import (
"bookmann/internal/database"
"bookmann/internal/services"
"shelf/internal/database"
"shelf/internal/services"
"context"
"net/http"
"strconv"
@@ -1,7 +1,7 @@
package services
import (
"bookmann/internal/database"
"shelf/internal/database"
"context"
"fmt"
"io/fs"
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.