-
Notifications
You must be signed in to change notification settings - Fork 193
E2602 - Spring - Backend Changes #322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ravisatyarsg
wants to merge
5
commits into
expertiza:main
Choose a base branch
from
ravisatyarsg:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6d57c4e
Implement student task workflow and improve Docker setup
84e0300
Stop tracking backend instructions
09819bd
Align database config with Docker defaults
0bc6bea
Remove backend implementation report
1a1f18f
Stabilize student task request specs
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,3 +32,4 @@ | |
| coverage/ | ||
| rsa_keys.yml | ||
| pg_data/ | ||
| backend_instructions.txt | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,26 @@ | ||
| FROM ruby:3.4.5 | ||
|
|
||
| LABEL maintainer="Ankur Mundra <ankurmundra0212@gmail.com>" | ||
| # Install dependencies | ||
|
|
||
| RUN apt-get update && \ | ||
| apt-get install -y curl && \ | ||
| curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ | ||
| apt-get install -y nodejs && \ | ||
| apt-get install -y netcat-openbsd | ||
| apt-get install -y --no-install-recommends \ | ||
| build-essential \ | ||
| curl \ | ||
| default-libmysqlclient-dev \ | ||
| netcat-openbsd \ | ||
| pkg-config && \ | ||
| curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ | ||
| apt-get install -y --no-install-recommends nodejs && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Set the working directory | ||
| WORKDIR /app | ||
|
|
||
| # Copy your application files from current location to WORKDIR | ||
| COPY . . | ||
| COPY Gemfile Gemfile.lock ./ | ||
| RUN gem install bundler:2.4.14 && bundle install | ||
|
|
||
| # Install Ruby dependencies | ||
| RUN gem update --system && gem install bundler:2.4.7 | ||
| RUN bundle install | ||
| COPY . . | ||
|
|
||
| EXPOSE 3002 | ||
| EXPOSE 3002 | ||
|
|
||
| # Set the entry point | ||
| ENTRYPOINT ["/app/setup.sh"] | ||
| ENTRYPOINT ["/app/setup.sh"] | ||
| CMD ["bin/rails", "server", "-p", "3002", "-b", "0.0.0.0"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,83 @@ | ||
| # Expertiza Backend Re-Implementation | ||
|
|
||
| This README would normally document whatever steps are necessary to get the | ||
| application up and running. | ||
| Rails API backend for the Expertiza reimplementation project. | ||
|
|
||
| Things you may want to cover: | ||
| ## Stack | ||
|
|
||
| * Ruby version - 3.4.5 | ||
| - Ruby `3.4.5` | ||
| - Rails `8.0` | ||
| - MySQL `8.0` | ||
| - Redis | ||
| - Docker Compose | ||
|
|
||
| ## Development Environment | ||
| ## Run With Docker | ||
|
|
||
| ### Prerequisites | ||
| - Verify that [Docker Desktop](https://www.docker.com/products/docker-desktop/) is installed and running. | ||
| - [Download](https://www.jetbrains.com/ruby/download/) RubyMine | ||
| - Make sure that the Docker plugin [is enabled](https://www.jetbrains.com/help/ruby/docker.html#enable_docker). | ||
|
|
||
| - Docker Desktop installed and running | ||
| - Docker Compose available as `docker compose` | ||
|
|
||
| ### Instructions | ||
| Tutorial: [Docker Compose as a remote interpreter](https://www.jetbrains.com/help/ruby/using-docker-compose-as-a-remote-interpreter.html) | ||
| ### Start the app | ||
|
|
||
| ### Video Tutorial | ||
| ```bash | ||
| docker compose up --build | ||
| ``` | ||
|
|
||
| <a href="http://www.youtube.com/watch?feature=player_embedded&v=BHniRaZ0_JE | ||
| " target="_blank"><img src="http://img.youtube.com/vi/BHniRaZ0_JE/maxresdefault.jpg" | ||
| alt="IMAGE ALT TEXT HERE" width="560" height="315" border="10" /></a> | ||
| This starts: | ||
|
|
||
| ### Database Credentials | ||
| - username: root | ||
| - password: expertiza | ||
| - `app` on `http://localhost:3002` | ||
| - MySQL on host port `3307` | ||
| - Redis on host port `6380` | ||
|
|
||
| On startup the app container will: | ||
|
|
||
| 1. wait for MySQL to become healthy | ||
| 2. run `bin/rails db:create` and `bin/rails db:migrate` | ||
| 3. start Rails on port `3002` | ||
|
|
||
| The database is not seeded by default. To seed it once during startup: | ||
|
|
||
| ```bash | ||
| SEED_DB=true docker compose up --build | ||
| ``` | ||
|
|
||
| Use seeding carefully: the current seed file is sample-data oriented and is not intended to run on every restart. | ||
|
|
||
| ## Database Access | ||
|
|
||
| ### App database settings inside Docker | ||
|
|
||
| - host: `db` | ||
| - port: `3306` | ||
| - username: `root` | ||
| - password: `expertiza` | ||
| - development database: `reimplementation_development` | ||
| - test database: `reimplementation_test` | ||
| - production database: `reimplementation_production` | ||
|
|
||
| ### Connect from the host machine | ||
|
|
||
| ```bash | ||
| mysql -h 127.0.0.1 -P 3307 -u root -pexpertiza reimplementation_development | ||
| ``` | ||
|
|
||
| ### Connect through the container | ||
|
|
||
| ```bash | ||
| docker compose exec db mysql -uroot -pexpertiza reimplementation_development | ||
| ``` | ||
|
|
||
| ### Useful database commands | ||
|
|
||
| ```bash | ||
| docker compose exec app bin/rails db:create | ||
| docker compose exec app bin/rails db:migrate | ||
| docker compose exec app bin/rails db:seed | ||
| docker compose exec app bin/rails dbconsole | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - MySQL data is persisted in the `expertiza-mysql` Docker volume. | ||
| - Redis data is persisted in the `expertiza-redis` Docker volume. | ||
| - If you run Rails outside Docker, point it at the Dockerized MySQL instance with `DB_HOST=127.0.0.1` and `DB_PORT=3307`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class RevisionRequestsController < ApplicationController | ||
| prepend_before_action :set_assignment, only: :index | ||
| prepend_before_action :set_revision_request, only: %i[show update] | ||
|
|
||
| def action_allowed? | ||
| case params[:action] | ||
| when 'index' | ||
| current_user_has_instructor_privileges? && current_user_instructs_assignment?(@assignment) | ||
| when 'show' | ||
| return false unless @revision_request | ||
|
|
||
| owns_revision_request? || current_user_instructs_assignment?(@revision_request.assignment) | ||
| when 'update' | ||
| return false unless @revision_request | ||
|
|
||
| current_user_has_instructor_privileges? && current_user_instructs_assignment?(@revision_request.assignment) | ||
| else | ||
| false | ||
| end | ||
| end | ||
|
|
||
| def index | ||
| return if invalid_status_filter? | ||
|
|
||
| revision_requests = RevisionRequest.where(assignment_id: @assignment.id) | ||
| revision_requests = revision_requests.where(status: params[:status]) if params[:status].present? | ||
|
|
||
| render json: revision_requests.order(created_at: :desc).map(&:as_json), status: :ok | ||
| end | ||
|
|
||
| def show | ||
| return unless @revision_request | ||
|
|
||
| render json: @revision_request.as_json, status: :ok | ||
| end | ||
|
|
||
| def update | ||
| return render json: { error: 'This revision request has already been processed' }, status: :unprocessable_entity unless @revision_request.status == RevisionRequest::PENDING | ||
| return if invalid_update_status? | ||
|
|
||
| if @revision_request.update(update_params) | ||
| render json: @revision_request.as_json, status: :ok | ||
| else | ||
| render json: { error: @revision_request.errors.full_messages.to_sentence }, status: :unprocessable_entity | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def set_assignment | ||
| @assignment = Assignment.find_by(id: params[:assignment_id]) | ||
| return if @assignment | ||
|
|
||
| render json: { error: 'Assignment not found' }, status: :not_found | ||
| end | ||
|
|
||
| def set_revision_request | ||
| @revision_request = RevisionRequest.find_by(id: params[:id]) | ||
| return if @revision_request | ||
|
|
||
| render json: { error: 'Revision request not found' }, status: :not_found | ||
| end | ||
|
|
||
| def owns_revision_request? | ||
| @revision_request.participant.user_id == current_user.id | ||
| end | ||
|
|
||
| def invalid_status_filter? | ||
| return false if params[:status].blank? || RevisionRequest::STATUSES.include?(params[:status]) | ||
|
|
||
| render json: { error: 'Status must be PENDING, APPROVED, or DECLINED' }, status: :unprocessable_entity | ||
| true | ||
| end | ||
|
|
||
| def invalid_update_status? | ||
| return false if valid_resolved_status? | ||
|
|
||
| render json: { error: 'Status must be APPROVED or DECLINED' }, status: :unprocessable_entity | ||
| true | ||
| end | ||
|
|
||
| def valid_resolved_status? | ||
| [RevisionRequest::APPROVED, RevisionRequest::DECLINED].include?(update_params[:status]) | ||
| end | ||
|
|
||
| def update_params | ||
| params.require(:revision_request).permit(:status, :response_comment) | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,52 @@ | ||
| class StudentTasksController < ApplicationController | ||
| before_action :set_student_task, only: %i[show view request_revision] | ||
|
|
||
| # List retrieves all student tasks associated with the current logged-in user. | ||
| def action_allowed? | ||
| current_user_has_student_privileges? | ||
| end | ||
|
|
||
| def index | ||
| list | ||
| end | ||
|
|
||
| def list | ||
| # Retrieves all tasks that belong to the current user. | ||
| @student_tasks = StudentTask.from_user(current_user) | ||
| # Render the list of student tasks as JSON. | ||
| render json: @student_tasks, status: :ok | ||
| render json: StudentTask.from_user(current_user), status: :ok | ||
| end | ||
|
|
||
| def show | ||
| render json: @student_task, status: :ok | ||
| end | ||
|
|
||
| # The view function retrieves a student task based on a participant's ID. | ||
| # It is meant to provide an endpoint where tasks can be queried based on participant ID. | ||
| def view | ||
| # Retrieves the student task where the participant's ID matches the provided parameter. | ||
| # This function will be used for clicking on a specific student task to "view" its details. | ||
| @student_task = StudentTask.from_participant_id(params[:id]) | ||
| # Render the found student task as JSON. | ||
| render json: @student_task, status: :ok | ||
| show | ||
| end | ||
|
|
||
| def request_revision | ||
| return render json: { error: 'Revision requests require a team submission' }, status: :unprocessable_entity unless @student_task.team | ||
| return render json: { error: 'Revision requests are not available for this task' }, status: :unprocessable_entity unless @student_task.can_request_revision | ||
|
|
||
| revision_request = RevisionRequest.new( | ||
| participant: @participant, | ||
| team: @student_task.team, | ||
| assignment: @participant.assignment, | ||
| comments: params[:comments] | ||
| ) | ||
|
|
||
| if revision_request.save | ||
| @student_task = StudentTask.from_participant(@participant) | ||
| render json: { message: 'Revision request submitted successfully', revision_request: revision_request.as_json, student_task: @student_task.as_json }, status: :created | ||
| else | ||
| render json: { error: revision_request.errors.full_messages.to_sentence }, status: :unprocessable_entity | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def set_student_task | ||
| @participant = AssignmentParticipant.find_by(id: params[:id]) | ||
| return render json: { error: 'Student task not found' }, status: :not_found unless @participant | ||
| return render json: { error: 'You are not authorized to access this student task' }, status: :forbidden unless @participant.user_id == current_user.id | ||
|
|
||
| @student_task = StudentTask.from_participant(@participant) | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class RevisionRequest < ApplicationRecord | ||
| PENDING = 'PENDING' | ||
| APPROVED = 'APPROVED' | ||
| DECLINED = 'DECLINED' | ||
| STATUSES = [PENDING, APPROVED, DECLINED].freeze | ||
|
|
||
| belongs_to :participant, class_name: 'AssignmentParticipant' | ||
| belongs_to :team, class_name: 'AssignmentTeam' | ||
| belongs_to :assignment | ||
|
|
||
| validates :comments, presence: true | ||
| validates :status, inclusion: { in: STATUSES } | ||
| validate :one_pending_request_per_participant_team, on: :create | ||
|
|
||
| scope :pending, -> { where(status: PENDING) } | ||
|
|
||
| def as_json(_options = {}) | ||
| { | ||
| id: id, | ||
| participant_id: participant_id, | ||
| team_id: team_id, | ||
| assignment_id: assignment_id, | ||
| status: status, | ||
| comments: comments, | ||
| response_comment: response_comment, | ||
| created_at: created_at&.iso8601, | ||
| updated_at: updated_at&.iso8601 | ||
| } | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def one_pending_request_per_participant_team | ||
| return unless self.class.pending.exists?(participant_id: participant_id, team_id: team_id) | ||
|
|
||
| errors.add(:base, 'A pending revision request already exists for this task') | ||
| end | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revisions to shared files like this are likely going to lead to conflicts and reduce the chance of this PR ever getting merged.