605 lines
12 KiB
Markdown
605 lines
12 KiB
Markdown
# Milestone 6: App Store Backend API
|
|
|
|
**Status**: Planning
|
|
**Goal**: REST API for app submission, review, and distribution.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The backend API serves the developer portal, CLI tools, and device-side app management. It handles authentication, app lifecycle, file storage, and telemetry ingestion.
|
|
|
|
---
|
|
|
|
## API Design Principles
|
|
|
|
1. **RESTful** - Standard HTTP methods and status codes
|
|
2. **JSON** - Request and response bodies in JSON
|
|
3. **Versioned** - `/v1/` prefix for breaking changes
|
|
4. **Consistent** - Same patterns across all endpoints
|
|
5. **Documented** - OpenAPI specification
|
|
|
|
---
|
|
|
|
## Base URL
|
|
|
|
```
|
|
Production: https://api.mosis.dev/v1
|
|
Staging: https://api.staging.mosis.dev/v1
|
|
Local: http://localhost:8080/v1
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### Headers
|
|
|
|
```
|
|
Authorization: Bearer <jwt_token>
|
|
# or
|
|
X-API-Key: mk_live_xxxxxxxx
|
|
```
|
|
|
|
### Scopes
|
|
|
|
| Scope | Description |
|
|
|-------|-------------|
|
|
| `apps:read` | Read app metadata |
|
|
| `apps:write` | Create/update apps |
|
|
| `versions:upload` | Upload new versions |
|
|
| `versions:publish` | Publish versions |
|
|
| `telemetry:read` | Read analytics |
|
|
| `keys:manage` | Manage API keys |
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### Authentication
|
|
|
|
```yaml
|
|
POST /v1/auth/oauth/github:
|
|
summary: Start GitHub OAuth flow
|
|
response: { redirect_url: string }
|
|
|
|
GET /v1/auth/oauth/github/callback:
|
|
summary: GitHub OAuth callback
|
|
query:
|
|
code: string
|
|
state: string
|
|
response: { access_token, refresh_token, user }
|
|
|
|
POST /v1/auth/oauth/google:
|
|
summary: Start Google OAuth flow
|
|
response: { redirect_url: string }
|
|
|
|
GET /v1/auth/oauth/google/callback:
|
|
summary: Google OAuth callback
|
|
|
|
POST /v1/auth/refresh:
|
|
summary: Refresh access token
|
|
body: { refresh_token: string }
|
|
response: { access_token, refresh_token }
|
|
|
|
POST /v1/auth/logout:
|
|
summary: Invalidate tokens
|
|
auth: required
|
|
|
|
GET /v1/auth/me:
|
|
summary: Get current user
|
|
auth: required
|
|
response: Developer
|
|
```
|
|
|
|
### Apps
|
|
|
|
```yaml
|
|
GET /v1/apps:
|
|
summary: List developer's apps
|
|
auth: required
|
|
query:
|
|
status: draft | published | suspended
|
|
page: number
|
|
limit: number
|
|
response: { apps: App[], total: number }
|
|
|
|
POST /v1/apps:
|
|
summary: Create new app
|
|
auth: required
|
|
body:
|
|
package_id: string # com.developer.appname
|
|
name: string
|
|
description?: string
|
|
category?: string
|
|
response: App
|
|
|
|
GET /v1/apps/:id:
|
|
summary: Get app details
|
|
auth: required
|
|
response: App
|
|
|
|
PATCH /v1/apps/:id:
|
|
summary: Update app metadata
|
|
auth: required
|
|
body:
|
|
name?: string
|
|
description?: string
|
|
category?: string
|
|
tags?: string[]
|
|
response: App
|
|
|
|
DELETE /v1/apps/:id:
|
|
summary: Delete app (if no published versions)
|
|
auth: required
|
|
response: { success: true }
|
|
```
|
|
|
|
### App Versions
|
|
|
|
```yaml
|
|
GET /v1/apps/:id/versions:
|
|
summary: List app versions
|
|
auth: required
|
|
query:
|
|
status: draft | review | approved | published | rejected
|
|
page: number
|
|
limit: number
|
|
response: { versions: AppVersion[], total: number }
|
|
|
|
POST /v1/apps/:id/versions:
|
|
summary: Create new version (get upload URL)
|
|
auth: required
|
|
body:
|
|
version_name: string # 1.0.0
|
|
version_code: number # 1
|
|
release_notes?: string
|
|
response:
|
|
version: AppVersion
|
|
upload_url: string # Presigned S3 URL
|
|
upload_expires: string # ISO timestamp
|
|
|
|
PUT /v1/apps/:id/versions/:vid/upload-complete:
|
|
summary: Mark upload as complete, trigger validation
|
|
auth: required
|
|
response: AppVersion
|
|
|
|
GET /v1/apps/:id/versions/:vid:
|
|
summary: Get version details
|
|
auth: required
|
|
response: AppVersion
|
|
|
|
POST /v1/apps/:id/versions/:vid/submit:
|
|
summary: Submit version for review
|
|
auth: required
|
|
response: AppVersion # status: review
|
|
|
|
POST /v1/apps/:id/versions/:vid/publish:
|
|
summary: Publish approved version
|
|
auth: required
|
|
response: AppVersion # status: published
|
|
|
|
DELETE /v1/apps/:id/versions/:vid:
|
|
summary: Delete draft version
|
|
auth: required
|
|
response: { success: true }
|
|
```
|
|
|
|
### Public App Store
|
|
|
|
```yaml
|
|
GET /v1/store/apps:
|
|
summary: Browse/search published apps
|
|
auth: none
|
|
query:
|
|
q: string # Search query
|
|
category: string
|
|
sort: popular | recent | name
|
|
page: number
|
|
limit: number
|
|
response: { apps: PublicApp[], total: number }
|
|
|
|
GET /v1/store/apps/:package_id:
|
|
summary: Get app store listing
|
|
auth: none
|
|
response: PublicApp
|
|
|
|
GET /v1/store/apps/:package_id/download:
|
|
summary: Get download URL for latest version
|
|
auth: none (or device token)
|
|
response:
|
|
download_url: string
|
|
version: string
|
|
size: number
|
|
signature: string
|
|
|
|
GET /v1/store/apps/:package_id/versions/:version_code/download:
|
|
summary: Get download URL for specific version
|
|
auth: none
|
|
response: { download_url, version, size, signature }
|
|
```
|
|
|
|
### API Keys
|
|
|
|
```yaml
|
|
GET /v1/keys:
|
|
summary: List API keys
|
|
auth: required
|
|
response: { keys: APIKey[] }
|
|
|
|
POST /v1/keys:
|
|
summary: Create API key
|
|
auth: required
|
|
body:
|
|
name: string
|
|
permissions: string[]
|
|
expires_at?: string
|
|
response:
|
|
key: APIKey
|
|
secret: string # Only shown once!
|
|
|
|
DELETE /v1/keys/:id:
|
|
summary: Revoke API key
|
|
auth: required
|
|
response: { success: true }
|
|
```
|
|
|
|
### Signing Keys
|
|
|
|
```yaml
|
|
GET /v1/signing-keys:
|
|
summary: List signing keys
|
|
auth: required
|
|
response: { keys: SigningKey[] }
|
|
|
|
POST /v1/signing-keys:
|
|
summary: Register signing key
|
|
auth: required
|
|
body:
|
|
name: string
|
|
public_key: string # PEM format
|
|
response: SigningKey
|
|
|
|
DELETE /v1/signing-keys/:id:
|
|
summary: Revoke signing key
|
|
auth: required
|
|
response: { success: true }
|
|
```
|
|
|
|
### Telemetry
|
|
|
|
```yaml
|
|
POST /v1/telemetry/events:
|
|
summary: Submit telemetry events (batch)
|
|
auth: device token or API key
|
|
body:
|
|
events:
|
|
- app_id: string
|
|
event_type: string
|
|
event_data: object
|
|
timestamp: string
|
|
response: { received: number }
|
|
|
|
POST /v1/telemetry/crash:
|
|
summary: Submit crash report
|
|
auth: device token or API key
|
|
body:
|
|
app_id: string
|
|
app_version: string
|
|
crash_type: string
|
|
message: string
|
|
stack_trace: string
|
|
context: object
|
|
timestamp: string
|
|
response: { id: string }
|
|
|
|
GET /v1/apps/:id/analytics:
|
|
summary: Get app analytics
|
|
auth: required
|
|
query:
|
|
start_date: string
|
|
end_date: string
|
|
metrics: downloads | active_users | crashes
|
|
response:
|
|
data:
|
|
- date: string
|
|
downloads: number
|
|
active_users: number
|
|
crashes: number
|
|
|
|
GET /v1/apps/:id/crashes:
|
|
summary: Get crash reports
|
|
auth: required
|
|
query:
|
|
version?: string
|
|
page: number
|
|
limit: number
|
|
response: { crashes: CrashReport[], total: number }
|
|
```
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### Developer
|
|
|
|
```typescript
|
|
interface Developer {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
avatar_url?: string;
|
|
verified: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
```
|
|
|
|
### App
|
|
|
|
```typescript
|
|
interface App {
|
|
id: string;
|
|
package_id: string;
|
|
name: string;
|
|
description?: string;
|
|
category?: string;
|
|
tags: string[];
|
|
status: 'draft' | 'published' | 'suspended';
|
|
icon_url?: string;
|
|
latest_version?: AppVersion;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
```
|
|
|
|
### AppVersion
|
|
|
|
```typescript
|
|
interface AppVersion {
|
|
id: string;
|
|
app_id: string;
|
|
version_name: string;
|
|
version_code: number;
|
|
package_url?: string;
|
|
package_size?: number;
|
|
signature?: string;
|
|
permissions: string[];
|
|
min_mosis_version?: string;
|
|
release_notes?: string;
|
|
status: 'draft' | 'uploading' | 'validating' | 'review' | 'approved' | 'published' | 'rejected';
|
|
review_notes?: string;
|
|
published_at?: string;
|
|
created_at: string;
|
|
}
|
|
```
|
|
|
|
### PublicApp
|
|
|
|
```typescript
|
|
interface PublicApp {
|
|
package_id: string;
|
|
name: string;
|
|
description?: string;
|
|
category?: string;
|
|
tags: string[];
|
|
icon_url?: string;
|
|
author_name: string;
|
|
latest_version: string;
|
|
download_count: number;
|
|
rating?: number;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
```
|
|
|
|
### APIKey
|
|
|
|
```typescript
|
|
interface APIKey {
|
|
id: string;
|
|
name: string;
|
|
key_prefix: string; // "mk_live_abc..."
|
|
permissions: string[];
|
|
last_used_at?: string;
|
|
expires_at?: string;
|
|
created_at: string;
|
|
}
|
|
```
|
|
|
|
### CrashReport
|
|
|
|
```typescript
|
|
interface CrashReport {
|
|
id: string;
|
|
app_id: string;
|
|
app_version: string;
|
|
crash_type: string;
|
|
message: string;
|
|
stack_trace: string;
|
|
context: object;
|
|
occurrences: number;
|
|
first_seen: string;
|
|
last_seen: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Error Response Format
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "VALIDATION_ERROR",
|
|
"message": "Invalid package_id format",
|
|
"details": {
|
|
"field": "package_id",
|
|
"constraint": "Must match pattern: ^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+$"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Codes
|
|
|
|
| Code | HTTP Status | Description |
|
|
|------|-------------|-------------|
|
|
| `UNAUTHORIZED` | 401 | Missing or invalid auth |
|
|
| `FORBIDDEN` | 403 | Insufficient permissions |
|
|
| `NOT_FOUND` | 404 | Resource not found |
|
|
| `VALIDATION_ERROR` | 400 | Invalid request body |
|
|
| `CONFLICT` | 409 | Resource already exists |
|
|
| `RATE_LIMITED` | 429 | Too many requests |
|
|
| `INTERNAL_ERROR` | 500 | Server error |
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
### Headers
|
|
|
|
```
|
|
X-RateLimit-Limit: 1000
|
|
X-RateLimit-Remaining: 999
|
|
X-RateLimit-Reset: 1704067200
|
|
```
|
|
|
|
### Limits
|
|
|
|
| Endpoint Category | Limit | Window |
|
|
|-------------------|-------|--------|
|
|
| Auth | 10 | 1 minute |
|
|
| Read | 1000 | 1 hour |
|
|
| Write | 100 | 1 hour |
|
|
| Upload | 10 | 1 hour |
|
|
| Telemetry | 10000 | 1 hour |
|
|
|
|
---
|
|
|
|
## Pagination
|
|
|
|
### Request
|
|
|
|
```
|
|
GET /v1/apps?page=2&limit=20
|
|
```
|
|
|
|
### Response
|
|
|
|
```json
|
|
{
|
|
"apps": [...],
|
|
"pagination": {
|
|
"page": 2,
|
|
"limit": 20,
|
|
"total": 45,
|
|
"total_pages": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Webhooks (Future)
|
|
|
|
```yaml
|
|
POST /v1/webhooks:
|
|
summary: Register webhook
|
|
body:
|
|
url: string
|
|
events: string[] # version.published, crash.new
|
|
secret: string
|
|
|
|
Webhook Payload:
|
|
headers:
|
|
X-Mosis-Signature: sha256=xxx
|
|
body:
|
|
event: string
|
|
data: object
|
|
timestamp: string
|
|
```
|
|
|
|
---
|
|
|
|
## OpenAPI Specification
|
|
|
|
Full OpenAPI 3.0 spec will be generated and hosted at:
|
|
- `https://api.mosis.dev/v1/openapi.json`
|
|
- `https://api.mosis.dev/v1/docs` (Swagger UI)
|
|
|
|
---
|
|
|
|
## Implementation Structure
|
|
|
|
```
|
|
src/
|
|
├── main.go (or index.ts)
|
|
├── api/
|
|
│ ├── routes.go
|
|
│ ├── middleware/
|
|
│ │ ├── auth.go
|
|
│ │ ├── ratelimit.go
|
|
│ │ └── logging.go
|
|
│ └── handlers/
|
|
│ ├── auth.go
|
|
│ ├── apps.go
|
|
│ ├── versions.go
|
|
│ ├── store.go
|
|
│ ├── keys.go
|
|
│ └── telemetry.go
|
|
├── service/
|
|
│ ├── app_service.go
|
|
│ ├── version_service.go
|
|
│ ├── auth_service.go
|
|
│ └── storage_service.go
|
|
├── repository/
|
|
│ ├── app_repo.go
|
|
│ ├── version_repo.go
|
|
│ └── developer_repo.go
|
|
├── domain/
|
|
│ ├── app.go
|
|
│ ├── version.go
|
|
│ └── developer.go
|
|
└── pkg/
|
|
├── validator/
|
|
└── crypto/
|
|
```
|
|
|
|
---
|
|
|
|
## Deliverables
|
|
|
|
- [ ] OpenAPI specification
|
|
- [ ] Authentication middleware
|
|
- [ ] Rate limiting middleware
|
|
- [ ] Auth endpoints
|
|
- [ ] Apps CRUD endpoints
|
|
- [ ] Versions endpoints with upload flow
|
|
- [ ] Store public endpoints
|
|
- [ ] API keys management
|
|
- [ ] Signing keys management
|
|
- [ ] Telemetry ingestion
|
|
- [ ] Error handling
|
|
- [ ] Request validation
|
|
- [ ] Integration tests
|
|
|
|
---
|
|
|
|
## Open Questions
|
|
|
|
1. GraphQL alongside REST?
|
|
2. WebSocket for real-time review status?
|
|
3. Batch operations for bulk updates?
|
|
4. API versioning strategy (URL vs header)?
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [REST API Design Guidelines](https://github.com/microsoft/api-guidelines)
|
|
- [OpenAPI 3.0 Specification](https://swagger.io/specification/)
|
|
- [HTTP Status Codes](https://httpstatuses.com/)
|