6de2455917
- Added GLOBAL_MARKETS_TITLE to all translation files - Updated footer with 12 markets (4 active + 8 upcoming) - Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi - Built and deployed to production - CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
1173 lines
28 KiB
Markdown
1173 lines
28 KiB
Markdown
# AAMOS Admin
|
|
|
|
Administration console for the AAMOS platform — system dashboard, module control, user management, and real-time metrics.
|
|
|
|
**Version:** 1.0.0
|
|
**Stack:** Go 1.25 · Rust 2021 · SwiftUI (iOS) · Jetpack Compose (Android) · Vanilla HTML/JS (Web)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture](#architecture)
|
|
2. [Prerequisites](#prerequisites)
|
|
3. [Environment Variables](#environment-variables)
|
|
4. [Go Backend — Setup & Run](#go-backend)
|
|
5. [Rust Core — Build](#rust-core)
|
|
6. [Web — Deploy](#web-deploy)
|
|
7. [iOS — Xcode Setup](#ios-xcode-setup)
|
|
8. [Android — Android Studio Setup](#android-android-studio-setup)
|
|
9. [API Reference](#api-reference)
|
|
10. [WebSocket](#websocket)
|
|
11. [Database Schema](#database-schema)
|
|
12. [Deployment Guide](#deployment-guide)
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ AAMOS Admin │
|
|
│ │
|
|
│ Clients Backend │
|
|
│ ───────── ───────── │
|
|
│ │
|
|
│ ┌──────────┐ ┌─────────────────────────────┐ │
|
|
│ │ Web UI │─── HTTP/WS ──▶ │ Go HTTP Server :8080 │ │
|
|
│ │ (static) │ │ │ │
|
|
│ └──────────┘ │ /health HealthH. │ │
|
|
│ │ /auth/* AuthH. │ │
|
|
│ ┌──────────┐ │ /api/v1/ ─────────── │ │
|
|
│ │ iOS App │─── HTTPS ───▶ │ dashboard DashboardH. │ │
|
|
│ │ SwiftUI │ │ metrics MetricsH. │ │
|
|
│ └──────────┘ │ modules ModulesH. │ │
|
|
│ │ audit AuditH. │ │
|
|
│ ┌──────────┐ │ settings SettingsH. │ │
|
|
│ │ Android │─── HTTPS ───▶ │ users UsersH. │ │
|
|
│ │ Compose │ │ onboarding OnboardH. │ │
|
|
│ └──────────┘ │ /ws WebSocketH. │ │
|
|
│ └─────────────┬───────────────┘ │
|
|
│ │ │
|
|
│ ┌─────────────▼───────────────┐ │
|
|
│ │ PostgreSQL │ │
|
|
│ │ users · modules · audit │ │
|
|
│ │ settings · onboarding │ │
|
|
│ └─────────────────────────────┘ │
|
|
│ │
|
|
│ Security Layer │
|
|
│ ────────────── │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Rust Core (aamos-core.so) │ │
|
|
│ │ │ │
|
|
│ │ crypto.rs argon2 + random bytes │ │
|
|
│ │ jwt.rs token sign / verify (HS256) │ │
|
|
│ │ ratelimit.rs sliding-window rate limiting │ │
|
|
│ │ sanitize.rs input sanitisation / escaping │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ Middleware chain (every request) │
|
|
│ Request ──▶ CORS ──▶ Logging ──▶ JWTAuth* ──▶ Handler │
|
|
│ (* public routes bypass JWTAuth) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Directory Layout
|
|
|
|
```
|
|
aamos-admin/
|
|
├── backend/ Go HTTP API
|
|
│ ├── config/ Environment config loader
|
|
│ ├── db/ PostgreSQL connection + auto-migration
|
|
│ ├── handlers/ HTTP handler implementations
|
|
│ ├── middleware/ CORS, logging, JWT auth
|
|
│ ├── models/ Shared Go types
|
|
│ ├── go.mod
|
|
│ └── go.sum
|
|
├── core/ Rust security library
|
|
│ ├── src/
|
|
│ │ ├── lib.rs
|
|
│ │ ├── crypto.rs
|
|
│ │ ├── jwt.rs
|
|
│ │ ├── ratelimit.rs
|
|
│ │ └── sanitize.rs
|
|
│ └── Cargo.toml
|
|
├── web/ Static admin console
|
|
│ ├── index.html Main dashboard
|
|
│ ├── modules.html Module management
|
|
│ └── assets/
|
|
├── ios/ iOS native app (SwiftUI)
|
|
│ └── AamosAdmin/
|
|
│ ├── AamosAdmin.xcodeproj
|
|
│ ├── Services/ AuthService (Keychain)
|
|
│ ├── ViewModels/ Auth, Dashboard
|
|
│ ├── Views/ Login, Modules, Audit, Settings
|
|
│ ├── Components/ HealthRing, MetricCard, ModuleToggleRow
|
|
│ └── Models/
|
|
├── android/ Android native app (Compose)
|
|
│ ├── app/
|
|
│ │ └── src/main/java/com/aamos/admin/
|
|
│ ├── app/build.gradle.kts
|
|
│ └── settings.gradle.kts
|
|
└── shared/ Shared TypeScript types & constants
|
|
```
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
| Tool | Minimum Version | Install |
|
|
|------|----------------|---------|
|
|
| Go | 1.25.0 | `brew install go` / go.dev/dl |
|
|
| Rust + Cargo | 1.78 | `curl https://sh.rustup.rs -sSf \| sh` |
|
|
| PostgreSQL | 14 | `brew install postgresql@14` |
|
|
| Xcode | 16.0 | Mac App Store |
|
|
| Android Studio | Meerkat (2025.1) | developer.android.com |
|
|
| Node.js | 20+ (web serve only) | `brew install node` |
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
All variables are read at startup. Missing variables fall back to the listed defaults.
|
|
|
|
| Variable | Default | Required in prod | Description |
|
|
|----------|---------|-----------------|-------------|
|
|
| `PORT` | `8080` | — | HTTP listen port |
|
|
| `DB_URL` | `postgres://postgres:postgres@localhost:5432/aamos?sslmode=disable` | **YES** | PostgreSQL connection string |
|
|
| `JWT_SECRET` | `change-me-in-production` | **YES** | HMAC-SHA256 signing secret (min 32 chars) |
|
|
| `AMOS_BASE_URL` | `http://localhost:9000` | — | Base URL for upstream AMOS service calls |
|
|
| `CORS_ORIGINS` | `http://localhost:3000` | — | Comma-separated allowed CORS origins |
|
|
|
|
Create a local `.env` file (not committed):
|
|
|
|
```bash
|
|
PORT=8080
|
|
DB_URL=postgres://postgres:secret@localhost:5432/aamos?sslmode=disable
|
|
JWT_SECRET=replace-with-64-char-random-string
|
|
AMOS_BASE_URL=http://localhost:9000
|
|
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
|
|
```
|
|
|
|
---
|
|
|
|
## Go Backend
|
|
|
|
### 1. Install dependencies
|
|
|
|
```bash
|
|
cd backend
|
|
go mod download
|
|
```
|
|
|
|
### 2. Start PostgreSQL
|
|
|
|
```bash
|
|
# macOS (Homebrew)
|
|
brew services start postgresql@14
|
|
|
|
# Linux
|
|
sudo systemctl start postgresql
|
|
|
|
# Create database
|
|
createdb aamos
|
|
```
|
|
|
|
The backend runs auto-migration on startup — no manual schema steps required.
|
|
|
|
### 3. Run in development
|
|
|
|
```bash
|
|
cd backend
|
|
|
|
# Export env vars, then:
|
|
go run ./cmd/main.go
|
|
```
|
|
|
|
Or inline:
|
|
|
|
```bash
|
|
DB_URL="postgres://postgres:secret@localhost:5432/aamos?sslmode=disable" \
|
|
JWT_SECRET="dev-secret-change-me" \
|
|
go run ./cmd/main.go
|
|
```
|
|
|
|
Server starts on `http://localhost:8080`.
|
|
|
|
### 4. Build binary
|
|
|
|
```bash
|
|
cd backend
|
|
CGO_ENABLED=0 GOOS=linux go build -o aamos-admin ./cmd/main.go
|
|
./aamos-admin
|
|
```
|
|
|
|
### 5. Run tests
|
|
|
|
```bash
|
|
cd backend
|
|
go test ./...
|
|
```
|
|
|
|
---
|
|
|
|
## Rust Core
|
|
|
|
The `aamos-core` crate compiles as both a dynamic library (`cdylib`) and a Rust library (`rlib`). The Go backend links against the compiled `.so` at runtime for cryptographic operations.
|
|
|
|
### Build
|
|
|
|
```bash
|
|
cd core
|
|
|
|
# Development
|
|
cargo build
|
|
|
|
# Production (optimised — LTO, abort on panic)
|
|
cargo build --release
|
|
```
|
|
|
|
Output artifacts:
|
|
|
|
| File | Platform |
|
|
|------|---------|
|
|
| `target/release/libaamos_core.so` | Linux |
|
|
| `target/release/libaamos_core.dylib` | macOS |
|
|
| `target/release/libaamos_core.a` | Static archive |
|
|
|
|
### Run tests
|
|
|
|
```bash
|
|
cd core
|
|
cargo test
|
|
```
|
|
|
|
### Modules
|
|
|
|
| Module | Purpose |
|
|
|--------|---------|
|
|
| `crypto` | Argon2id password hashing, random byte generation |
|
|
| `jwt` | HS256 token sign and verify |
|
|
| `ratelimit` | Sliding-window rate limiting (no external state) |
|
|
| `sanitize` | Input sanitisation: HTML escape, trim, length enforcement |
|
|
|
|
---
|
|
|
|
## Web Deploy
|
|
|
|
The web frontend is a static site — no build step required.
|
|
|
|
### Local development
|
|
|
|
```bash
|
|
cd web
|
|
python3 -m http.server 3000
|
|
# or
|
|
npx serve .
|
|
```
|
|
|
|
Open `http://localhost:3000`.
|
|
|
|
### Production (nginx)
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name admin.example.com;
|
|
|
|
root /opt/aamos-admin/web;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
|
|
location /auth/ {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
|
|
location /ws {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## iOS — Xcode Setup
|
|
|
|
### Requirements
|
|
|
|
- Mac with Apple silicon or Intel
|
|
- Xcode 16.0 or later
|
|
- iOS 18.0+ device or simulator
|
|
|
|
### Step-by-step
|
|
|
|
**1. Open the project**
|
|
|
|
```
|
|
File > Open...
|
|
```
|
|
|
|
Navigate to `aamos-admin/ios/` and select `AamosAdmin.xcodeproj`.
|
|
|
|
**2. Set the development team**
|
|
|
|
- In the Project navigator, select **AamosAdmin** (root)
|
|
- Select the **AamosAdmin** target
|
|
- Open the **Signing & Capabilities** tab
|
|
- Under **Signing**, set **Team** to your Apple Developer account
|
|
|
|
**3. Configure the API base URL**
|
|
|
|
The base URL is set in `AuthViewModel.swift`:
|
|
|
|
```
|
|
AamosAdmin/ViewModels/AuthViewModel.swift
|
|
```
|
|
|
|
Change the `baseURL` constant:
|
|
|
|
```swift
|
|
private let baseURL = "https://admin-api.example.com"
|
|
```
|
|
|
|
For local development against the Go backend:
|
|
|
|
```swift
|
|
private let baseURL = "http://localhost:8080"
|
|
```
|
|
|
|
> iOS blocks HTTP by default. For local HTTP targets, add an App Transport Security exception in `Info.plist`:
|
|
>
|
|
> ```xml
|
|
> <key>NSAppTransportSecurity</key>
|
|
> <dict>
|
|
> <key>NSAllowsArbitraryLoads</key>
|
|
> <true/>
|
|
> </dict>
|
|
> ```
|
|
|
|
**4. Select a simulator or device**
|
|
|
|
- Click the device selector in the Xcode toolbar
|
|
- Choose **iPhone 16 Pro** (or any iOS 18+ simulator)
|
|
- Or connect a physical device
|
|
|
|
**5. Build and run**
|
|
|
|
```
|
|
Product > Run (⌘R)
|
|
```
|
|
|
|
**6. Authentication notes**
|
|
|
|
The app stores JWT tokens in the iOS Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. The token persists across restarts. To clear it during development, delete and reinstall the app.
|
|
|
|
### Project reference
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Bundle ID | `com.aamos.admin` |
|
|
| Keychain service | `com.aamos.admin` |
|
|
| Min deployment target | iOS 18.0 |
|
|
| UI framework | SwiftUI |
|
|
|
|
### App structure
|
|
|
|
```
|
|
AamosAdmin/
|
|
├── Services/
|
|
│ └── AuthService.swift Keychain JWT storage (read/write/delete)
|
|
├── ViewModels/
|
|
│ ├── AuthViewModel.swift Login / logout state machine
|
|
│ └── DashboardViewModel.swift Live metric polling
|
|
├── Views/
|
|
│ ├── LoginView.swift
|
|
│ ├── DashboardView.swift
|
|
│ ├── ModulesView.swift
|
|
│ ├── AuditView.swift
|
|
│ └── SettingsView.swift
|
|
└── Components/
|
|
├── HealthRing.swift System health ring visualisation
|
|
├── MetricCard.swift CPU / RAM / disk display card
|
|
└── ModuleToggleRow.swift Module enable / disable row
|
|
```
|
|
|
|
---
|
|
|
|
## Android — Android Studio Setup
|
|
|
|
### Requirements
|
|
|
|
- Android Studio Meerkat (2025.1) or later
|
|
- JDK 17 (bundled with Android Studio)
|
|
- Android SDK API 35 (install via SDK Manager)
|
|
- Device or emulator running API 26+ (Android 8.0)
|
|
|
|
### Step-by-step
|
|
|
|
**1. Open the project**
|
|
|
|
```
|
|
File > Open...
|
|
```
|
|
|
|
Select the `aamos-admin/android/` folder. Android Studio detects `settings.gradle.kts` and imports automatically. Wait for the initial Gradle sync (first run downloads ~500 MB).
|
|
|
|
**2. Gradle sync**
|
|
|
|
If sync does not start:
|
|
|
|
```
|
|
File > Sync Project with Gradle Files
|
|
```
|
|
|
|
**3. Configure the API base URL**
|
|
|
|
Locate the Retrofit base URL constant in the DI network module (e.g. `di/NetworkModule.kt`):
|
|
|
|
```kotlin
|
|
private const val BASE_URL = "https://admin-api.example.com/"
|
|
```
|
|
|
|
For the Android Emulator (routes `10.0.2.2` to host `localhost`):
|
|
|
|
```kotlin
|
|
private const val BASE_URL = "http://10.0.2.2:8080/"
|
|
```
|
|
|
|
For a physical device on the same LAN as the dev machine:
|
|
|
|
```kotlin
|
|
private const val BASE_URL = "http://192.168.1.x:8080/"
|
|
```
|
|
|
|
**4. Create or start an emulator**
|
|
|
|
```
|
|
Tools > Device Manager > Create Device
|
|
```
|
|
|
|
Choose **Pixel 9 Pro** → **API 35** system image → Finish.
|
|
|
|
**5. Build and run**
|
|
|
|
```
|
|
Run > Run 'app' (Shift+F10)
|
|
```
|
|
|
|
**6. Build release APK / AAB**
|
|
|
|
```
|
|
Build > Generate Signed App Bundle / APK...
|
|
```
|
|
|
|
Release builds enable ProGuard (`isMinifyEnabled = true`) and resource shrinking.
|
|
|
|
### Project configuration
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Application ID | `com.aamos.admin` |
|
|
| Min SDK | API 26 (Android 8.0) |
|
|
| Target SDK | API 35 (Android 15) |
|
|
| Kotlin | 2.1.0 |
|
|
| Compose BOM | 2025.04.01 |
|
|
| Hilt | 2.56.1 |
|
|
| Retrofit | 2.11.0 |
|
|
| OkHttp | 4.12.0 |
|
|
| Navigation Compose | 2.8.9 |
|
|
| Lifecycle / ViewModel | 2.9.0 |
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
**Base URL (development):** `http://localhost:8080`
|
|
|
|
All endpoints that require authentication expect:
|
|
|
|
```
|
|
Authorization: Bearer <jwt-token>
|
|
```
|
|
|
|
JWT tokens expire after **24 hours**.
|
|
|
|
---
|
|
|
|
### Health
|
|
|
|
#### `GET /health`
|
|
|
|
No authentication required.
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
```
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"version": "1.0.0",
|
|
"uptime": "4h32m15s"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Authentication
|
|
|
|
#### `POST /auth/login`
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "admin@example.com", "password": "secret"}'
|
|
```
|
|
|
|
**200 OK**
|
|
|
|
```json
|
|
{
|
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"user": {
|
|
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
|
"email": "admin@example.com",
|
|
"name": "Admin User",
|
|
"role": "admin"
|
|
}
|
|
}
|
|
```
|
|
|
|
**401 Unauthorized** — invalid credentials
|
|
|
|
```json
|
|
{ "error": "invalid credentials" }
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /auth/logout`
|
|
|
|
JWT is stateless — the client discards the token. Server confirms.
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/auth/logout \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{ "message": "logged out" }
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /auth/me`
|
|
|
|
```bash
|
|
curl http://localhost:8080/auth/me \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
|
"email": "admin@example.com",
|
|
"name": "Admin User",
|
|
"role": "admin"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Dashboard
|
|
|
|
#### `GET /api/v1/dashboard`
|
|
|
|
Aggregated view: system metrics, module states, and recent audit summary.
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/v1/dashboard \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
---
|
|
|
|
### System Metrics
|
|
|
|
#### `GET /api/v1/metrics`
|
|
|
|
Live CPU, RAM, disk, and process counts. CPU is sampled twice with a 200 ms interval.
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/v1/metrics \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"timestamp": "2026-05-10T14:23:00Z",
|
|
"cpu": { "usage_percent": 12.50 },
|
|
"ram": {
|
|
"total_bytes": 8589934592,
|
|
"used_bytes": 4123456789,
|
|
"free_bytes": 1073741824,
|
|
"available_bytes": 2147483648,
|
|
"usage_percent": 48.01
|
|
},
|
|
"disk": {
|
|
"path": "/",
|
|
"total_bytes": 107374182400,
|
|
"used_bytes": 53687091200,
|
|
"free_bytes": 53687091200,
|
|
"usage_percent": 50.00
|
|
},
|
|
"processes": { "total": 312, "running": 4 }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Modules
|
|
|
|
The 10 Ouroboros modules are seeded automatically on first start.
|
|
|
|
#### `GET /api/v1/modules`
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/v1/modules \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "academy",
|
|
"name": "Academy",
|
|
"description": "Learning & Training Platform",
|
|
"icon": "graduation-cap",
|
|
"enabled": false,
|
|
"status": "stopped"
|
|
},
|
|
{
|
|
"id": "finance",
|
|
"name": "Finance",
|
|
"description": "Financial Management & Reporting",
|
|
"icon": "chart-line",
|
|
"enabled": true,
|
|
"status": "running"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Module catalogue:**
|
|
|
|
| ID | Name | Description |
|
|
|----|------|-------------|
|
|
| `academy` | Academy | Learning & Training Platform |
|
|
| `finance` | Finance | Financial Management & Reporting |
|
|
| `compliance` | Compliance | Regulatory Compliance & Auditing |
|
|
| `people` | People | HR & People Management |
|
|
| `operations` | Operations | Operations & Process Management |
|
|
| `commerce` | Commerce | Sales & Commerce Platform |
|
|
| `identity` | Identity | Identity & Access Management |
|
|
| `connect` | Connect | Communications & Integration Hub |
|
|
| `governance` | Governance | Corporate Governance & Policy |
|
|
| `analytics` | Analytics | Data Analytics & Business Intelligence |
|
|
|
|
#### `PUT /api/v1/modules/{id}/toggle`
|
|
|
|
Flips `enabled` and syncs `status` (`running` ↔ `stopped`).
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/v1/modules/finance/toggle \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"id": "finance",
|
|
"name": "Finance",
|
|
"description": "Financial Management & Reporting",
|
|
"icon": "chart-line",
|
|
"enabled": true,
|
|
"status": "running"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Audit Log
|
|
|
|
#### `GET /api/v1/audit`
|
|
|
|
Paginated, newest first.
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `page` | int | `1` | Page number |
|
|
| `limit` | int | `20` | Page size (max 100) |
|
|
| `user` | string | — | Filter by `user_id` |
|
|
| `action` | string | — | Filter by action name |
|
|
| `resource` | string | — | Filter by resource name |
|
|
|
|
```bash
|
|
curl "http://localhost:8080/api/v1/audit?page=1&limit=5&action=login" \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "a1b2c3d4-...",
|
|
"user_id": "01234567-...",
|
|
"action": "login",
|
|
"resource": "auth",
|
|
"ip": "127.0.0.1",
|
|
"timestamp": "2026-05-10T14:00:00Z",
|
|
"details": ""
|
|
}
|
|
],
|
|
"total": 42,
|
|
"page": 1,
|
|
"limit": 5
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Settings
|
|
|
|
#### `GET /api/v1/settings`
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/v1/settings \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"maintenance_mode": "false",
|
|
"max_login_attempts": "5"
|
|
}
|
|
```
|
|
|
|
#### `PUT /api/v1/settings`
|
|
|
|
Upserts one or more key-value pairs atomically.
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/v1/settings \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"maintenance_mode": "true", "max_login_attempts": "3"}'
|
|
```
|
|
|
|
Returns the updated keys:
|
|
|
|
```json
|
|
{
|
|
"maintenance_mode": "true",
|
|
"max_login_attempts": "3"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Users
|
|
|
|
#### `GET /api/v1/users?page=1&limit=20`
|
|
|
|
```bash
|
|
curl "http://localhost:8080/api/v1/users?page=1&limit=20" \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"users": [
|
|
{
|
|
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
|
"email": "admin@example.com",
|
|
"name": "Admin User",
|
|
"role": "admin",
|
|
"created_at": "2026-01-01T00:00:00Z",
|
|
"last_login": "2026-05-10T14:00:00Z"
|
|
}
|
|
],
|
|
"total": 1,
|
|
"page": 1,
|
|
"limit": 20
|
|
}
|
|
```
|
|
|
|
#### `POST /api/v1/users`
|
|
|
|
Role must be `admin` or `user` (defaults to `user`).
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/v1/users \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "alice@example.com",
|
|
"name": "Alice",
|
|
"role": "user",
|
|
"password": "strong-password-here"
|
|
}'
|
|
```
|
|
|
|
**201 Created**
|
|
|
|
```json
|
|
{
|
|
"id": "abcdef01-...",
|
|
"email": "alice@example.com",
|
|
"name": "Alice",
|
|
"role": "user",
|
|
"created_at": "2026-05-10T14:30:00Z",
|
|
"last_login": "2026-05-10T14:30:00Z"
|
|
}
|
|
```
|
|
|
|
**409 Conflict** — email already exists
|
|
|
|
#### `GET /api/v1/users/{id}`
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/v1/users/abcdef01-... \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
#### `PUT /api/v1/users/{id}`
|
|
|
|
Partial update. Omit `role` to preserve the existing value.
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/v1/users/abcdef01-... \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "Alice Smith", "role": "admin"}'
|
|
```
|
|
|
|
#### `DELETE /api/v1/users/{id}`
|
|
|
|
Soft delete — sets `deleted_at`. The user disappears from all list and lookup queries.
|
|
|
|
```bash
|
|
curl -X DELETE http://localhost:8080/api/v1/users/abcdef01-... \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
**204 No Content**
|
|
|
|
---
|
|
|
|
### Onboarding
|
|
|
|
#### `POST /api/v1/onboarding/start`
|
|
|
|
Creates an onboarding session.
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/v1/onboarding/start \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"steps": ["create_admin", "enable_modules", "configure_settings"]}'
|
|
```
|
|
|
|
**201 Created**
|
|
|
|
```json
|
|
{
|
|
"id": "d4e5f6a7b8c9...",
|
|
"steps": ["create_admin", "enable_modules", "configure_settings"],
|
|
"started_at": "2026-05-10T14:00:00Z",
|
|
"status": "pending"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket
|
|
|
|
Real-time system metrics are pushed to every connected client every **5 seconds**.
|
|
|
|
**Endpoint:** `ws://localhost:8080/ws`
|
|
|
|
**Frame format** — same shape as `GET /api/v1/metrics`:
|
|
|
|
```json
|
|
{
|
|
"timestamp": "2026-05-10T14:23:05Z",
|
|
"cpu": { "usage_percent": 8.25 },
|
|
"ram": {
|
|
"total_bytes": 8589934592,
|
|
"used_bytes": 4000000000,
|
|
"free_bytes": 1000000000,
|
|
"available_bytes": 2000000000,
|
|
"usage_percent": 46.57
|
|
},
|
|
"disk": {
|
|
"path": "/",
|
|
"total_bytes": 107374182400,
|
|
"used_bytes": 50000000000,
|
|
"free_bytes": 50000000000,
|
|
"usage_percent": 46.57
|
|
},
|
|
"processes": { "total": 310, "running": 3 }
|
|
}
|
|
```
|
|
|
|
**Browser example:**
|
|
|
|
```javascript
|
|
const ws = new WebSocket('ws://localhost:8080/ws');
|
|
ws.onmessage = (event) => {
|
|
const m = JSON.parse(event.data);
|
|
console.log('CPU:', m.cpu.usage_percent, '%');
|
|
};
|
|
```
|
|
|
|
**Keepalive:** Server sends a WebSocket `PING` every 54 seconds. Clients that do not respond within 60 seconds are disconnected.
|
|
|
|
---
|
|
|
|
## Database Schema
|
|
|
|
Auto-migrated on every backend startup (idempotent — safe to restart).
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
email TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL DEFAULT '',
|
|
role TEXT NOT NULL DEFAULT 'user', -- 'admin' | 'user'
|
|
password_hash TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_login TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ -- soft delete
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS modules (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
icon TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'stopped' -- 'running' | 'stopped'
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE SET NULL,
|
|
action TEXT NOT NULL,
|
|
resource TEXT NOT NULL DEFAULT '',
|
|
ip TEXT NOT NULL DEFAULT '',
|
|
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
details TEXT NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON audit_logs(timestamp DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL DEFAULT '',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS onboarding_sessions (
|
|
id TEXT PRIMARY KEY,
|
|
steps TEXT NOT NULL, -- JSON array
|
|
started_at TIMESTAMPTZ NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending'
|
|
);
|
|
```
|
|
|
|
Connection pool (configured in `db/postgres.go`):
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Max open connections | 25 |
|
|
| Max idle connections | 10 |
|
|
| Max connection lifetime | 5 minutes |
|
|
|
|
---
|
|
|
|
## Deployment Guide
|
|
|
|
### Docker (single container)
|
|
|
|
```dockerfile
|
|
# backend/Dockerfile
|
|
FROM golang:1.25-alpine AS builder
|
|
WORKDIR /app
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
COPY . .
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /aamos-admin ./cmd/main.go
|
|
|
|
FROM gcr.io/distroless/static:nonroot
|
|
COPY --from=builder /aamos-admin /aamos-admin
|
|
EXPOSE 8080
|
|
ENTRYPOINT ["/aamos-admin"]
|
|
```
|
|
|
|
```bash
|
|
docker build -t aamos-admin:latest ./backend
|
|
|
|
docker run -d \
|
|
--name aamos-admin \
|
|
-p 8080:8080 \
|
|
-e DB_URL="postgres://user:pass@db:5432/aamos?sslmode=disable" \
|
|
-e JWT_SECRET="$(openssl rand -base64 48)" \
|
|
-e CORS_ORIGINS="https://admin.example.com" \
|
|
aamos-admin:latest
|
|
```
|
|
|
|
### Docker Compose (backend + database)
|
|
|
|
```yaml
|
|
services:
|
|
db:
|
|
image: postgres:15-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: aamos
|
|
POSTGRES_USER: aamos
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U aamos"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 5
|
|
|
|
backend:
|
|
build: ./backend
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
ports:
|
|
- "8080:8080"
|
|
environment:
|
|
DB_URL: postgres://aamos:${DB_PASSWORD}@db:5432/aamos?sslmode=disable
|
|
JWT_SECRET: ${JWT_SECRET}
|
|
CORS_ORIGINS: ${CORS_ORIGINS}
|
|
PORT: "8080"
|
|
|
|
volumes:
|
|
pgdata:
|
|
```
|
|
|
|
```bash
|
|
# .env
|
|
DB_PASSWORD=strong-db-password
|
|
JWT_SECRET=$(openssl rand -base64 48)
|
|
CORS_ORIGINS=https://admin.example.com
|
|
|
|
docker compose up -d
|
|
docker compose logs -f backend
|
|
```
|
|
|
|
### systemd (bare metal / VM)
|
|
|
|
```ini
|
|
# /etc/systemd/system/aamos-admin.service
|
|
[Unit]
|
|
Description=AAMOS Admin Backend
|
|
After=network.target postgresql.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=aamos
|
|
WorkingDirectory=/opt/aamos-admin/backend
|
|
EnvironmentFile=/etc/aamos-admin/env
|
|
ExecStart=/opt/aamos-admin/backend/aamos-admin
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
```bash
|
|
# /etc/aamos-admin/env
|
|
PORT=8080
|
|
DB_URL=postgres://aamos:secret@localhost:5432/aamos?sslmode=disable
|
|
JWT_SECRET=replace-with-64-char-random-string
|
|
CORS_ORIGINS=https://admin.example.com
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now aamos-admin
|
|
sudo systemctl status aamos-admin
|
|
```
|
|
|
|
### First-run: create admin user
|
|
|
|
After the backend starts for the first time, create an admin account:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/v1/users \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "admin@example.com",
|
|
"name": "Administrator",
|
|
"role": "admin",
|
|
"password": "change-me-immediately"
|
|
}'
|
|
```
|
|
|
|
Obtain a token:
|
|
|
|
```bash
|
|
TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"admin@example.com","password":"change-me-immediately"}' \
|
|
| jq -r .token)
|
|
```
|
|
|
|
Verify:
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
# {"ok":true,"version":"1.0.0","uptime":"0s"}
|
|
```
|
|
|
|
### iOS — App Store / TestFlight
|
|
|
|
1. **Product > Archive** in Xcode
|
|
2. In Organizer: **Distribute App > App Store Connect**
|
|
3. Follow the upload wizard — select the provisioning profile for `com.aamos.admin`
|
|
4. In App Store Connect, submit the build to TestFlight for internal testing
|
|
|
|
### Android — Google Play
|
|
|
|
```bash
|
|
cd android
|
|
./gradlew :app:bundleRelease
|
|
# Output: app/build/outputs/bundle/release/app-release.aab
|
|
```
|
|
|
|
Sign the AAB with your upload key and upload via the Google Play Console.
|