{"id":28386111,"url":"https://github.com/blacksky-algorithms/safe-skies-api","last_synced_at":"2025-06-26T13:31:49.161Z","repository":{"id":282592605,"uuid":"936368388","full_name":"blacksky-algorithms/safe-skies-api","owner":"blacksky-algorithms","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-21T22:05:02.000Z","size":451,"stargazers_count":3,"open_issues_count":5,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-22T22:12:21.951Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blacksky-algorithms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-21T00:54:18.000Z","updated_at":"2025-04-21T22:05:06.000Z","dependencies_parsed_at":"2025-03-30T19:23:17.228Z","dependency_job_id":"14790ba6-c3a6-4ef1-8d77-f930ad40b27b","html_url":"https://github.com/blacksky-algorithms/safe-skies-api","commit_stats":null,"previous_names":["freedomwriter/safe-skies-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/blacksky-algorithms/safe-skies-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blacksky-algorithms%2Fsafe-skies-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blacksky-algorithms%2Fsafe-skies-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blacksky-algorithms%2Fsafe-skies-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blacksky-algorithms%2Fsafe-skies-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blacksky-algorithms","download_url":"https://codeload.github.com/blacksky-algorithms/safe-skies-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blacksky-algorithms%2Fsafe-skies-api/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262076644,"owners_count":23255042,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-30T13:10:08.505Z","updated_at":"2025-06-26T13:31:49.151Z","avatar_url":"https://github.com/blacksky-algorithms.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SAFEskies API :shield:\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\nSAFEskies 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.\n\n**Live Application**: API powers the frontend at [www.safeskies.app](https://www.safeskies.app)\n\n## Table of Contents\n\n- [SAFEskies API :shield:](#safeskies-api-shield)\n  - [Table of Contents](#table-of-contents)\n  - [⚠️ Stability Warning](#️-stability-warning)\n  - [Features](#features)\n  - [Setup](#setup)\n    - [Prerequisites](#prerequisites)\n    - [Installation](#installation)\n    - [Environment Variables](#environment-variables)\n    - [Database Migrations](#database-migrations)\n    - [Database Backups and Restoration](#database-backups-and-restoration)\n      - [How Backups Work](#how-backups-work)\n      - [Managing Backups](#managing-backups)\n      - [Restoration Process](#restoration-process)\n  - [Running the Server](#running-the-server)\n    - [Local Development Auth/Client](#local-development-authclient)\n    - [Available Scripts](#available-scripts)\n  - [API Endpoints](#api-endpoints)\n    - [Authentication](#authentication)\n    - [Moderation / Reporting](#moderation--reporting)\n  - [Testing](#testing)\n    - [Testing Strategy](#testing-strategy)\n    - [Test Structure](#test-structure)\n    - [Mocking Pattern](#mocking-pattern)\n    - [Running Tests](#running-tests)\n  - [Development Tools](#development-tools)\n  - [Contributing](#contributing)\n    - [Current Contribution Priorities](#current-contribution-priorities)\n    - [Contribution Process](#contribution-process)\n  - [License](#license)\n  - [Maintainer](#maintainer)\n\n## ⚠️ Stability Warning\n\n**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.\n\nYou should be aware of the following:\n\n- The API interfaces may change without backward compatibility\n- Database schema and data structures could be modified between versions\n- Authentication mechanisms might evolve as we stabilize the architecture\n- Documentation is still evolving along with the application\n\nWe encourage testing and feedback but recommend caution when using SAFEskies API in production environments at this stage.\n\n## Features\n\n- **OAuth Authentication:**\n  Supports BlueSky OAuth flows with persistent session storage using custom persistent stores.\n- **Profile Management:**\n  Automatically creates or updates user profiles on login.\n- **Feed Permissions:**\n  Manages feed roles with a clear hierarchy (`admin`, `mod`, `user`).\n- **Moderation Logging \u0026 Reporting:**\n  Logs moderation actions (post deletion/restoration, user bans/unbans, mod promotions/demotions) in a dedicated `logs` table and provides endpoints for reporting posts.\n- **Client Metadata Endpoint:**\n  Serves OAuth client metadata for discovery.\n- **Automated Backups:**\n  Creates automatic backups during schema migrations with easy restoration options.\n\n## Setup\n\n### Prerequisites\n\n- **Node.js** (v18 or higher recommended)\n- **PostgreSQL** (or a Supabase instance)\n- **npm** (v9 or higher)\n\n### Installation\n\n1. **Clone the Repository:**\n\n   ```bash\n   git clone https://github.com/blacksky-algorithms/safe-skies-api.git\n   cd safe-skies-api\n   ```\n\n2. **Install Dependencies:**\n\n   ```bash\n   npm install\n   ```\n\n### Environment Variables\n\nCopy the sample file and update with your configuration:\n\n```bash\ncp .env.sample .env\n```\n\nExample `.env.sample`:\n\n```bash\nPORT=5000\nPGUSER=your_PGUSER\nPGPASSWORD=your_PGPASSWORD\nPGHOST=your_PGHOST\nPGDATABASE=your_PGDATABASE\nPGPORT=your_PGPORT\n\n\n# Encryption key must be a base64-encoded 32-byte key\nENCRYPTION_KEY=your_base64_32byte_key\n\n# BlueSky / Atproto configuration\nBSKY_BASE_API_URL=https://api.bsky.app\n\n\n# Client URL to which users are redirected after authentication\nCLIENT_URL=https://your-frontend-url.com\n\n# Base URL for the backend server\nBASE_URL=https://your-backend-url.com\n\n# JWT secret key for token exchange with client\nJWT_SECRET=\n\n# RSKY Feedgen URL\nRSKY_FEEDGEN=at://did:plc:w4xbfzo7kqfes5zb7r6qv3rw/app.bsky.feed.generator/blacksky\n\n# RSKY API Key\nRSKY_API_KEY=\n```\n\n### Database Migrations\n\nThis project uses Knex.js to manage your database schema.\n\n- **Create a Migration:**\n\n  ```bash\n  npm run migrate:create migration_name\n  ```\n\n- **Run Migrations:**\n\n  ```bash\n  npm run migrate:up\n  ```\n\n- **Rollback Migrations:**\n\n  ```bash\n  npm run migrate:down\n  ```\n\nWhen running migrations down, the system automatically creates backup tables with timestamps for all affected data.\n\n### Database Backups and Restoration\n\n#### How Backups Work\n\n- Automatic backups are created before any `migrate:down` operation.\n- Each backup is tagged with a timestamp (format: YYYYMMDD_HHMMSS).\n- Backups include:\n  - Table data\n  - Enum values\n  - Constraints and indexes\n  - Row Level Security policies\n\n#### Managing Backups\n\nAvailable PostgreSQL functions:\n\n- **List Backups:**\n\n  ```sql\n  SELECT * FROM list_backups();\n  ```\n\n- **Restore a Backup:**\n\n  ```sql\n  SELECT restore_from_backup('20250222_103045');  -- Replace with your backup timestamp.\n  ```\n\n- **Clean Up Old Backups:**\n\n  ```sql\n  SELECT cleanup_old_backups(30);  -- Removes backups older than 30 days.\n  ```\n\n#### Restoration Process\n\n1. List available backups:\n\n   ```sql\n   SELECT * FROM list_backups();\n   ```\n\n2. Choose a backup timestamp.\n\n3. Restore the backup:\n\n   ```sql\n   SELECT restore_from_backup('YOUR_BACKUP_TIMESTAMP');\n   ```\n\n4. Verify your data after restoration.\n\n## Running the Server (with Docker)\n\nGetting the entire backend running with docker takes a few steps:\n\n1. 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](https://www.docker.com/)\n\n2. Update your `.env` file\n\n```diff\n- PGHOST=127.0.0.1\n+ PGHOST=db\n```\n\n3. Start the backend\n\n```bash\ndocker compose up -d\n```\n\n## Running the Server (without Docker)\n\nThe server can be run in different modes:\n\n```bash\n# Development mode with ts-node-dev (auto-reloading)\nnpm run dev\n\n# Production mode\nnpm start\n\n# Build the TypeScript code\nnpm run build\n```\n\nWhen 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).\n\n### Local Development Auth/Client\n\nFor development locally when working on auth features or the [client](https://github.com/blacksky-algorithms/SAFEskies/blob/main/.env.sample), 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:\n\n1. **Using ngrok**:\n\n   ```bash\n   # Install ngrok globally\n   npm install -g ngrok\n\n   # Expose your local server\n   ngrok http 5000\n   ```\n\n2. **Update your environment variables**:\n\n   ```node\n   BASE_URL=https://your-ngrok-url.ngrok.io\n   ```\n\nThis setup allows OAuth providers to redirect back to your local development environment.\n\n### Available Scripts\n\n- `npm run dev`: Starts the development server with auto-reloading.\n- `npm run build`: Compiles TypeScript to JavaScript.\n- `npm start`: Runs the compiled code in production.\n- `npm test`: Runs Jest tests.\n- `npm run test:watch`: Runs tests in watch mode.\n- `npm run test:coverage`: Runs tests with coverage report.\n- `npm run test:e2e`: Runs Postman collection tests using Newman.\n- `npm run lint`: Runs Biome for code quality.\n- `npm run format`: Formats code using Biome.\n- `npm run migrate:create`: Creates a new migration file.\n- `npm run migrate:up`: Runs pending migrations.\n- `npm run migrate:down`: Rolls back the most recent migration.\n\n## API Endpoints\n\n### Authentication\n\n- **GET /auth/signin**\n  Initiates the OAuth flow.\n  Query Parameters:\n\n  - `handle`: A login hint (e.g., the user handle).\n\n  Response:\n  Returns a JSON object with an authorization URL.\n\n- **GET /auth/callback**\n  Handles the OAuth callback. Processes the OAuth response, upserts the user profile, sets an HTTP-only cookie, and redirects to the client URL.\n\n- **POST /auth/logout**\n  Logs the user out by clearing the session cookie.\n\n- **GET /oauth/client-metadata.json**\n  Serves the OAuth client metadata for discovery.\n\n### Moderation / Reporting\n\n- **POST /moderation/report**\n  Accepts a JSON payload (or an array of payloads) to report a post. The payload includes:\n\n  - `targetedPostUri`\n  - `reason`\n  - `toServices` (array of moderation services)\n  - `targetedUserDid`\n  - `uri`\n  - `feedName`\n  - `additionalInfo`\n  - `action`\n  - Optional metadata fields.\n\n  Response:\n  Returns a summary of the processing of each report.\n\n**TODO:** Add detailed API documentation for each endpoint.\n\n## Testing\n\nSAFEskies API uses Jest for unit testing with a structured mocking pattern to ensure consistent and maintainable tests.\n\n### Testing Strategy\n\nThe testing approach focuses on:\n\n- **Unit tests** for individual functions and modules\n- **Integration tests** for API endpoints using Supertest\n- **Isolated testing** using comprehensive mocks\n- **Centralized mock definitions** for consistency and reusability\n- **Clear setup patterns** that follow a consistent structure\n\n### Test Structure\n\nTests are organized in the `test` directory with:\n\n- `fixtures`: Sample data for consistent test scenarios\n- `mocks`: Centralized mock definitions for various modules (database, JWT, API clients, etc.)\n- `unit`: Tests for individual functions and components\n- `integration`: End-to-end tests that verify endpoint behavior\n\n### Mocking Pattern\n\nThe project follows a consistent mocking pattern:\n\n1. **Centralized mock definitions**:\n\n   ```typescript\n   // Example from logs.mocks.ts\n   export const mockGetLogs = jest\n     .fn\u003cPromise\u003cLogEntry[]\u003e, [LogFilters]\u003e()\n     .mockResolvedValue(mockLogEntries);\n\n   export const mockCreateModerationLog = jest\n     .fn()\n     .mockResolvedValue(undefined);\n\n   // Setup function\n   export const setupLogsMocks = (): void =\u003e {\n     jest.mock(\"../../src/repos/logs\", () =\u003e ({\n       getLogs: mockGetLogs,\n       createModerationLog: mockCreateModerationLog,\n     }));\n   };\n   ```\n\n2. **Authentication mocking**:\n\n   ```typescript\n   // Example from auth.mocks.ts\n   export const mockJwtSign = jest.fn().mockReturnValue(mockToken);\n   export const mockJwtVerify = jest.fn().mockImplementation(() =\u003e adminUser);\n\n   export const setupAuthMocks = (): void =\u003e {\n     jest.mock(\"jsonwebtoken\", () =\u003e ({\n       sign: mockJwtSign,\n       verify: mockJwtVerify,\n     }));\n     // Other auth-related mocks...\n   };\n   ```\n\n3. **Express request/response mocking**:\n\n   ```typescript\n   // Example usage in controller tests\n   import {\n     createMockRequest,\n     createMockResponse,\n   } from \"../mocks/express.mock\";\n\n   it(\"should handle the request\", async () =\u003e {\n     const req = createMockRequest({\n       user: { did: mockAdmin.did },\n       query: { limit: \"10\" },\n     });\n     const res = createMockResponse();\n\n     await myController(req, res);\n\n     expect(res.status).toHaveBeenCalledWith(200);\n   });\n   ```\n\n4. **Integration testing pattern**:\n\n   ```typescript\n   // Example integration test\n   import request from \"supertest\";\n   import app from \"../../../src/app\";\n   import { setupAuthMocks, mockJwtVerify } from \"../../mocks/auth.mocks\";\n   import { setupLogsMocks } from \"../../mocks/logs.mocks\";\n\n   // Setup mocks before testing\n   setupAuthMocks();\n   setupLogsMocks();\n\n   describe(\"API Endpoint\", () =\u003e {\n     it(\"should return expected response\", async () =\u003e {\n       const response = await request(app)\n         .get(\"/api/route\")\n         .set(\"Authorization\", \"Bearer token\");\n\n       expect(response.status).toBe(200);\n     });\n   });\n   ```\n\n### Running Tests\n\nRun the test suite with one of the following commands:\n\n```bash\n# Run all tests\nnpm test\n\n# Run tests in watch mode (during development)\nnpm run test:watch\n\n# Generate coverage report\nnpm run test:coverage\n```\n\n## Development Tools\n\n- **Express**: HTTP server framework.\n- **Knex**: SQL query builder for PostgreSQL.\n- **ts-node-dev**: TypeScript execution and development environment with auto-reloading.\n- **Helmet**: Security headers.\n- **CORS**: Cross-Origin Resource Sharing.\n- **Morgan**: HTTP request logging.\n- **TypeScript**: Static typing.\n- **Jest \u0026 Supertest**: Testing.\n\u003c!-- - **Newman**: Command-line collection runner for Postman. --\u003e\n- **Biome**: Code quality.\n- **Husky**: Git hooks for code quality.\n- **Zod**: TypeScript-first schema validation.\n\n## Contributing\n\nSAFEskies welcomes community contributions, but please note our current development phase focuses on establishing stability before implementing major new features.\n\n### Current Contribution Priorities\n\n- Bug fixes and stability improvements\n- Documentation improvements\n- Test coverage expansion\n- Security enhancements\n\n### Contribution Process\n\n1. **Check Existing Issues**: Review open issues to see if your concern is already being addressed.\n\n2. **Open an Issue First**: Before submitting code changes, please open an issue to discuss your proposed changes.\n\n   - For bugs, include reproduction steps and expected behavior\n   - For features, explain the use case and implementation approach\n\n3. **Development Workflow**:\n\n   ```bash\n   # Fork and clone the repository\n   git clone https://github.com/your-username/SAFEskies.git\n   cd SAFEskies\n\n   # Create a descriptive feature branch\n   git checkout -b fix/issue-description\n\n   # Install dependencies\n   npm install\n\n   # Make your changes with tests\n   # Run tests to ensure no regressions\n   npm test\n   ```\n\n4. **Code Standards**:\n\n   - Follow existing code style patterns\n   - Include comments for complex logic\n   - Add tests for new functionality\n   - Update documentation to reflect changes\n   - Follow the established testing and mocking patterns\n\n5. **Pull Request Process**:\n   - Ensure all tests pass\n   - Reference the related issue in your PR\n   - Provide a clear description of changes\n   - Be responsive to review feedback\n\nThe maintainer will review PRs on a regular basis, prioritizing stability-focused contributions during this alpha development phase.\n\n## License\n\nThis project is licensed under the MIT License.\n\n## Maintainer\n\nMaintainer: Natalie Davis ([@codefreedomritr.bsky.social](https://bsky.app/profile/codefreedomritr.bsky.social))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblacksky-algorithms%2Fsafe-skies-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblacksky-algorithms%2Fsafe-skies-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblacksky-algorithms%2Fsafe-skies-api/lists"}