A scalable web service built using Django for managing users, cameras, and rules etc. for the scout notification camera system. This web service handles registering / logging in users, and CRUD operations for users, cameras and rules for cameras. It also serves presigned urls to the Scout camera client for uploading images to AWS S3. All endpoints are secured using JWTs and the service is currently designed to be used with postgres version 18+ because it uses the UUIDv7 function.
Scout Cam Event Processor - Processes images received from cameras and send users a push notification if the image matches one or more of the users rules.
Scout Cam Camera Client - Detects Motion and filters images using object detection and then sends the image to the event processor if an object is detected.
Scout Cam iOS App - User app for managing cameras and notifing the user of events.
| Column | Type | Notes |
|---|---|---|
id |
bigint |
Primary key, auto-increment |
public_user_id |
uuid |
UUIDv7, unique, non-editable |
email |
varchar(255) |
Unique |
first_name |
varchar(150) |
|
last_name |
varchar(150) |
|
username |
varchar(150) |
Unique |
password |
varchar(128) |
Hashed |
is_active |
boolean |
|
is_staff |
boolean |
|
is_superuser |
boolean |
|
date_joined |
timestamptz |
|
last_login |
timestamptz |
Nullable |
| Column | Type | Notes |
|---|---|---|
id |
bigint |
Primary key, auto-increment |
public_camera_id |
uuid |
UUIDv7, unique, non-editable |
owner_id |
bigint |
Foreign key → users.id |
location |
varchar(100) |
|
created_at |
timestamptz |
Auto-set on create |
| Column | Type | Notes |
|---|---|---|
id |
bigint |
Primary key, auto-increment |
public_rule_id |
uuid |
UUIDv7, unique, non-editable |
owner_id |
bigint |
Foreign key → users.id |
camera_id |
bigint |
Foreign key → camera_camera.id |
rule |
varchar(240) |
|
rule_nickname |
varchar(240) |
|
is_enabled |
boolean |
Default: true |
created_at |
timestamptz |
Auto-set on create |
| Method | URL | Name |
|---|---|---|
| POST | /v1/auth/register/ |
users:register |
| POST | /v1/auth/token/ |
users:token_obtain_pair |
| POST | /v1/auth/token/refresh/ |
users:token_refresh |
Request:
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"password": "securepassword123"
}Response 201 Created:
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"public_user_id": "019731a2-1234-7abc-8def-000000000001"
}Request:
{
"email": "jane.doe@example.com",
"password": "securepassword123"
}Response 200 OK:
{
"access": "<jwt-access-token>",
"refresh": "<jwt-refresh-token>"
}Request:
{
"refresh": "<jwt-refresh-token>"
}Response 200 OK:
{
"access": "<jwt-access-token>"
}| Method | URL | Name |
|---|---|---|
| GET | /v1/users/ |
users:user_list |
| GET | /v1/user/<uuid:public_user_id>/ |
users:user_detail |
Response 200 OK:
[
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"public_user_id": "019731a2-1234-7abc-8def-000000000001"
}
]Response 200 OK:
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"public_user_id": "019731a2-1234-7abc-8def-000000000001"
}| Method | URL | Name |
|---|---|---|
| GET, POST | /v1/cameras/ |
camera:camera-list |
| GET, PUT, PATCH, DELETE | /v1/cameras/<public_camera_id>/ |
camera:camera-detail |
Response 200 OK:
[
{
"id": 1,
"public_camera_id": "019731a2-1234-7abc-8def-000000000002",
"owner": "jane.doe@example.com",
"location": "Front Door",
"created_at": "2026-04-28T12:00:00Z"
}
]Request:
{
"location": "Front Door"
}Response 201 Created:
{
"id": 1,
"public_camera_id": "019731a2-1234-7abc-8def-000000000002",
"owner": "jane.doe@example.com",
"location": "Front Door",
"created_at": "2026-04-28T12:00:00Z"
}Response 200 OK:
{
"id": 1,
"public_camera_id": "019731a2-1234-7abc-8def-000000000002",
"owner": "jane.doe@example.com",
"location": "Front Door",
"created_at": "2026-04-28T12:00:00Z"
}Request:
{
"location": "Back Door"
}Response 200 OK:
{
"id": 1,
"public_camera_id": "019731a2-1234-7abc-8def-000000000002",
"owner": "jane.doe@example.com",
"location": "Back Door",
"created_at": "2026-04-28T12:00:00Z"
}Response 204 No Content
| Method | URL | Name |
|---|---|---|
| GET, POST | /v1/cameras/<public_camera_id>/rules/ |
rules:camera-rules-list |
| GET, PUT, PATCH, DELETE | /v1/cameras/<public_camera_id>/rules/<public_rule_id>/ |
rules:camera-rules-detail |
Response 200 OK:
[
{
"id": 1,
"public_rule_id": "019731a2-1234-7abc-8def-000000000003",
"owner": 1,
"camera": 1,
"rule": "Alert when motion detected after 10pm",
"rule_nickname": "Night Motion Alert",
"is_enabled": true,
"created_at": "2026-04-28T12:00:00Z"
}
]Request:
{
"rule": "Alert when motion detected after 10pm",
"rule_nickname": "Night Motion Alert",
"is_enabled": true
}Response 201 Created:
{
"id": 1,
"public_rule_id": "019731a2-1234-7abc-8def-000000000003",
"owner": 1,
"camera": 1,
"rule": "Alert when motion detected after 10pm",
"rule_nickname": "Night Motion Alert",
"is_enabled": true,
"created_at": "2026-04-28T12:00:00Z"
}Response 200 OK:
{
"id": 1,
"public_rule_id": "019731a2-1234-7abc-8def-000000000003",
"owner": 1,
"camera": 1,
"rule": "Alert when motion detected after 10pm",
"rule_nickname": "Night Motion Alert",
"is_enabled": true,
"created_at": "2026-04-28T12:00:00Z"
}Request:
{
"rule": "Alert when motion detected after 11pm",
"rule_nickname": "Late Night Motion Alert",
"is_enabled": true
}Response 200 OK:
{
"id": 1,
"public_rule_id": "019731a2-1234-7abc-8def-000000000003",
"owner": 1,
"camera": 1,
"rule": "Alert when motion detected after 11pm",
"rule_nickname": "Late Night Motion Alert",
"is_enabled": true,
"created_at": "2026-04-28T12:00:00Z"
}Request:
{
"is_enabled": false
}Response 200 OK:
{
"id": 1,
"public_rule_id": "019731a2-1234-7abc-8def-000000000003",
"owner": 1,
"camera": 1,
"rule": "Alert when motion detected after 11pm",
"rule_nickname": "Late Night Motion Alert",
"is_enabled": false,
"created_at": "2026-04-28T12:00:00Z"
}Response 204 No Content
| Method | URL | Name |
|---|---|---|
| POST | /v1/uploads/presign |
uploads:presigned_upload_url |
Returns a presigned S3 URL the client can use to upload an image directly to S3. The URL expires in 300 seconds.
Request:
{
"content-type": "image/jpeg"
}Supported content types: image/jpeg, image/png. Defaults to image/jpeg if omitted.
Response 200 OK:
{
"url": "https://s3.amazonaws.com/bucket/uploads/<public_user_id>/<uuid>.jpeg?...",
"key": "uploads/<public_user_id>/<uuid>.jpeg",
"expires_in": 300
}Response 400 Bad Request (unsupported content type):
{
"detail": "Unsupported content type"
}| URL | Name |
|---|---|
/admin/ |
admin:index |
/admin/auth/group/ |
admin:auth_group_changelist |
/admin/auth/group/add/ |
admin:auth_group_add |
/admin/auth/group/<id>/change/ |
admin:auth_group_change |
/admin/auth/group/<id>/delete/ |
admin:auth_group_delete |
/admin/auth/group/<id>/history/ |
admin:auth_group_history |
