Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

HaditsSoft API

Backend REST API for HaditsSoft, a digital hadith reader and search platform. It serves 14 major kitabs with Arabic text and Indonesian translations, provides full-text search with Indonesian stemming, and powers the frontend at haditssoft.github.io.


Features

  • Hadith data API β€” load hadith, chapters, books, sanad (chain), narrator biographies, scholar comments, classifications, similar hadith, and total counts.
  • Smart search β€” search within a single kitab or across multiple kitabs concurrently; uses LIKE, multi-keyword, and FTS5 with KBBI-based stemming for Indonesian queries.
  • Authentication β€” JWT access tokens (15 min) with rotating refresh tokens (7 days) including reuse detection.
  • Email verification β€” 6-digit code sign-up verification with 15-minute expiry and a 2-minute resend cooldown.
  • Forgot password β€” reset password via a 6-digit email code.
  • User data β€” bookmarks, notes, last-read position, theme, font, and search-mode preferences.
  • Admin API β€” admin authentication, user management, and full CRUD for hadith records per kitab.
  • Utility integrations β€” reCAPTCHA verification with Telegram report forwarding, and an AI assistant proxy to a local opencode server.
  • Operations β€” SQLite with WAL mode and a connection pool for concurrent reads, activity logging, and per-request query logs.

Tech Stack

Layer Technology
Language Go 1.19
Web framework Fiber v2 (Prefork enabled)
ORM GORM
Database SQLite via glebarez/sqlite (WAL mode)
Auth golang-jwt + gofiber/jwt, bcrypt
Validation go-playground/validator
Email Hermes + gomail (SMTP)
Env godotenv

Getting Started

Prerequisites

  • Go 1.19 or newer
  • A SQLite database file containing the hadith dataset (see Database note)

Setup

# 1. Clone the repository
git clone <your-repo-url>
cd <your-repo-dir>

# 2. Create your environment file from the example
cp .env.example .env

# 3. Edit .env and fill in at least:
#    ADMIN_EMAIL, ADMIN_PASSWORD, JWT_SECRET, DB_CREDENTIAL

# 4. Run the server
go run main.go

The server listens on the port set by APP_PORT (default 8081).

Build

# Build binary (Windows)
go build -o app

# Build for Linux (deployment)
set GOOS=linux && go build -ldflags "-s -w" -o app

Run tests

go test ./...

Database note

On startup the app only auto-migrates the User, Activity, BlacklistToken, and RefreshToken tables. The hadith data lives in the SQLite file referenced by DB_CREDENTIAL and must already exist with:

  • One table per kitab (e.g. ShahihBukhari, ShahihMuslim β€” see Known Kitab Names).
  • Optional FTS5 shadow tables named FTS<KitabName> (e.g. FTSSunanIbnuMajah) used for the Indonesian full-text search.
  • A KBBI table (katakunci / artikata columns) used for Indonesian word stemming.

On first boot the server also creates the initial admin user from the ADMIN_EMAIL and ADMIN_PASSWORD environment variables if no user with that email exists.


Environment Variables

All configuration is read from the environment (loaded from .env via godotenv).

Variable Required Default Description
APP_NAME no HaditsSoft Application name, used as email sender name
APP_PORT yes 8081 HTTP listen port
APP_URL no http://127.0.0.1:${APP_PORT} Public application URL
ADMIN_EMAIL yes β€” Email of the initial admin user (created on first boot)
ADMIN_PASSWORD yes β€” Password of the initial admin user (bcrypt-hashed)
DB_CREDENTIAL yes β€” SQLite connection string (e.g. haditssoft.db?_busy_timeout=5000)
JWT_SECRET yes β€” Secret used to sign JWTs; the server panics if unset
MAIL_MAILER no smtp Mail driver
MAIL_HOST no smtp.gmail.com SMTP host
MAIL_PORT no 465 SMTP port
MAIL_USERNAME no β€” SMTP username
MAIL_PASSWORD no β€” SMTP password / app password
MAIL_ENCRYPTION no ssl Encryption type
MAIL_FROM_ADDRESS no β€” Sender address
MAIL_FROM_NAME no ${APP_NAME} Sender display name
TELEGRAM_BOT_TOKEN no β€” Bot token for Telegram report forwarding
TELEGRAM_CHAT_ID no β€” Chat/group ID to receive reports
RECAPTCHA_KEY no β€” reCAPTCHA v2 secret key
OPENCODE_URL no http://127.0.0.1:4096 Local opencode server base URL for the AI endpoint
OPENCODE_PROVIDER_ID no opencode AI model provider ID
OPENCODE_MODEL_ID no deepseek-v4-flash-free AI model ID
OPENCODE_AGENT no plan opencode agent used for /ai/ask

Note: JWT_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD, and DB_CREDENTIAL are mandatory β€” the server fails to start without them.


Project Structure

β”œβ”€β”€ main.go                     # Entry point: wiring, middleware, route registration
β”œβ”€β”€ internal/                   # Domain-driven application code
β”‚   β”œβ”€β”€ hadithdata/             # Public hadith data endpoints
β”‚   β”œβ”€β”€ search/                 # Single- and multi-kitab search
β”‚   β”œβ”€β”€ auth/                   # Login, logout, identity, refresh (user + admin)
β”‚   β”œβ”€β”€ user/                   # Registration, verification, forgot password, profile
β”‚   β”œβ”€β”€ bookmark/               # Bookmarks
β”‚   β”œβ”€β”€ note/                   # Notes
β”‚   β”œβ”€β”€ font/                   # Font preference
β”‚   β”œβ”€β”€ theme/                  # Theme preference
β”‚   β”œβ”€β”€ searchmode/             # Search mode preference
β”‚   β”œβ”€β”€ lastread/               # Last-read position
β”‚   β”œβ”€β”€ hadithadmin/            # Admin hadith CRUD
β”‚   β”œβ”€β”€ captcha/                # reCAPTCHA verification + Telegram reports
β”‚   β”œβ”€β”€ opencode/               # AI assistant proxy
β”‚   └── shared/
β”‚       β”œβ”€β”€ auth/               # JWT helpers, bcrypt, token generation
β”‚       β”œβ”€β”€ database/           # Connection, migrations, scopes, entities
β”‚       β”œβ”€β”€ email/              # SMTP sender + Hermes templates
β”‚       β”œβ”€β”€ env/                # godotenv loader
β”‚       β”œβ”€β”€ middleware/         # JWT Protected/TokenOnly, IsAdmin, context
β”‚       β”œβ”€β”€ response/           # Response DTOs
β”‚       β”œβ”€β”€ utils/              # Query logging, FTS helpers, KBBI, caches
β”‚       └── validator/          # Validation engine + custom validators
β”œβ”€β”€ models/                     # GORM models + kitab index + FTS helpers
β”œβ”€β”€ validations/                # Per-domain input validation structs
β”œβ”€β”€ .env.example                # Environment template
└── AGENTS.md                   # Contributor / coding guidelines

Each domain under internal/ follows a handler β†’ service β†’ repository pattern:

internal/<domain>/
β”œβ”€β”€ handler.go      # HTTP layer: parse, validate, respond
β”œβ”€β”€ service.go      # Business logic
β”œβ”€β”€ repository.go   # Data access (GORM)
β”œβ”€β”€ routes.go       # Route registration
β”œβ”€β”€ dto.go          # Request/response types
└── handler_test.go # Handler tests

API Overview

Base URL: http://<host>:<port>

Public endpoints need no authentication. Protected endpoints require:

Authorization: Bearer <access_token>

The standard error envelope is:

{
  "status": "error",
  "message": "<description>",
  "data": null
}

Validation errors return {"errors": { "<field>": "<message>" }} with HTTP 400.

Hadith data (public)

Method Path Description
GET /loadMainData/:kitabName/:number Load a hadith record
GET /classificationData/:kitabName/:number/:classify Classification data
GET /loadCustomData/:kitabName/:number/:position/:actionId Custom data
GET /loadSanadHadits/:kitabName/:number Sanad (chain of narration)
GET /loadScholarComment/:narratorId Scholar comments for a narrator
GET /loadTotalHadith/:kitabName/:narratorId Hadith totals for a narrator
GET /loadSimilarHadith/:kitabName/:number Similar hadith
GET /loadCompleteProfile/:narratorId Complete narrator profile
GET /searchNoLain/:kitabName/:number Other reference numbers
GET /loadBiographyData/:kitabName Narrator biographies
GET /loadAllBooks/:kitabName List of books in a kitab
GET /loadAllChapters/endfirst/:kitabName/:start/:vSelectedK Chapter list (end-first)
GET /loadAllChapters/:kitabName/:start/:end Chapter list
POST /loadListOfRawiName/ List of narrators

Search (public)

Method Path Description
POST /searchHadits/:kitabName/:column Search a single kitab
POST /searchHadits/all/:column Search multiple kitabs concurrently

See Search Endpoints for request/response shapes.

Auth

Method Path Auth Description
POST /auths/login β€” Login, returns access + refresh tokens
POST /auths/logout πŸ”’ Blacklist current access token
GET /auths/identity πŸ”’ Current user ID + email
POST /auths/refresh β€” Rotate refresh token for a new pair

Users

Method Path Auth Description
POST /users β€” Sign up (sends 6-digit verification email)
POST /users/verify πŸ”‘ token Verify email with code
POST /users/verify/resend πŸ”‘ token Resend verification code
POST /users/forgot-password β€” Request password reset code
POST /users/forgot-password/confirm β€” Confirm reset with code + new password
GET /users/:id πŸ”’ Get profile
PUT /users/:id πŸ”’ Update profile
DELETE /users/:id πŸ”’ Delete account

πŸ”’ = Protected() (JWT + active + blacklist checks). πŸ”‘ token = TokenOnly() (JWT validity only, so inactive users can verify their email).

User preferences (protected)

Method Path Description
GET / PUT /fonts Get / update font preference
GET / PUT /theme Get / update theme preference
GET / PUT /search-mode Get / update search mode preference
GET /lastRead/:book_name Get last-read position
PUT /lastRead Update last-read position

Bookmarks (protected)

Method Path Description
POST /bookmarks Create a bookmark
GET /bookmarks List bookmark titles
GET /bookmarks/:title Get one bookmark by title
GET /bookmarks/:title/:book_name Get bookmark items for a book
PUT /bookmarks/:title/:book_name Update all bookmark items
DELETE /bookmarks/:title/:book_name Delete a bookmark group

Notes (protected)

Method Path Description
POST /notes/:book_name/:hadith_id Create a note
GET /notes/:book_name/:hadith_id Get a note
GET /notes/validate-delete/:book_name/:hadith_id Check whether a note can be deleted
GET /notes/:book_name List notes for a book
PUT /notes/:book_name/:hadith_id Update a note
DELETE /notes/:book_name/:hadith_id Delete a note

Utility

Method Path Auth Description
POST /verifyreCaptcha/ β€” Verify a reCAPTCHA token, then forward a report to Telegram
POST /ai/ask πŸ”’ Ask the opencode-backed AI assistant
GET /test β€” Health check, returns Hello, World!

Admin (JWT + IsAdmin)

All admin routes are prefixed with /admin.

Method Path Description
POST /admin/auths/login Admin login (Active + Admin users only)
POST /admin/auths/logout Admin logout
GET /admin/auths/identity Admin identity
POST /admin/auths/refresh Rotate admin refresh token
GET /admin/users List users (pagination + search)
GET /admin/users/some?filter=... Get users by IDs
GET /admin/users/:id Get a user
POST /admin/users Create a user
PUT /admin/users/:id Update a user
DELETE /admin/users/:id Soft-delete a user
DELETE /admin/users?ids=[...] Delete multiple users
GET /admin/:kitabName List hadith records (pagination + search)
GET /admin/:kitabName/:number Get one hadith record
POST /admin/:kitabName Create a hadith record
PUT /admin/:kitabName/:number Update a hadith record
DELETE /admin/:kitabName/:number Delete a hadith record

Search Endpoints

Two search strategies are available β€” the frontend picks which to call.

Single kitab

POST /searchHadits/:kitabName/:column

Body:

{
  "keyword": ["niat", "amal"]
}

Response β€” a JSON array:

[rows, "SEARCHRESULTCOUNT", kitabName]

where rows is an array of { "<kitabIndex>": <hadithNumber> } objects.

Multi kitab (concurrent)

POST /searchHadits/all/:column

Body:

{
  "keyword": ["niat", "amal"],
  "books": ["ShahihBukhari", "ShahihMuslim"]
}

books is required (HTTP 400 if missing or empty). Searches are run concurrently via goroutines against the SQLite WAL-enabled pool.

Response:

{
  "results": {
    "ShahihBukhari": { "rows": [...], "count": 5 },
    "ShahihMuslim":  { "rows": [...], "count": 3 }
  },
  "total": 8
}

Search modes

Based on keyword count, the backend dispatches to one of:

  • Single keyword β€” LIKE '%keyword%' on the target column.
  • Multi keyword β€” multiple LIKE conditions joined with AND.
  • Indonesian full-text (FTS5) β€” used when the column is Indonesia and more than one keyword remains after filtering; combines the kitab's FTS<KitabName> table, KBBI stemming, phrase-variant combinations, and a LIKE fallback when FTS tables are missing or results are sparse.

Common conjunction words (atau, dan, di, yang, tentang, hadits, hadis, hadist, takhrij) are filtered out before searching. Each search writes SEARCH_START / SEARCH_RESULT entries to the daily query log.


Authentication & Tokens

Access token

Property Value
Algorithm HS256
Expiry 15 minutes
Secret JWT_SECRET env var
Claims user_id, email, exp
Header Authorization: Bearer <token>

Refresh token

Property Value
Format 64 random bytes β†’ hex string
Storage SHA-256 hash in the RefreshToken table (plain text never persisted)
Expiry 7 days
Rotation Each refresh marks the old token is_used = true and issues a new pair
Reuse detection Presenting an already-used token revokes all of the user's refresh tokens

Flow

1. POST /auths/login            β†’ access_token + refresh_token
2. Use access_token in headers  β†’ Authorization: Bearer <access_token>
3. When access_token expires    β†’ POST /auths/refresh with refresh_token
4. POST /auths/logout           β†’ blacklists the current access token

Middleware

  • Protected() β€” validates the JWT, then checks the blacklist, the user's active flag, and soft-deleted status.
  • TokenOnly() β€” validates the JWT only (used so inactive users can verify their email).
  • IsAdmin() β€” requires active = true and admin = true (used on all /admin routes).

Admin Panel

  • The initial admin is created automatically on first boot from ADMIN_EMAIL / ADMIN_PASSWORD.
  • Admin identity is defined by active = true and admin = true on the User model.
  • All /admin/* routes are guarded by Protected() + IsAdmin() except login and refresh.
  • Admin mutations (login, logout, user CRUD) are recorded in the Activity table.

Email & Verification

  • Registration sends a 6-digit verification code with a 15-minute expiry.
  • POST /users/verify activates the account (active = true).
  • POST /users/verify/resend generates a new code (old one invalidated) with a 2-minute cooldown.
  • Forgot password uses the same code mechanism, then POST /users/forgot-password/confirm sets the new password.
  • Emails are sent over SMTP (MAIL_* env vars) using Hermes templates; if SMTP is not configured, verification features still function but no email is delivered.

Deployment

# Cross-compile for Linux
set GOOS=linux && go build -ldflags "-s -w" -o app

Production hints:

  • Static files β€” app.Static("/", "./storage") serves uploaded content from the storage/ directory. Create it on the target machine (mkdir storage).
  • Database β€” SQLite runs in WAL mode with a 10-connection pool (set on startup), which supports the concurrent multi-kitab searches. Copy your prepared .db file alongside the binary and point DB_CREDENTIAL at it. Set _busy_timeout (e.g. ?_busy_timeout=5000) to avoid lock errors.
  • Secrets β€” JWT_SECRET is mandatory; the server refuses to start without it. Never commit .env.
  • Prefork β€” Fiber runs with Prefork: true, spawning one process per CPU core; ensure your deployment honors that (systemd KillMode=process or similar) if applicable.
  • Logs β€” a daily query log query_YYYYMMDD.log is written to the working directory for search/query diagnostics.
  • Health β€” GET /test returns Hello, World! and can be used as a basic health check.

Testing

# Run the full test suite
go test ./...

# Single package
go test ./internal/auth/...

# Single test (verbose)
go test -v -run TestFunctionName ./internal/user/...

The suite includes 180+ tests covering domain handlers, the migration pipeline, schema drift checks, and FTS utilities.


Known Kitab Names

Valid values for the kitabName path parameter (each is its own database table):

Value Kitab
ShahihBukhari Sahih al-Bukhari
ShahihMuslim Sahih Muslim
SunanTirmidzi Sunan al-Tirmidhi
SunanAbuDaud Sunan Abu Dawud
SunanNasai Sunan al-Nasa'i
SunanIbnuMajah Sunan Ibn Majah
SunanDarimi Sunan al-Darimi
MusnadAhmad Musnad Ahmad
MuwathaMalik Muwatta Malik
SunanDaruquthni Sunan al-Daraqutni
ShahihIbnuKhuzaimah Sahih Ibn Khuzaymah
ShahihIbnuHibban Sahih Ibn Hibban
AlMustadrak Al-Mustadrak ala al-Sahihayn
MusnadSyafii Musnad al-Shafi'i

Related Documentation


License

MIT

Releases

Packages

Contributors

Languages