Changelog
v0.1.13
Native Apple Provider Registration
Allows native Apple sign-in to register with bundle-id-only backend configuration.
- Registers the Apple provider when `APPLE_APP_BUNDLE_IDENTIFIER` is configured.
- Keeps `APPLE_CLIENT_SECRET` optional for native ID-token sign-in and required only for web OAuth redirects.
- Adds provider diagnostics that report registration status without exposing secrets.
v0.1.12
Native iOS Auth
Enabled native app session tokens for Better Auth social sign-in.
- Added Better Auth bearer-token support for protected API routes.
- Allowed comma-separated Google client IDs and Apple bundle ID audiences for native ID-token verification.
- Documented the iOS ID-token exchange and Authorization header contract.
v0.1.11
Authenticated Prayer Marks
Added persistent per-user prayed-state sync for individual prayers.
- Added the `user_prayer_marks` table keyed by user, date, and prayer name.
- Exposed authenticated `GET`, `POST`, and `DELETE /v1/prayer-marks` routes.
- Allows the app to restore marked-prayed history after reinstall when the user logs back in.
v0.1.10
Reverse Geocoded Nearby Inference
Improved `/v1/location/nearby` for Indonesia coordinates whose local bounding boxes are too narrow.
- Added BigDataCloud reverse geocoding as a country-inference fallback.
- Automatically enables Indonesia centroid recommendations when reverse geocoding returns `countryCode=ID`.
- Boosts official locations matching the reverse-geocoded city before pure distance sorting.
v0.1.9
Qibla Direction Endpoint
Added coordinate-based qibla direction calculation to the normalized prayer API.
- Exposed myQuran-style coordinate path input at `/v1/qibla/:coordinates`.
- Added query-parameter input at `/v1/qibla` for app clients.
- Returned latitude, longitude, and direction in degrees clockwise from true north.
v0.1.8
Social Safety Controls
Added block lists, mute lists, social settings, and the baseline Drizzle migration.
- Blocks prevent friend requests and prayer nudges in either direction.
- Mutes suppress APNs notification boundary responses.
- Blocking removes reciprocal friendships and cancels pending requests.
v0.1.7
Social Flow Hardening
Closed friend-request and nudge lifecycle gaps after reviewing the v0.1.6 flow.
- Friend requests now validate the addressee before insert.
- Reverse pending friend requests are rejected explicitly.
- Added cancel friend request and mark nudge read endpoints.
v0.1.6
Social Foundation API
Implemented the first authenticated social route surface.
- Added device registration, friend requests, friendships, and prayer nudges.
- Added Better Auth session protection for social routes.
- Added notification queue boundary objects for social events.
v0.1.5
Coordinate-Based Official Location Recommendations
Changed nearby location resolution into ranked official location recommendations.
- Added Indonesia centroid and bounding-box ranking.
- Returned fallback coordinate location alongside recommendations.
- Avoided accidental Indonesia recommendations for unsupported nearby countries.
v0.1.4
Lightweight API Documentation Landing Page
Added the backend documentation page served from `/` and `/docs`.
- Documented implemented endpoints with example input and output.
- Kept the page static and dependency-free.
- Linked the page to the architecture document.
v0.1.3
Prayer Schedule Cache Layer
Added cache keys and Redis-backed storage for normalized prayer responses.
- Cached day and month prayer responses.
- Added in-memory fallback for local development.
- Kept provider fallbacks compatible with cache reads.
v0.1.2
Code Hygiene and Package Modernization
Cleaned lint issues and modernized package usage.
- Aligned TypeScript and ESLint behavior.
- Cleaned location sync code.
- Kept build and lint passing as a baseline.
v0.1.1
Indonesia Kemenag via myQuran v3
Added Indonesia official prayer-location support through myQuran v3.
- Synced province and kab/kota catalog data.
- Added Kemenag/myQuran provider adapter.
- Exposed Indonesia province and kab/kota endpoints.
v0.1.0
Backend Foundation
Created the initial Hono, TypeScript, Drizzle, Better Auth, and module foundation.
- Added health, auth, prayer, social, and notification module boundaries.
- Defined normalized country, location, and prayer models.
- Established the architecture document as the implementation ledger.
Implemented Endpoints
GET
/health
Health Check
Confirms that the API process is running.
Example Input
GET /health
Example Output
{
"status": "ok",
"service": "sapujagat-backend",
"timestamp": "2026-06-25T00:00:00.000Z"
}
GET
/v1/country-standards
Country Standards
Lists normalized prayer country standards and their default providers.
Example Input
GET /v1/country-standards
Example Output
{
"data": [
{
"countryCode": "ID",
"countryName": "Indonesia",
"locationMode": "city",
"authorityName": "Kementerian Agama Republik Indonesia",
"defaultProvider": "kemenag",
"fallbackProvider": "calculation"
}
]
}
GET
/v1/qibla/:coordinates
Qibla Direction
Calculates the qibla bearing from latitude and longitude coordinates.
Example Input
GET /v1/qibla/-6.200000,106.816666
Example Output
{
"data": {
"latitude": -6.2,
"longitude": 106.816666,
"direction": 295.156312837391
}
}
GET
/v1/location/indonesia/provinces
Indonesia Provinces
Returns province choices used by the iOS location picker.
Example Input
GET /v1/location/indonesia/provinces
Example Output
{
"data": [
{
"code": "ID-JB",
"name": "JAWA BARAT",
"timezone": "Asia/Jakarta"
}
]
}
GET
/v1/location/indonesia/kabkota
Indonesia Kab/Kota List
Searches the locally synced Kemenag/myQuran kabupaten and kota catalog.
Example Input
GET /v1/location/indonesia/kabkota?q=bekasi
Example Output
{
"data": [
{
"id": "cedebb6e872f539bef8c3f919874e9d7",
"countryCode": "ID",
"locationMode": "city",
"displayName": "KOTA BEKASI",
"officialCode": "cedebb6e872f539bef8c3f919874e9d7",
"provinceCode": "ID-JB",
"provinceName": "JAWA BARAT",
"locationType": "kota",
"timezone": "Asia/Jakarta"
}
]
}
GET
/v1/location/indonesia/kabkota/:id
Indonesia Kab/Kota Detail
Looks up a single Indonesia prayer location by myQuran/Kemenag location id.
Example Input
GET /v1/location/indonesia/kabkota/cedebb6e872f539bef8c3f919874e9d7
Example Output
{
"data": {
"id": "cedebb6e872f539bef8c3f919874e9d7",
"displayName": "KOTA BEKASI",
"provinceCode": "ID-JB",
"provinceName": "JAWA BARAT",
"timezone": "Asia/Jakarta"
}
}
GET
/v1/location/search
Normalized Location Search
Searches official provider locations through the country standard abstraction.
Example Input
GET /v1/location/search?countryCode=ID&q=bekasi
Example Output
{
"data": [
{
"id": "cedebb6e872f539bef8c3f919874e9d7",
"countryCode": "ID",
"locationMode": "city",
"displayName": "KOTA BEKASI",
"timezone": "Asia/Jakarta"
}
]
}
GET
/v1/location/nearby
Nearby Location Recommendations
Ranks official prayer locations near user coordinates using local containment, reverse-geocoded country inference, and centroid distance.
Example Input
GET /v1/location/nearby?latitude=-6.2383&longitude=106.9756&limit=10
Example Output
{
"data": {
"input": {
"latitude": -6.2383,
"longitude": 106.9756
},
"inferredCountryCode": "ID",
"locationMode": "city",
"recommendations": [
{
"rank": 1,
"distanceKm": 2.13,
"confidence": "high",
"location": {
"id": "cedebb6e872f539bef8c3f919874e9d7",
"countryCode": "ID",
"locationMode": "city",
"displayName": "KOTA BEKASI",
"officialCode": "cedebb6e872f539bef8c3f919874e9d7",
"latitude": -6.2349858,
"longitude": 106.9945444,
"timezone": "Asia/Jakarta",
"metadata": {
"provinceCode": "ID-JB",
"provinceName": "JAWA BARAT",
"locationType": "kota",
"source": "myQuran v3 / Kemenag Bimas Islam"
}
}
}
],
"fallbackLocation": {
"id": "coord:-6.23830,106.97560",
"countryCode": "XX",
"locationMode": "coordinate",
"displayName": "Coordinate Location",
"latitude": -6.2383,
"longitude": 106.9756,
"timezone": "UTC"
}
}
}
GET
/v1/prayer-times/day
Prayer Times Day
Returns one normalized prayer day. Indonesia requests use Kemenag data through myQuran v3 and the cache layer.
Example Input
GET /v1/prayer-times/day?countryCode=ID&location=KOTA%20BEKASI&date=2026-06-24
Example Output
{
"data": {
"date": "2026-06-24",
"timezone": "Asia/Jakarta",
"location": {
"id": "cedebb6e872f539bef8c3f919874e9d7",
"countryCode": "ID",
"locationMode": "city",
"displayName": "KOTA BEKASI",
"timezone": "Asia/Jakarta"
},
"source": {
"providerCode": "kemenag",
"countryCode": "ID",
"authorityName": "Kementerian Agama Republik Indonesia",
"methodName": "Kemenag Bimas Islam via myQuran v3"
},
"times": {
"fajr": "04:40",
"sunrise": "05:58",
"dhuhr": "11:58",
"asr": "15:19",
"maghrib": "17:50",
"isha": "19:05"
}
}
}
GET
/v1/prayer-times/month
Prayer Times Month
Returns normalized prayer days for a full month.
Example Input
GET /v1/prayer-times/month?countryCode=ID&location=KOTA%20BEKASI&year=2026&month=6
Example Output
{
"data": {
"year": 2026,
"month": 6,
"timezone": "Asia/Jakarta",
"location": {
"id": "cedebb6e872f539bef8c3f919874e9d7",
"displayName": "KOTA BEKASI"
},
"days": [
{
"date": "2026-06-01",
"times": {
"fajr": "04:36",
"sunrise": "05:53",
"dhuhr": "11:53",
"asr": "15:14",
"maghrib": "17:47",
"isha": "19:00"
}
}
]
}
}
GET
/v1/prayer-marks
Prayer Mark List
Lists the authenticated user's prayers marked as prayed, optionally filtered by local prayer date range.
Example Input
GET /v1/prayer-marks?from=2026-07-01&to=2026-07-31
Authenticated session headers
Example Output
{
"data": [
{
"userId": "user_123",
"prayerDate": "2026-07-01",
"prayerName": "fajr",
"createdAt": "2026-07-01T21:30:00.000Z",
"updatedAt": "2026-07-01T21:30:00.000Z"
}
]
}
POST
/v1/prayer-marks
Mark Prayer Prayed
Creates or updates one prayed-state row for the authenticated user.
Example Input
POST /v1/prayer-marks
Authenticated session headers
{"prayerDate":"2026-07-01","prayerName":"fajr"}
Example Output
{
"data": {
"userId": "user_123",
"prayerDate": "2026-07-01",
"prayerName": "fajr"
}
}
DELETE
/v1/prayer-marks/:prayerDate/:prayerName
Unmark Prayer Prayed
Deletes one prayed-state row owned by the authenticated user.
Example Input
DELETE /v1/prayer-marks/2026-07-01/fajr
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"prayerDate": "2026-07-01",
"prayerName": "fajr"
}
}
GET
/v1/social/settings
Social Settings
Returns authenticated user social preferences with default-open behavior when no row exists.
Example Input
GET /v1/social/settings
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"allowFriendRequests": true,
"allowPrayerNudges": true
}
}
PATCH
/v1/social/settings
Update Social Settings
Updates friend-request and prayer-nudge receiving preferences for the authenticated user.
Example Input
PATCH /v1/social/settings
Authenticated session headers
{"allowPrayerNudges":false}
Example Output
{
"data": {
"userId": "user_123",
"allowFriendRequests": true,
"allowPrayerNudges": false
}
}
POST
/v1/social/devices
Device Registration
Registers or refreshes an authenticated user's push-capable device token.
Example Input
POST /v1/social/devices
Authenticated session headers
{"platform":"ios","pushToken":"apns-token-value","deviceName":"Dandy iPhone"}
Example Output
{
"data": {
"id": "a7b5b6c8-6c7d-4b3a-9f7a-7b94a9f8f0f1",
"userId": "user_123",
"platform": "ios",
"pushToken": "apns-token-value",
"deviceName": "Dandy iPhone"
}
}
POST
/v1/social/friend-requests
Create Friend Request
Creates a pending friend request and queues a friend-request notification event.
Example Input
POST /v1/social/friend-requests
Authenticated session headers
{"addresseeId":"user_456"}
Example Output
{
"data": {
"request": {
"id": "f0c01b16-f836-4a90-a00f-fd76a909c4dd",
"requesterId": "user_123",
"addresseeId": "user_456",
"status": "pending"
},
"notification": {
"channel": "apns",
"event": "friend_request",
"status": "queued"
}
}
}
GET
/v1/social/friend-requests
Friend Request List
Lists incoming and outgoing friend requests for the authenticated user.
Example Input
GET /v1/social/friend-requests
Authenticated session headers
Example Output
{
"data": [
{
"id": "f0c01b16-f836-4a90-a00f-fd76a909c4dd",
"requesterId": "user_123",
"addresseeId": "user_456",
"status": "pending"
}
]
}
POST
/v1/social/friend-requests/:id/accept
Accept Friend Request
Accepts a pending friend request addressed to the authenticated user and creates reciprocal friendship rows.
Example Input
POST /v1/social/friend-requests/f0c01b16-f836-4a90-a00f-fd76a909c4dd/accept
Authenticated session headers
Example Output
{
"data": {
"id": "f0c01b16-f836-4a90-a00f-fd76a909c4dd",
"requesterId": "user_123",
"addresseeId": "user_456",
"status": "accepted"
}
}
POST
/v1/social/friend-requests/:id/decline
Decline Friend Request
Declines a pending friend request addressed to the authenticated user.
Example Input
POST /v1/social/friend-requests/f0c01b16-f836-4a90-a00f-fd76a909c4dd/decline
Authenticated session headers
Example Output
{
"data": {
"id": "f0c01b16-f836-4a90-a00f-fd76a909c4dd",
"requesterId": "user_123",
"addresseeId": "user_456",
"status": "declined"
}
}
POST
/v1/social/friend-requests/:id/cancel
Cancel Friend Request
Cancels a pending outgoing friend request owned by the authenticated user.
Example Input
POST /v1/social/friend-requests/f0c01b16-f836-4a90-a00f-fd76a909c4dd/cancel
Authenticated session headers
Example Output
{
"data": {
"id": "f0c01b16-f836-4a90-a00f-fd76a909c4dd",
"requesterId": "user_123",
"addresseeId": "user_456",
"status": "cancelled"
}
}
GET
/v1/social/friends
Friend List
Lists accepted friendship rows for the authenticated user.
Example Input
GET /v1/social/friends
Authenticated session headers
Example Output
{
"data": [
{
"userId": "user_456",
"friendId": "user_123",
"createdAt": "2026-06-27T00:00:00.000Z"
}
]
}
POST
/v1/social/blocks/:userId
Block User
Blocks another user from social interactions with the authenticated user.
Example Input
POST /v1/social/blocks/user_456
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"blockedUserId": "user_456"
}
}
DELETE
/v1/social/blocks/:userId
Unblock User
Removes an authenticated user's block for another user.
Example Input
DELETE /v1/social/blocks/user_456
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"blockedUserId": "user_456"
}
}
GET
/v1/social/blocks
Blocked User List
Lists users blocked by the authenticated user.
Example Input
GET /v1/social/blocks
Authenticated session headers
Example Output
{
"data": [
{
"userId": "user_123",
"blockedUserId": "user_456"
}
]
}
POST
/v1/social/mutes/:userId
Mute User
Suppresses notification fan-out for social actions from another user.
Example Input
POST /v1/social/mutes/user_456
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"mutedUserId": "user_456"
}
}
DELETE
/v1/social/mutes/:userId
Unmute User
Removes an authenticated user's mute for another user.
Example Input
DELETE /v1/social/mutes/user_456
Authenticated session headers
Example Output
{
"data": {
"userId": "user_123",
"mutedUserId": "user_456"
}
}
GET
/v1/social/mutes
Muted User List
Lists users muted by the authenticated user.
Example Input
GET /v1/social/mutes
Authenticated session headers
Example Output
{
"data": [
{
"userId": "user_123",
"mutedUserId": "user_456"
}
]
}
GET
/v1/social/nudges
Prayer Nudge List
Lists sent and received prayer nudges for the authenticated user.
Example Input
GET /v1/social/nudges
Authenticated session headers
Example Output
{
"data": [
{
"id": "df3d1a9d-5207-45c2-8df7-1b53866819a7",
"senderId": "user_123",
"recipientId": "user_456",
"prayerName": "fajr",
"message": "Subuh yuk",
"status": "queued"
}
]
}
POST
/v1/social/nudges
Send Prayer Nudge
Stores a prayer nudge for an accepted friend and queues the notification boundary event.
Example Input
POST /v1/social/nudges
Authenticated session headers
{"recipientId":"user_456","prayerName":"fajr","message":"Subuh yuk"}
Example Output
{
"data": {
"nudge": {
"id": "df3d1a9d-5207-45c2-8df7-1b53866819a7",
"senderId": "user_123",
"recipientId": "user_456",
"prayerName": "fajr",
"message": "Subuh yuk",
"status": "queued"
},
"notification": {
"channel": "apns",
"event": "prayer_nudge",
"status": "queued"
}
}
}
POST
/v1/social/nudges/:id/read
Mark Prayer Nudge Read
Marks a received prayer nudge as read for the authenticated recipient.
Example Input
POST /v1/social/nudges/df3d1a9d-5207-45c2-8df7-1b53866819a7/read
Authenticated session headers
Example Output
{
"data": {
"id": "df3d1a9d-5207-45c2-8df7-1b53866819a7",
"senderId": "user_123",
"recipientId": "user_456",
"prayerName": "fajr",
"status": "read"
}
}
POST
/api/auth/sign-in/social
Native Social Sign-In
Exchanges an Apple or Google provider ID token for a Better Auth session token. iOS should store the returned token in Keychain.
Example Input
POST /api/auth/sign-in/social
{"provider":"apple","idToken":{"token":"apple-identity-token","nonce":"nonce-used-for-apple-request"}}
Example Output
{
"redirect": false,
"token": "better-auth-session-token",
"user": {
"id": "user_123",
"email": "dandy@example.com",
"name": "Dandy Candrasaputra"
}
}
GET
/api/auth/get-session
Get Auth Session
Returns the current Better Auth session. Native clients can authenticate this request with Authorization: Bearer <session-token>.
Example Input
GET /api/auth/get-session
Authorization: Bearer better-auth-session-token
Example Output
{
"session": {
"id": "session_123",
"userId": "user_123",
"expiresAt": "2026-07-14T00:00:00.000Z"
},
"user": {
"id": "user_123",
"email": "dandy@example.com",
"name": "Dandy Candrasaputra"
}
}