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:
1 parent
152ed27200
commit
4318f8624b
31 files changed
+38
-175
No files matched your search
File renamed without changes.
+2
-3
@@ -197,9 +197,8 @@ coverage/
|
|||||||
bruno/collection.bru
|
bruno/collection.bru
|
||||||
|
|
||||||
# Generated static assets (SvelteKit build output)
|
# Generated static assets (SvelteKit build output)
|
||||||
backend/cmd/server/static/
|
cmd/server/static/
|
||||||
|
|
||||||
# Built binaries
|
# Built binaries
|
||||||
main
|
main
|
||||||
backend/main
|
server
|
||||||
backend/server
|
|
||||||
@@ -6,20 +6,17 @@ WORKDIR /app
|
|||||||
# Install sqlc
|
# Install sqlc
|
||||||
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||||
|
|
||||||
# Copy backend source code
|
# Copy source code
|
||||||
COPY backend/ ./backend/
|
COPY . .
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
RUN cd backend && go mod tidy
|
RUN go mod tidy
|
||||||
|
|
||||||
# Generate sqlc code
|
# Generate sqlc code
|
||||||
RUN cd backend && sqlc generate
|
RUN sqlc generate
|
||||||
|
|
||||||
# Copy templates (updated for new homepage)
|
|
||||||
COPY backend/templates ./backend/templates
|
|
||||||
|
|
||||||
# Build the application
|
# 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
|
# Final stage
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
@@ -31,10 +28,10 @@ WORKDIR /root/
|
|||||||
COPY --from=builder /app/main .
|
COPY --from=builder /app/main .
|
||||||
|
|
||||||
# Copy migrations (if needed for initialization)
|
# Copy migrations (if needed for initialization)
|
||||||
COPY --from=builder /app/backend/migrations ./migrations
|
COPY --from=builder /app/migrations ./migrations
|
||||||
|
|
||||||
# Copy templates
|
# Copy templates
|
||||||
COPY --from=builder /app/backend/templates ./templates
|
COPY --from=builder /app/templates ./templates
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8765
|
EXPOSE 8765
|
||||||
@@ -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
|
## ✨ Features
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailw
|
|||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Access the application at http://localhost:8765
|
3. Access Shelf at http://localhost:8765
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
@@ -46,16 +46,15 @@ PostgreSQL runs on port 5432 with default credentials:
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Backend
|
### Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate
|
go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate
|
||||||
go run cmd/server/main.go
|
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:**
|
**✨ Enhanced Features:**
|
||||||
- **Authentication**: Login/Register forms with multiple theme support at `/login` and `/register`
|
- **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
|
||||||
│ ├── cmd/server/ # Application entry point
|
├── internal/
|
||||||
│ ├── internal/
|
│ ├── config/ # Configuration management
|
||||||
│ │ ├── config/ # Configuration management
|
│ ├── database/ # Database connection and queries
|
||||||
│ │ ├── database/ # Database connection and queries
|
│ ├── handlers/ # HTTP handlers (auth + ebooks)
|
||||||
│ │ ├── handlers/ # HTTP handlers (auth + ebooks)
|
│ └── services/ # Business logic services (ebook scanner)
|
||||||
│ │ └── services/ # Business logic services (ebook scanner)
|
├── migrations/ # Database migrations
|
||||||
│ ├── migrations/ # Database migrations
|
├── templates/ # Go HTML templates with HTMX
|
||||||
│ ├── templates/ # Go HTML templates with HTMX
|
|
||||||
│ └── sqlc.yaml # sqlc configuration
|
|
||||||
├── bruno/ # Bruno API testing collection
|
├── 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
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -3,14 +3,14 @@ version: "3.8"
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: bookmann_db
|
container_name: shelf_db
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ebookdb
|
POSTGRES_DB: ebookdb
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: ${DBPASS}
|
POSTGRES_PASSWORD: ${DBPASS}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./backend/migrations:/docker-entrypoint-initdb.d
|
- ./migrations:/docker-entrypoint-initdb.d
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -21,11 +21,11 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|
||||||
backend:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./backend/Dockerfile
|
dockerfile: ./Dockerfile
|
||||||
container_name: bookmann
|
container_name: shelf
|
||||||
environment:
|
environment:
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: 5432
|
DATABASE_PORT: 5432
|
||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend/uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8765/"]
|
test: ["CMD", "curl", "-f", "http://localhost:8765/"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module bookmann
|
module shelf
|
||||||
|
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
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.
File renamed without changes.
@@ -1,7 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bookmann/internal/database"
|
"shelf/internal/database"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bookmann/internal/database"
|
"shelf/internal/database"
|
||||||
"bookmann/internal/services"
|
"shelf/internal/services"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bookmann/internal/database"
|
"shelf/internal/database"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user