Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9448ed1
feat(domain): Add Username field to JWT Claims struct
FinnTheHero Aug 24, 2025
5301403
refactor(token): Update token generation functions to accept *domain.…
FinnTheHero Aug 24, 2025
7b58470
refactor(token): Adapt token generation call sites to new API
FinnTheHero Aug 24, 2025
483bcb9
feat(handler): Enhance ValidateToken response with username and role,…
FinnTheHero Aug 24, 2025
1db0216
feat(common): Add robust domain parsing for env variables
FinnTheHero Sep 2, 2025
b841aa0
feat(db): Implement exponential backoff for database connection
FinnTheHero Sep 2, 2025
ad46ed0
refactor(gin): Centralize GIN_MODE setup in init functions
FinnTheHero Sep 2, 2025
cc31423
feat(middleware): Introduce granular token middleware components
FinnTheHero Sep 2, 2025
5aff020
feat(middleware): Add user loading and access token refresh
FinnTheHero Sep 2, 2025
66d1337
refactor(middleware): Remove deprecated token middleware files
FinnTheHero Sep 2, 2025
d470631
refactor(api): Update token handler and route definitions
FinnTheHero Sep 2, 2025
d3ed642
chore: Remove Procfile
FinnTheHero Sep 3, 2025
d0e63a8
feat: Add .dockerignore for optimized Docker builds
FinnTheHero Sep 3, 2025
c15bf61
feat: Implement Dockerfile for web service
FinnTheHero Sep 3, 2025
06495d0
feat: Implement Dockerfile for worker service
FinnTheHero Sep 3, 2025
d1e6a17
refactor: Prefix all API route groups with `/api/`
FinnTheHero Sep 12, 2025
f7805e4
feat: Add basic health check endpoint
FinnTheHero Sep 12, 2025
7b35501
Upgrades Go version and refines module dependencies
FinnTheHero Sep 19, 2025
7c84a2e
Converts timestamp fields to time.Time
FinnTheHero Sep 19, 2025
8ae466a
Migrates database to PostgreSQL
FinnTheHero Sep 19, 2025
718eead
feat(db): Add users table and essential indexes to schema
FinnTheHero Sep 19, 2025
583e847
refactor(domain): Use time.Time for User timestamps
FinnTheHero Sep 19, 2025
6782ac6
feat(users): Migrate user data access from Firestore to PostgreSQL
FinnTheHero Sep 19, 2025
f13d6d0
refactor(sql): Simplify static SQL queries using string literals
FinnTheHero Sep 19, 2025
8427540
Simplifies database client initialization
FinnTheHero Sep 23, 2025
eaf993e
Improves database client configuration and structure
FinnTheHero Sep 23, 2025
10a807e
Refactor service layer and database interaction
FinnTheHero Sep 23, 2025
dfa72dc
feat: Remove Firestore dependencies
FinnTheHero Sep 24, 2025
0eff121
refactor(database): Update client import path
FinnTheHero Sep 24, 2025
00d8488
refactor(database): Enhance PostgreSQL partitioned table schema for c…
FinnTheHero Sep 24, 2025
f3ed2f7
feat(database): Add batch chapter upload functionality
FinnTheHero Sep 24, 2025
c54533f
feat(chapters): Migrate to PostgreSQL seek-based pagination
FinnTheHero Sep 24, 2025
1363065
feat(novels): Integrate batch chapter upload into EPUB processing
FinnTheHero Sep 24, 2025
16e79e4
chore(go): Update for-loop idiom
FinnTheHero Sep 24, 2025
e9afc43
chore(build): Remove .air.toml hot-reloading configuration
FinnTheHero Sep 24, 2025
a4c1145
Establishes initial database schema
FinnTheHero Sep 24, 2025
fe236ec
Adds SQL migrations and refines chapter/novel APIs
FinnTheHero Sep 24, 2025
2bdc21b
Defines domain types for novel and chapter actions
FinnTheHero Sep 24, 2025
95a27e7
Refactors resource ID handling and API routes
FinnTheHero Sep 24, 2025
08e92f6
Improves service API consistency and data handling
FinnTheHero Sep 24, 2025
5b3a4c9
Merge branch 'master' into development
FinnTheHero Sep 24, 2025
4e07d4c
docs: Update README with latest project details and API documentation
FinnTheHero Sep 24, 2025
29351ce
Upgrades Go builder image to 1.25
FinnTheHero Sep 25, 2025
33b66e2
Sets proper ownership for Dockerfile assets
FinnTheHero Sep 25, 2025
92549f6
refactor: Remove commented out client middleware
FinnTheHero Sep 29, 2025
94c403d
feat: Standardize validate token route path
FinnTheHero Sep 29, 2025
20a07ff
refactor: Rename 'role' to 'type' in validate token response
FinnTheHero Sep 29, 2025
1f7946d
feat(migrations): Add foreign key to chapters table
FinnTheHero Oct 3, 2025
e8e683f
feat(migrations): Add user type default and check constraint
FinnTheHero Oct 3, 2025
16fc815
feat(migrations): Create progress tracking table and indexes
FinnTheHero Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 0 additions & 61 deletions .air.toml

This file was deleted.

12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.git
.gitignore

.github

Dockerfile.web
Dockerfile.worker
README.md

.air.toml

tmp
35 changes: 35 additions & 0 deletions Dockerfile.web
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM golang:1.25-alpine AS builder

WORKDIR /app
COPY . .
RUN go build -o web ./api/cmd/web

FROM alpine:latest

# Install ca-certificates for HTTPS requests and security updates
RUN apk --no-cache add ca-certificates && \
apk upgrade

# Create a non-root user for security
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup

# Create app directory
WORKDIR /app

# Copy binary with proper ownership and permissions
COPY --from=builder --chown=appuser:appgroup /app/web ./web
COPY --chown=appuser:appgroup .env .env
COPY --chown=appuser:appgroup migrations/ ./migrations/

RUN chmod +x ./web

# Switch to non-root user
USER appuser

# Health check for monitoring
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# Run the application
CMD ["./web"]
31 changes: 31 additions & 0 deletions Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM golang:1.25-alpine AS builder

WORKDIR /app
COPY . .
RUN go build -o worker ./api/cmd/worker

FROM alpine:latest

# Install ca-certificates for HTTPS requests and security updates
RUN apk --no-cache add ca-certificates && \
apk upgrade

# Create a non-root user for security
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup

# Create app directory
WORKDIR /app

# Copy binary with proper ownership and permissions
COPY --from=builder --chown=appuser:appgroup /app/worker ./worker
COPY --chown=appuser:appgroup .env .env
COPY --chown=appuser:appgroup migrations/ ./migrations/

RUN chmod +x ./worker

# Switch to non-root user
USER appuser

# Run the application
CMD ["./worker"]
2 changes: 0 additions & 2 deletions Procfile

This file was deleted.

64 changes: 40 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,67 @@ Backend for Codex - novel reading platform.

## Details

Codex-Backend is built in `GoLang`, using `Gin` for server and ~AWS-dynamoDB~ firestore (moving to Heroku Postgres) for database.
Codex-Backend is built in `GoLang`, using `Gin` for server and ~AWS-dynamoDB~ ~firestore (moving to Heroku Postgres)~ PostgreSQL for database.

It is deployed on `Heroku` (thats why the code is in api directory).
~It is deployed on `Heroku` (thats why the code is in api directory).~

air config is outdated and not recommended. use [Run](Run guide instead)
Deployed on personal server in Docker.

## Run
run server:

```bash
go run api/cmd/web/main.go
GIN_MODE=debug go run api/cmd/web/main.go
```

```bash
go run api/cmd/worker/main.go
GIN_MODE=debug go run api/cmd/worker/main.go
```

Both are needed

## Endpoints

3 Groups of endpoints: Client, Manage and User.
5 Groups of endpoints: Client, Manage, User, Validate and Health.

- Client is responsible for basic GET requests.
- Manage is responsible for Upload/Modification operations.
- Manage is responsible for Upload/Modification/Delete operations.
- User is responsible for user authentication, authorization and Registration (Delete is not yet implemented).
- Validate is responsible for validating user tokens.
- Health is responsible for checking the health of the server.

### Client: base path followed by request path
- `/all` - Get all novels
- `/:novel` - Get a novel by id
- `/:novel/:chapter` - Get chapter from novel using both ids
- `/:novel/all` - Get all chapters from novel using id
- `/:novel/chapter` - Get cursor paginated chapters from novel using id
### Client: `/api` followed by request path
- GET `/all` - Get all novels
- GET `/:novel` - Get a novel by id
- GET `/:novel/:chapter` - Get chapter from novel using both ids
- GET `/:novel/all` - Get all chapters from novel using id
- GET `/:novel/chapter` - Get cursor paginated chapters from novel using id

Options: limit (max 100), cursor (chapter index (integer)) and sort ("asc" || "desc").
Pagination querries:
- `?limit=100` - Limit the number of results returned, Max = 200, Min = 1
- `?cursor=""` - Encoded offset, will be handled automatically
- `?sort="asc"` - Sort order, asc or desc

Defaults: limit=100, cursor=0, sort="desc"
### Manage: `/api/manage` followed by request path
- POST `/epub` Create Novel/Chapters from epub file.

### Manage: `/manage` followed by request path
- `/upload` - Upload novel
- `/:novel` - Update novel
- `/:novel/:chapter` - Update chapter
- POST `/create/novel` Create Novel
- POST `/create/chapter` Create Chapter

### User: `/user` followed by request path
- `/validate` - Validate user token
- `/login` - Login user
- `/register` - Register user
- `/logout` - Logout user
- PUT `/update/novel` - Update novel
- PUT `/update/chapter` - Update chapter

- DELETE `/delete/novel` - Delete novel
- DELETE `/delete/chapter` - Delete chapter

### User: `/api/user` followed by request path
- POST `/login` - Login user
- POST `/logout` - Logout user
- POST `/register` - Register user

### Validate: `/api/validate` followed by request path
- GET `/` - Validate user token

## Health: `/health` followed by request path
- GET `/` - Check health of server
For now this does nothing, but will be used to check the health of Docker image.
20 changes: 16 additions & 4 deletions api/cmd/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@ package main

import (
cmn "Codex-Backend/api/common"
db "Codex-Backend/api/internal/database"
firestore_server "Codex-Backend/api/internal/server"
"os"
"context"
"fmt"

_ "github.com/heroku/x/hmetrics/onload"
"github.com/gin-gonic/gin"
)

func init() {
if mode := os.Getenv("GIN_MODE"); mode == "debug" {
cmn.LoadEnvVariables()
cmn.LoadEnvVariables()

mode := cmn.GetEnvVariable("GIN_MODE")
gin.SetMode(mode)

ctx := context.Background()
client, err := db.GetClient(ctx)
if err != nil {
panic(fmt.Sprintf("db new client: %v", err))
}
if err := client.EnsureSchema(ctx); err != nil {
panic(fmt.Sprintf("schema ensure failed: %v", err))
}
}

Expand Down
9 changes: 6 additions & 3 deletions api/cmd/worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
"os/signal"
"syscall"
"time"

"github.com/gin-gonic/gin"
)

func init() {
if mode := os.Getenv("GIN_MODE"); mode == "debug" {
cmn.LoadEnvVariables()
}
cmn.LoadEnvVariables()

mode := cmn.GetEnvVariable("GIN_MODE")
gin.SetMode(mode)
}

func main() {
Expand Down
15 changes: 14 additions & 1 deletion api/common/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,20 @@ func GetDomains(v string) []string {
log.Fatal(&Error{Err: errors.New("Environmental Variable " + v + " Not Found"), Status: http.StatusNotFound})
}

domains := []string{}

result := strings.Split(env_variable, ",")

return result
if len(result) == 0 {
return []string{"*"}
}

for _, domain := range result {
cleaned := strings.TrimSpace(domain)
if cleaned != "" {
domains = append(domains, cleaned)
}
}

return domains
}
59 changes: 56 additions & 3 deletions api/common/river/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package queue
import (
"Codex-Backend/api/internal/service/worker"
"context"
"fmt"
"log"
"log/slog"
"math"
"os"
"sync"
"time"

"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
Expand All @@ -19,10 +22,60 @@ var (
riverOnce sync.Once
)

// func InitializeRiverClient(ctx context.Context, workers *river.Workers) *river.Client[pgx.Tx] {
// dbPool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
// if err != nil {
// panic(err)
// }

// logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
// Level: slog.LevelInfo,
// }))

// riverClient, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{
// Logger: logger,
// Queues: map[string]river.QueueConfig{
// river.QueueDefault: {MaxWorkers: 10},
// },
// MaxAttempts: 3,
// Workers: workers,
// })
// if err != nil {
// panic(err)
// }

// return riverClient
// }

func InitializeRiverClient(ctx context.Context, workers *river.Workers) *river.Client[pgx.Tx] {
dbPool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
var dbPool *pgxpool.Pool
var err error

// Retry connection with exponential backoff
maxRetries := 10
for i := range maxRetries {
dbPool, err = pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err == nil {
// Test the connection
if pingErr := dbPool.Ping(ctx); pingErr == nil {
log.Printf("Successfully connected to database on attempt %d", i+1)
break
} else {
log.Printf("Database ping failed on attempt %d: %v", i+1, pingErr)
err = pingErr
}
} else {
log.Printf("Failed to create connection pool on attempt %d: %v", i+1, err)
}

if i == maxRetries-1 {
panic(fmt.Sprintf("Failed to connect to database after %d attempts: %v", maxRetries, err))
}

// Wait before retrying (exponential backoff)
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
log.Printf("Retrying database connection in %v...", waitTime)
time.Sleep(waitTime)
}

logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Expand Down
Loading
Loading