SAFEskies API is the backend for SAFEskies (Software Against a Fearful Environment)—a Node.js/Express API that handles authentication, profile management, feed permissions, and moderation/reporting for the SAFEskies project. This API uses PostgreSQL (or a Supabase instance) for persistence and provides endpoints for OAuth authentication with BlueSky (Atproto), as well as endpoints to report posts, manage moderation, and more.
Live Application: API powers the frontend at www.safeskies.app
- SAFEskies API 🛡️
IMPORTANT: SAFEskies API is currently in an alpha state of development. The application is functional but subject to significant changes as we work toward a stable release.
You should be aware of the following:
- The API interfaces may change without backward compatibility
- Database schema and data structures could be modified between versions
- Authentication mechanisms might evolve as we stabilize the architecture
- Documentation is still evolving along with the application
We encourage testing and feedback but recommend caution when using SAFEskies API in production environments at this stage.
- OAuth Authentication: Supports BlueSky OAuth flows with persistent session storage using custom persistent stores.
- Profile Management: Automatically creates or updates user profiles on login.
- Feed Permissions:
Manages feed roles with a clear hierarchy (
admin
,mod
,user
). - Moderation Logging & Reporting:
Logs moderation actions (post deletion/restoration, user bans/unbans, mod promotions/demotions) in a dedicated
logs
table and provides endpoints for reporting posts. - Client Metadata Endpoint: Serves OAuth client metadata for discovery.
- Automated Backups: Creates automatic backups during schema migrations with easy restoration options.
- Node.js (v18 or higher recommended)
- PostgreSQL (or a Supabase instance)
- npm (v9 or higher)
-
Clone the Repository:
git clone https://github.com/blacksky-algorithms/safe-skies-api.git cd safe-skies-api
-
Install Dependencies:
npm install
Copy the sample file and update with your configuration:
cp .env.sample .env
Example .env.sample
:
PORT=5000
PGUSER=your_PGUSER
PGPASSWORD=your_PGPASSWORD
PGHOST=your_PGHOST
PGDATABASE=your_PGDATABASE
PGPORT=your_PGPORT
# Encryption key must be a base64-encoded 32-byte key
ENCRYPTION_KEY=your_base64_32byte_key
# BlueSky / Atproto configuration
BSKY_BASE_API_URL=https://api.bsky.app
# Client URL to which users are redirected after authentication
CLIENT_URL=https://your-frontend-url.com
# Base URL for the backend server
BASE_URL=https://your-backend-url.com
# JWT secret key for token exchange with client
JWT_SECRET=
# RSKY Feedgen URL
RSKY_FEEDGEN=at://did:plc:w4xbfzo7kqfes5zb7r6qv3rw/app.bsky.feed.generator/blacksky
# RSKY API Key
RSKY_API_KEY=
This project uses Knex.js to manage your database schema.
-
Create a Migration:
npm run migrate:create migration_name
-
Run Migrations:
npm run migrate:up
-
Rollback Migrations:
npm run migrate:down
When running migrations down, the system automatically creates backup tables with timestamps for all affected data.
- Automatic backups are created before any
migrate:down
operation. - Each backup is tagged with a timestamp (format: YYYYMMDD_HHMMSS).
- Backups include:
- Table data
- Enum values
- Constraints and indexes
- Row Level Security policies
Available PostgreSQL functions:
-
List Backups:
SELECT * FROM list_backups();
-
Restore a Backup:
SELECT restore_from_backup('20250222_103045'); -- Replace with your backup timestamp.
-
Clean Up Old Backups:
SELECT cleanup_old_backups(30); -- Removes backups older than 30 days.
-
List available backups:
SELECT * FROM list_backups();
-
Choose a backup timestamp.
-
Restore the backup:
SELECT restore_from_backup('YOUR_BACKUP_TIMESTAMP');
-
Verify your data after restoration.
Getting the entire backend running with docker takes a few steps:
-
If you don't have Docker installed or would like to proceed without it you can skip this part and continue following the steps below. If you would like to try running the backend with Docker you can install it here
-
Update your
.env
file
- PGHOST=127.0.0.1
+ PGHOST=db
- Start the backend
docker compose up -d
The server can be run in different modes:
# Development mode with ts-node-dev (auto-reloading)
npm run dev
# Production mode
npm start
# Build the TypeScript code
npm run build
When running in development mode with npm run dev
, ts-node-dev will automatically restart the server when changes are made. The server will run on the port specified in your .env
file (default is 5000).
For development locally when working on auth features or the client, you'll need to expose your local server to the public internet and update your client NEXT_PUBLIC_SAFE_SKIES_API
environment variable with the ngrok url:
-
Using ngrok:
# Install ngrok globally npm install -g ngrok # Expose your local server ngrok http 5000
-
Update your environment variables:
BASE_URL=https://your-ngrok-url.ngrok.io
This setup allows OAuth providers to redirect back to your local development environment.
npm run dev
: Starts the development server with auto-reloading.npm run build
: Compiles TypeScript to JavaScript.npm start
: Runs the compiled code in production.npm test
: Runs Jest tests.npm run test:watch
: Runs tests in watch mode.npm run test:coverage
: Runs tests with coverage report.npm run test:e2e
: Runs Postman collection tests using Newman.npm run lint
: Runs Biome for code quality.npm run format
: Formats code using Biome.npm run migrate:create
: Creates a new migration file.npm run migrate:up
: Runs pending migrations.npm run migrate:down
: Rolls back the most recent migration.
-
GET /auth/signin Initiates the OAuth flow. Query Parameters:
handle
: A login hint (e.g., the user handle).
Response: Returns a JSON object with an authorization URL.
-
GET /auth/callback Handles the OAuth callback. Processes the OAuth response, upserts the user profile, sets an HTTP-only cookie, and redirects to the client URL.
-
POST /auth/logout Logs the user out by clearing the session cookie.
-
GET /oauth/client-metadata.json Serves the OAuth client metadata for discovery.
-
POST /moderation/report Accepts a JSON payload (or an array of payloads) to report a post. The payload includes:
targetedPostUri
reason
toServices
(array of moderation services)targetedUserDid
uri
feedName
additionalInfo
action
- Optional metadata fields.
Response: Returns a summary of the processing of each report.
TODO: Add detailed API documentation for each endpoint.
SAFEskies API uses Jest for unit testing with a structured mocking pattern to ensure consistent and maintainable tests.
The testing approach focuses on:
- Unit tests for individual functions and modules
- Integration tests for API endpoints using Supertest
- Isolated testing using comprehensive mocks
- Centralized mock definitions for consistency and reusability
- Clear setup patterns that follow a consistent structure
Tests are organized in the test
directory with:
fixtures
: Sample data for consistent test scenariosmocks
: Centralized mock definitions for various modules (database, JWT, API clients, etc.)unit
: Tests for individual functions and componentsintegration
: End-to-end tests that verify endpoint behavior
The project follows a consistent mocking pattern:
-
Centralized mock definitions:
// Example from logs.mocks.ts export const mockGetLogs = jest .fn<Promise<LogEntry[]>, [LogFilters]>() .mockResolvedValue(mockLogEntries); export const mockCreateModerationLog = jest .fn() .mockResolvedValue(undefined); // Setup function export const setupLogsMocks = (): void => { jest.mock("../../src/repos/logs", () => ({ getLogs: mockGetLogs, createModerationLog: mockCreateModerationLog, })); };
-
Authentication mocking:
// Example from auth.mocks.ts export const mockJwtSign = jest.fn().mockReturnValue(mockToken); export const mockJwtVerify = jest.fn().mockImplementation(() => adminUser); export const setupAuthMocks = (): void => { jest.mock("jsonwebtoken", () => ({ sign: mockJwtSign, verify: mockJwtVerify, })); // Other auth-related mocks... };
-
Express request/response mocking:
// Example usage in controller tests import { createMockRequest, createMockResponse, } from "../mocks/express.mock"; it("should handle the request", async () => { const req = createMockRequest({ user: { did: mockAdmin.did }, query: { limit: "10" }, }); const res = createMockResponse(); await myController(req, res); expect(res.status).toHaveBeenCalledWith(200); });
-
Integration testing pattern:
// Example integration test import request from "supertest"; import app from "../../../src/app"; import { setupAuthMocks, mockJwtVerify } from "../../mocks/auth.mocks"; import { setupLogsMocks } from "../../mocks/logs.mocks"; // Setup mocks before testing setupAuthMocks(); setupLogsMocks(); describe("API Endpoint", () => { it("should return expected response", async () => { const response = await request(app) .get("/api/route") .set("Authorization", "Bearer token"); expect(response.status).toBe(200); }); });
Run the test suite with one of the following commands:
# Run all tests
npm test
# Run tests in watch mode (during development)
npm run test:watch
# Generate coverage report
npm run test:coverage
- Express: HTTP server framework.
- Knex: SQL query builder for PostgreSQL.
- ts-node-dev: TypeScript execution and development environment with auto-reloading.
- Helmet: Security headers.
- CORS: Cross-Origin Resource Sharing.
- Morgan: HTTP request logging.
- TypeScript: Static typing.
- Jest & Supertest: Testing.
- Biome: Code quality.
- Husky: Git hooks for code quality.
- Zod: TypeScript-first schema validation.
SAFEskies welcomes community contributions, but please note our current development phase focuses on establishing stability before implementing major new features.
- Bug fixes and stability improvements
- Documentation improvements
- Test coverage expansion
- Security enhancements
-
Check Existing Issues: Review open issues to see if your concern is already being addressed.
-
Open an Issue First: Before submitting code changes, please open an issue to discuss your proposed changes.
- For bugs, include reproduction steps and expected behavior
- For features, explain the use case and implementation approach
-
Development Workflow:
# Fork and clone the repository git clone https://github.com/your-username/SAFEskies.git cd SAFEskies # Create a descriptive feature branch git checkout -b fix/issue-description # Install dependencies npm install # Make your changes with tests # Run tests to ensure no regressions npm test
-
Code Standards:
- Follow existing code style patterns
- Include comments for complex logic
- Add tests for new functionality
- Update documentation to reflect changes
- Follow the established testing and mocking patterns
-
Pull Request Process:
- Ensure all tests pass
- Reference the related issue in your PR
- Provide a clear description of changes
- Be responsive to review feedback
The maintainer will review PRs on a regular basis, prioritizing stability-focused contributions during this alpha development phase.
This project is licensed under the MIT License.
Maintainer: Natalie Davis (@codefreedomritr.bsky.social)