# NEDA Backend

NEDA is a FastAPI backend for real-time group voice communication.
It includes authentication, user/group management, WebSocket signaling, Redis for real-time state, and PostgreSQL for persistent data.

## Tech Stack

- FastAPI + Uvicorn
- SQLAlchemy (Async) + asyncpg
- PostgreSQL
- Redis
- Alembic
- LiveKit

## Project Structure

```text
core/          config, security, deps
alembic/       database migrations
db/            database/redis setup
domains/       domain modules (auth, users, admin, groups, realtime, notifications)
integrations/  external integrations (LiveKit)
scripts/       utility scripts (create_admin)
main.py        FastAPI app entrypoint
```

## Active Routes

- `POST /auth/login`
- `GET /users/`
- `POST /admin/users`
- `POST /admin/users/{user_id}/logout`
- `POST /admin/users/{user_id}/reset-secret`
- `GET /admin/users`
- `GET /admin/groups`
- `POST /groups/`
- `GET /groups/my`
- `GET /groups/admin/all`
- `GET /groups/{group_id}/members`
- `POST /groups/{group_id}/invite`
- `DELETE /groups/{group_id}/members/{user_id}`
- `WS /ws/groups/{group_id}`

## Environment Variables

Create a `.env` file in the project root:

```env
APP_NAME=NEDA
DEBUG=False

SECRET_KEY=change-me
ACCESS_TOKEN_EXPIRE_MINUTES=30
ALGORITHM=HS256
SECRET_PASS_LENGTH=32

POSTGRES_DB=neda
POSTGRES_USER=neda_user
POSTGRES_PASSWORD=neda_pass
DATABASE_URL=postgresql+asyncpg://neda_user:neda_pass@postgres:5432/neda

REDIS_URL=redis://redis:6379/0

LIVEKIT_API_KEY=neda_key
LIVEKIT_API_SECRET=neda_secret
LIVEKIT_HOST=http://livekit:7880
LIVEKIT_PORT=7880
LIVEKIT_TCP_PORT=7881
LIVEKIT_UDP_PORT=7882
```

LiveKit note:
- This project runs LiveKit v1.9.12 in Docker.
- `--api-key/--api-secret` are not used; the server receives credentials via `LIVEKIT_KEYS`.
- `livekit.yaml` is kept as a reference file and is not mounted/used at runtime in `docker-compose.yml`.
- Docker-internal API host must be `http://livekit:7880`.
- For local API execution (outside Docker), use a separate env value such as `LIVEKIT_HOST=http://localhost:7780`.

## Run With Docker

```bash
docker compose up --build -d
```

Services:
- API: `http://localhost:8000`
- Swagger Docs: `http://localhost:8000/docs`
- LiveKit: `http://localhost:7780`
- Postgres: `localhost:5432`
- Redis: `localhost:6379`

API logs:

```bash
docker compose logs -f api
```

## Create Admin (Inside Docker)

After services are up, create the initial admin user inside the API container:

```bash
docker compose exec api python -m scripts.create_admin
```

The script asks for `username` and optional `phone_number`, creates the user with `is_admin=True`, and prints the initial `secret`.

## Admin API Hardening

- `POST /admin/users` creates only non-admin users.
- Sending `is_admin` in the payload is not allowed and returns `422`.
- Admin creation is only allowed through the server-side script: `scripts/create_admin.py`.

Invalid payload example:

```json
{
  "username": "new_user",
  "phone_number": "09123456789",
  "is_admin": true
}
```

Admin login:
- Endpoint: `POST /auth/login`
- Body:

```json
{
  "username": "admin_username",
  "secret": "printed_secret"
}
```

## Database Migrations (Alembic)

This project uses Alembic, and `env.py` reads `DATABASE_URL` from `.env`.

Apply migrations (inside Docker):

```bash
docker compose exec api alembic upgrade head
```

Create a new migration (autogenerate):

```bash
docker compose exec api alembic revision --autogenerate -m "your_message"
```

Check current DB revision:

```bash
docker compose exec api alembic current
```

Show migration history:

```bash
docker compose exec api alembic history
```

Important: run `alembic upgrade head` before starting the API in a new environment.

## Local Development (Without Docker)

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Run migrations:

```bash
alembic upgrade head
```

Start API:

```bash
uvicorn main:app --reload
```

## Realtime Notes

- Presence is stored in Redis.
- Speaker lock is managed atomically in Redis.
- LiveKit tokens for listener/speaker roles are issued during the WebSocket flow.
