diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 37aa223db..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,42 +0,0 @@ -# Copilot instructions for editor-api - -This repo is a Rails 7.1 exposing REST and GraphQL APIs for the Raspberry Pi Foundation Code Editor and Code Editor for Education features. - -Architecture and boundaries -- HTTP surfaces: REST under `app/controllers/api/**` (responses via jbuilder in `app/views/api/**`) and GraphQL at `/graphql` (schema in `app/graphql/**`). -- Authentication: Browser/session via OmniAuth (OIDC to Hydra) in `config/initializers/omniauth.rb` and `AuthController`; API token via `Authorization: Bearer ` with lookup in `Identifiable#identify_user` → `User.from_token` → `HydraPublicApiClient`. -- Authorization: `cancancan` in `app/models/ability.rb`. Permissions differ for students/teachers/owners/admin. Use `load_and_authorize_resource` in controllers and `Types::ProjectType.authorized?` plus `GraphqlController` context `current_ability` for GraphQL. -- Domain: `Project` (+ `Component`) with attachments (`images/videos/audio` via Active Storage). Higher-level operations live under `lib/concepts/**` (e.g., `Project::Create`, `Project::Update`, `Project::CreateRemix`). Prefer calling these from controllers/mutations. -- Jobs: GoodJob (`config/initializers/good_job.rb`, Procfile `worker`). Admin UI is mounted at `/admin/good_job` and gated by `AuthenticationHelper#current_user.admin?`. -- Integrations: Profile API (`lib/profile_api_client.rb`) for schools/students and safeguarding flags; UserInfo API for user detail fan-out; GitHub GraphQL client in `lib/github_api.rb`; GitHub webhooks via `GithubWebhooksController` trigger `UploadJob` when `ref == ENV['GITHUB_WEBHOOK_REF']` and files under `*/code/` change. -- Storage/CORS: Active Storage uses S3 in non-dev; `config/initializers/cors.rb` and `lib/origin_parser.rb` parse `ALLOWED_ORIGINS`. `CorpMiddleware` sets CORP for Active Storage routes. - -Key conventions and patterns -- GraphQL context includes: `current_user`, `current_ability`, and `remix_origin` (see `GraphqlController`). Max depth/complexity guard rails in `EditorApiSchema`. -- GraphQL object IDs use GlobalID; locale fallback for projects via `ProjectLoader` in order `[requested, 'en', nil]`. -- Jbuilder responses: see `app/views/api/projects/show.json.jbuilder` for shape (components, media URLs via `rails_blob_url`, optional `parent`). -- Pagination for REST lists returns HTTP `Link` header (see `Api::ProjectsController#pagination_link_header`). -- Project rules: identifiers unique per locale; default component’s name/extension immutable on update; students cannot update `instructions` on school projects; creating a project within a school auto-builds a `SchoolProject`. -- Remix: `Project::CreateRemix` clones media/components, sets `remix_origin` from `request.origin` and clears `lesson_id`. -- Errors: domain ops return `OperationResponse` with `:error`; controllers return 4xx heads for common cases; GraphQL raises `GraphQL::ExecutionError`. Exceptions are reported to Sentry. - -Developer workflows (docker-first) -- Setup: copy `.env.example` → `.env`. Build with `docker-compose build`. Prepare DB: `docker compose run --rm api rails db:setup`. -- Run: `docker-compose up` (API on http://localhost:3009). GraphiQL available in non-production at `/graphql`. -- Tests: `docker-compose run api rspec` (or pass a spec path). Bullet, WebMock, and RSpec rails are configured in `spec/rails_helper.rb`. -- Seeds: run `projects:create_all` and `for_education:*` Rake tasks (examples in README). Experience CS examples auto-run on release (see Procfile `release`). -- DB sync: scripts in `bin/db-sync/*` pull Heroku backups into your local Docker DB and reset Active Storage to `local`. -- Gems: update inside the builder image with `./bin/with-builder.sh bundle update`. - -Important env vars (see `.env.example`) -- Postgres: `POSTGRES_HOST/DB/USER/PASSWORD`. Hydra/identity: `HYDRA_PUBLIC_URL`, `HYDRA_PUBLIC_API_URL`, `HYDRA_PUBLIC_TOKEN_URL`, `HYDRA_CLIENT_ID/SECRET`, `IDENTITY_URL`, `HOST_URL`. -- External APIs: `USERINFO_API_URL/KEY`, `PROFILE_API_KEY`. Webhooks: `GITHUB_WEBHOOK_SECRET`, `GITHUB_WEBHOOK_REF`, optional `GITHUB_AUTH_TOKEN` for GitHub GraphQL. -- CORS/storage: `ALLOWED_ORIGINS`, `AWS_*` (S3). Local auth/dev: `BYPASS_OAUTH=true` to stub identity; `SMEE_TUNNEL` for local webhook relay. - -Examples to follow -- REST: `GET /api/projects/:id` resolves by identifier+locale (uses `ProjectLoader`); `POST /api/projects/:project_id/images` attaches files; `POST /api/projects/:project_id/remix` creates a remix (requires auth). -- GraphQL query snippet: `projects(userId: "") { edges { node { identifier name components { nodes { name extension } } } } }` and `project(identifier: "abc123") { name images { nodes { filename } } }`. - -Where to look first -- Routes: `config/routes.rb`. Auth: `config/initializers/omniauth.rb`, `app/helpers/authentication_helper.rb`, `app/controllers/concerns/identifiable.rb`. -- Permissions: `app/models/ability.rb`. Domain ops: `lib/concepts/**`. Data models: `app/models/**`. API views: `app/views/api/**`. GraphQL types/mutations: `app/graphql/**`. \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7b47a2cff --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# Project Overview +- Rails 7.1 monolith for the Raspberry Pi Code Editor API (REST + GraphQL), served at `editor-api.raspberrypi.org`. +- Primary runtime via Docker; API listens on port 3009. + +## Architecture +- **REST** under `app/controllers/api/**` with Jbuilder views in `app/views/api/**`; **GraphQL** at `/graphql` (schema in `app/graphql/**`). +- **Auth**: Browser/session via OmniAuth (OIDC to Hydra); API token via `Authorization: Bearer` with `Identifiable#identify_user` → `User.from_token` → `HydraPublicApiClient`. +- **Authorization**: cancancan in `app/models/ability.rb`. Use `load_and_authorize_resource` in controllers; GraphQL uses `Types::ProjectType.authorized?` and `current_ability` in context. +- **Domain**: `Project` (+ `Component`) with Active Storage attachments. Domain operations in `lib/concepts/**` (e.g. `Project::Create`, `Project::CreateRemix`). Prefer calling these from controllers/mutations. +- **Jobs**: GoodJob (`bundle exec good_job start --max-threads=8`). Admin UI at `/admin/good_job`. +- **Integrations**: Profile API (`lib/profile_api_client.rb`), UserInfo API, GitHub GraphQL (`lib/github_api.rb`), GitHub webhooks via `GithubWebhooksController`. +- **Storage/CORS**: Active Storage uses S3 in non-dev. CORS via `config/initializers/cors.rb` and `lib/origin_parser.rb`. `CorpMiddleware` sets CORP for Active Storage routes. + +## Key Conventions +- GraphQL context: `current_user`, `current_ability`, `remix_origin`. Object IDs use GlobalID. Locale fallback via `ProjectLoader`: `[requested, 'en', nil]`. +- REST pagination returns HTTP `Link` header (see `Api::ProjectsController#pagination_link_header`). +- Project rules: identifiers unique per locale; default component name/extension immutable on update; students cannot update `instructions` on school projects; creating a project in a school auto-builds `SchoolProject`. +- Remix: `Project::CreateRemix` clones media/components, sets `remix_origin`, clears `lesson_id`. +- Errors: domain ops return `OperationResponse` with `:error`; controllers return 4xx heads; GraphQL raises `GraphQL::ExecutionError`. Exceptions reported to Sentry. +- snake_case for variable numbers (exceptions: `sha256`, `X-Hub-Signature-256`). + +## Quickstart +```bash +cp .env.example .env +docker compose build +docker compose run --rm api rails db:setup +docker compose up +``` + +## Development +- Use `docker compose` for all commands; project mounts into `editor-api:builder` with tmpfs for `tmp/`. + +## Testing +- Full suite: `docker compose run --rm api rspec` +- Single spec: `docker compose run --rm api rspec spec/path/to/spec.rb` +- Lint: `docker compose run --rm api bundle exec rubocop` +- CI: CircleCI with Ruby 3.2, Postgres 12, Redis. + +## Where to Look First +- Routes: `config/routes.rb`. Auth: `config/initializers/omniauth.rb`, `app/helpers/authentication_helper.rb`, `app/controllers/concerns/identifiable.rb`. +- Permissions: `app/models/ability.rb`. Domain ops: `lib/concepts/**`. Models: `app/models/**`. GraphQL: `app/graphql/**`. + +## Security +- Never commit secrets (`.env`, `config/master.key`, API tokens, webhook secrets). +- `.env.example` contains placeholder values only.