Skip to content

Commit c083c02

Browse files
committed
solving merge conflicts with main
2 parents 1ec785f + a44daab commit c083c02

14 files changed

Lines changed: 221 additions & 22 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@
6767
"shopify.ruby-lsp",
6868
"koichisasada.vscode-rdbg",
6969
"postman.postman-for-vscode",
70-
"ninoseki.vscode-mogami"
70+
"ninoseki.vscode-mogami",
71+
"GitHub.copilot",
72+
"GitHub.copilot-chat"
7173
],
7274
"settings": {
7375
"terminal.integrated.defaultProfile.linux": "zsh",
@@ -79,7 +81,7 @@
7981
}
8082
}
8183
}
82-
}
84+
},
8385

8486
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
8587
// "remoteUser": "devcontainer"

.github/copilot-instructions.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copilot instructions for editor-api
2+
3+
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.
4+
5+
Architecture and boundaries
6+
- HTTP surfaces: REST under `app/controllers/api/**` (responses via jbuilder in `app/views/api/**`) and GraphQL at `/graphql` (schema in `app/graphql/**`).
7+
- Authentication: Browser/session via OmniAuth (OIDC to Hydra) in `config/initializers/omniauth.rb` and `AuthController`; API token via `Authorization: Bearer <token>` with lookup in `Identifiable#identify_user``User.from_token``HydraPublicApiClient`.
8+
- 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.
9+
- 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.
10+
- Jobs: GoodJob (`config/initializers/good_job.rb`, Procfile `worker`). Admin UI is mounted at `/admin/good_job` and gated by `AuthenticationHelper#current_user.admin?`.
11+
- 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.
12+
- 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.
13+
14+
Key conventions and patterns
15+
- GraphQL context includes: `current_user`, `current_ability`, and `remix_origin` (see `GraphqlController`). Max depth/complexity guard rails in `EditorApiSchema`.
16+
- GraphQL object IDs use GlobalID; locale fallback for projects via `ProjectLoader` in order `[requested, 'en', nil]`.
17+
- Jbuilder responses: see `app/views/api/projects/show.json.jbuilder` for shape (components, media URLs via `rails_blob_url`, optional `parent`).
18+
- Pagination for REST lists returns HTTP `Link` header (see `Api::ProjectsController#pagination_link_header`).
19+
- 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`.
20+
- Remix: `Project::CreateRemix` clones media/components, sets `remix_origin` from `request.origin` and clears `lesson_id`.
21+
- Errors: domain ops return `OperationResponse` with `:error`; controllers return 4xx heads for common cases; GraphQL raises `GraphQL::ExecutionError`. Exceptions are reported to Sentry.
22+
23+
Developer workflows (docker-first)
24+
- Setup: copy `.env.example``.env`. Build with `docker-compose build`. Prepare DB: `docker compose run --rm api rails db:setup`.
25+
- Run: `docker-compose up` (API on http://localhost:3009). GraphiQL available in non-production at `/graphql`.
26+
- Tests: `docker-compose run api rspec` (or pass a spec path). Bullet, WebMock, and RSpec rails are configured in `spec/rails_helper.rb`.
27+
- Seeds: run `projects:create_all` and `for_education:*` Rake tasks (examples in README). Experience CS examples auto-run on release (see Procfile `release`).
28+
- DB sync: scripts in `bin/db-sync/*` pull Heroku backups into your local Docker DB and reset Active Storage to `local`.
29+
- Gems: update inside the builder image with `./bin/with-builder.sh bundle update`.
30+
31+
Important env vars (see `.env.example`)
32+
- 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`.
33+
- External APIs: `USERINFO_API_URL/KEY`, `PROFILE_API_KEY`. Webhooks: `GITHUB_WEBHOOK_SECRET`, `GITHUB_WEBHOOK_REF`, optional `GITHUB_AUTH_TOKEN` for GitHub GraphQL.
34+
- CORS/storage: `ALLOWED_ORIGINS`, `AWS_*` (S3). Local auth/dev: `BYPASS_OAUTH=true` to stub identity; `SMEE_TUNNEL` for local webhook relay.
35+
36+
Examples to follow
37+
- 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).
38+
- GraphQL query snippet: `projects(userId: "<uuid>") { edges { node { identifier name components { nodes { name extension } } } } }` and `project(identifier: "abc123") { name images { nodes { filename } } }`.
39+
40+
Where to look first
41+
- Routes: `config/routes.rb`. Auth: `config/initializers/omniauth.rb`, `app/helpers/authentication_helper.rb`, `app/controllers/concerns/identifiable.rb`.
42+
- Permissions: `app/models/ability.rb`. Domain ops: `lib/concepts/**`. Data models: `app/models/**`. API views: `app/views/api/**`. GraphQL types/mutations: `app/graphql/**`.

Dockerfile

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
FROM ruby:3.2-slim-bullseye as base
1+
FROM ruby:3.2-slim-bullseye AS base
22
RUN gem install bundler \
33
&& apt-get update \
44
&& apt-get upgrade --yes \
55
&& apt-get install --yes --no-install-recommends \
66
libpq5 libxml2 libxslt1.1 libvips \
77
curl gnupg graphviz nodejs \
8-
&& echo "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
9-
&& curl -sL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
8+
&& mkdir -p /usr/share/keyrings \
9+
&& curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-archive-keyring.gpg \
10+
&& echo "deb [signed-by=/usr/share/keyrings/postgresql-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
1011
&& apt-get update \
11-
&& apt-get install --yes --no-install-recommends postgresql-client-15 \
12+
&& apt-get install --yes --no-install-recommends postgresql-client-17 \
1213
&& rm -rf /var/lib/apt/lists/* /var/lib/apt/archives/*.deb
1314
ENV TZ='Europe/London'
1415
ENV RUBYOPT='-W:no-deprecated -W:no-experimental'
@@ -31,11 +32,11 @@ FROM builder AS dev-container
3132
RUN apt-get update \
3233
&& apt-get install --yes --no-install-recommends sudo git vim zsh ssh curl less
3334
RUN sh -c "$(curl -L https://github.com/deluan/zsh-in-docker/releases/download/v1.1.5/zsh-in-docker.sh)" -- \
34-
-t robbyrussell \
35-
-p git -p docker-compose -p yarn \
36-
-p https://github.com/zsh-users/zsh-autosuggestions \
37-
# -p https://github.com/marlonrichert/zsh-autocomplete \
38-
-p https://github.com/unixorn/fzf-zsh-plugin
35+
-t robbyrussell \
36+
-p git -p docker-compose -p yarn \
37+
-p https://github.com/zsh-users/zsh-autosuggestions \
38+
# -p https://github.com/marlonrichert/zsh-autocomplete \
39+
-p https://github.com/unixorn/fzf-zsh-plugin
3940
RUN chsh -s $(which zsh) ${USER}
4041

4142
# Slim application image without development dependencies

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,48 @@ Start the application and its dependencies via docker:
3333
docker-compose up
3434
```
3535

36+
### Using the dev-container
37+
38+
There is a dedicated image called `dev-container`, and the easiest way to use this is to use the override:
39+
40+
`cp docker-compose.override.yml.example docker-compose.override.yml`
41+
42+
Now when you run `docker compose up -d` it will always use the `dev-container` image.
43+
44+
When adding a vscode project the dev-container should be automatically found and a dialog will show asking if
45+
you want to use this, but failing that you can open the cmd palette with `cmd + shift + p` and call:
46+
47+
`Dev Containers: Reopen in Container`
48+
49+
If there's an issue, do check the logs, but you can run:
50+
51+
`Dev Containers: Rebuild and Reopen in Container`
52+
53+
and there's also a 'without cache' variant, to ensure packages are re-built.
54+
3655
#### Updating gems inside the container
3756

38-
This can be done with the `bin/with-builder.sh` script:
57+
If running inside a dev-container:
58+
59+
```
60+
bundle install
61+
```
62+
63+
or outside with:
64+
65+
```
66+
docker compose run --rm --no-deps api bundle install
67+
```
68+
69+
This can also be done with the `bin/with-builder.sh` script:
3970

4071
```
4172
./bin/with-builder.sh bundle update
4273
```
4374

44-
which should update the Gems in the container, without the need for rebuilding.
75+
which should update the Gems in the container.
76+
77+
None of these methods require rebuilding.
4578

4679
### Seeding
4780

app/controllers/api/lessons_controller.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ def index
1010
archive_scope = params[:include_archived] == 'true' ? Lesson : Lesson.unarchived
1111
scope = params[:school_class_id] ? archive_scope.where(school_class_id: params[:school_class_id]) : archive_scope
1212
ordered_scope = scope.order(created_at: :asc)
13-
@lessons_with_users = ordered_scope.accessible_by(current_ability).with_users
13+
14+
accessible_lessons = ordered_scope.accessible_by(current_ability)
15+
lessons_with_users = accessible_lessons.with_users
16+
remixes = user_remixes(accessible_lessons)
17+
@lessons_with_users_and_remixes = lessons_with_users.zip(remixes)
1418
if current_user&.school_teacher?(school) || current_user&.school_owner?(school)
1519
render :teacher_index, formats: [:json], status: :ok
1620
else
@@ -78,6 +82,22 @@ def verify_school_class_belongs_to_school
7882
raise ParameterError, 'school_class_id does not correspond to school_id'
7983
end
8084

85+
def user_remixes(lessons)
86+
lessons.map do |lesson|
87+
next nil unless lesson&.project&.remixes&.any?
88+
89+
user_remix(lesson)
90+
end
91+
end
92+
93+
def user_remix(lesson)
94+
lesson.project&.remixes
95+
&.where(user_id: current_user.id)
96+
&.accessible_by(current_ability)
97+
&.order(created_at: :asc)
98+
&.first
99+
end
100+
81101
def lesson_params
82102
base_params.merge(user_id: current_user.id)
83103
end

app/controllers/api/projects/remixes_controller.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Projects
55
class RemixesController < ApiController
66
before_action :authorize_user
77
load_and_authorize_resource :school, only: :index
8-
before_action :load_and_authorize_remix, only: %i[show]
8+
before_action :load_and_authorize_remix, only: %i[show show_identifier]
99

1010
def index
1111
projects = Project.where(remixed_from_id: project.id).accessible_by(current_ability)
@@ -17,6 +17,10 @@ def show
1717
render '/api/projects/show', formats: [:json]
1818
end
1919

20+
def show_identifier
21+
render json: { identifier: @project.identifier }, status: :ok
22+
end
23+
2024
def create
2125
# Ensure we have a fallback value to prevent bad requests
2226
remix_origin = request.origin || request.referer

app/views/api/lessons/student_index.json.jbuilder

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
json.array!(@lessons_with_users) do |lesson, user|
44
json.partial! 'lesson', lesson: lesson, user: user
5+
json.remix_identifier(remix.identifier) if remix.present?
56
end

bin/docker-debug-entrypoint.sh

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
#!/bin/bash
2+
set -euo pipefail # Use strict-mode in based to ensure errors are surfaced early
23

3-
rails db:prepare
4-
rdbg -n -o -c -- bin/rails s -p 3009 -b '0.0.0.0'
4+
# Check if bundle install needs to be run
5+
bundle check >/dev/null 2>&1 || bundle install --jobs "${BUNDLE_JOBS:-4}"
6+
7+
# Check if yarn install needs to be run
8+
if [ -f package.json ] && command -v yarn >/dev/null 2>&1; then
9+
yarn install --check-files --frozen-lockfile || yarn install --check-files
10+
fi
11+
12+
# Prepare the database
13+
bundle exec rails db:prepare
14+
15+
exec rdbg -n -o -c -- bundle exec rails s -p 3009 -b '0.0.0.0'

bin/docker-entrypoint.sh

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
#!/bin/bash
2+
set -euo pipefail # Use strict-mode in based to ensure errors are surfaced early
23

3-
rails db:prepare
4-
rails server --port 3009 --binding 0.0.0.0
4+
# Check if bundle install needs to be run
5+
bundle check >/dev/null 2>&1 || bundle install --jobs "${BUNDLE_JOBS:-4}"
6+
7+
# Check if yarn install needs to be run
8+
if [ -f package.json ] && command -v yarn >/dev/null 2>&1; then
9+
yarn install --check-files --frozen-lockfile || yarn install --check-files
10+
fi
11+
12+
# Prepare the database
13+
bundle exec rails db:prepare
14+
15+
exec bundle exec rails server --port 3009 --binding 0.0.0.0

config/routes.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@
4343
post :submit, on: :member, to: 'school_projects#submit'
4444
post :return, on: :member, to: 'school_projects#return'
4545
post :complete, on: :member, to: 'school_projects#complete'
46-
resource :remix, only: %i[show create], controller: 'projects/remixes'
46+
resource :remix, only: %i[show create], controller: 'projects/remixes' do
47+
get :identifier, on: :member, to: 'projects/remixes#show_identifier'
48+
end
4749
resources :remixes, only: %i[index], controller: 'projects/remixes'
4850
resource :images, only: %i[show create], controller: 'projects/images'
4951
resources :feedback, only: %i[index create], controller: 'feedback' do

0 commit comments

Comments
 (0)