diff --git a/backend/app/controllers/movies_controller.rb b/backend/app/controllers/movies_controller.rb new file mode 100644 index 0000000..4b5d6f9 --- /dev/null +++ b/backend/app/controllers/movies_controller.rb @@ -0,0 +1,47 @@ +class MoviesController < ApplicationController + rescue_from ActiveRecord::RecordNotFound, with: :movie_not_found + + def index + render json: Movies::Find.new.call + end + + def create + movie = Movies::Create.new(params: movie_params).call! + + render json: movie, status: :created + rescue ActiveRecord::RecordInvalid + render json: { error: "Could not create a movie" }, status: :unprocessable_entity + end + + def update + Movies::Update.new(params: movie_params.merge(id: params[:id]), movie: Movie).call! + + render status: :no_content + rescue ActiveRecord::RecordInvalid + render json: { error: "Could not update a movie" }, status: :unprocessable_entity + end + + def show + movie = Movies::FindById.new(id: params[:id]).call + + render json: movie + end + + def destroy + Movies::Delete.new(id: params[:id]).call + + render status: :no_content + end + + private + + def movie_params + params + .require(:movie) + .permit(:director, :writer, :title, :producer, :production_company, :year, cast: []) + end + + def movie_not_found + render json: { error: "Movie not found" }, status: :not_found + end +end diff --git a/backend/app/models/movie.rb b/backend/app/models/movie.rb new file mode 100644 index 0000000..0f9f713 --- /dev/null +++ b/backend/app/models/movie.rb @@ -0,0 +1,31 @@ +class Movie + attr_accessor :id, :director, :writer, :title, :producer, :production_company, :cast, :year, :created_at, :updated_at + + def initialize(id: nil, director:, writer:, title:, producer:, production_company:, cast:, year:, created_at: nil, updated_at: nil) + @id = id + @director = director + @writer = writer + @title = title + @producer = producer + @production_company = production_company + @cast = cast + @year = year + @created_at = created_at + @updated_at = updated_at + end + + def to_hash + { + id: @id, + director: @director, + writer: @writer, + title: @title, + producer: @producer, + production_company: @production_company, + cast: @cast, + year: @year, + created_at: @created_at, + updated_at: @updated_at + }.compact + end +end diff --git a/backend/app/repositories/movies_repository.rb b/backend/app/repositories/movies_repository.rb new file mode 100644 index 0000000..fe2a6dd --- /dev/null +++ b/backend/app/repositories/movies_repository.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class MoviesRepository < ActiveRecord::Base + self.table_name = 'movies' + + validates_presence_of :director, :writer, :title, :producer, :cast, :year +end diff --git a/backend/app/usecases/movies/create.rb b/backend/app/usecases/movies/create.rb new file mode 100644 index 0000000..6a3195d --- /dev/null +++ b/backend/app/usecases/movies/create.rb @@ -0,0 +1,26 @@ +class Movies::Create + def initialize(params:, movie: Movie, repository: MoviesRepository) + @params = params + @movie = movie + @repository = repository + end + + def call! + movie = @movie.new( + director: @params[:director], + writer: @params[:writer], + title: @params[:title], + producer: @params[:producer], + production_company: @params[:production_company], + cast: @params[:cast], + year: @params[:year] + ) + + result = @repository.create!(movie.to_hash) + movie.id = result.id + movie.created_at = result.created_at + movie.updated_at = result.updated_at + + movie + end +end diff --git a/backend/app/usecases/movies/delete.rb b/backend/app/usecases/movies/delete.rb new file mode 100644 index 0000000..1d6d3b5 --- /dev/null +++ b/backend/app/usecases/movies/delete.rb @@ -0,0 +1,10 @@ +class Movies::Delete + def initialize(id:, repository: MoviesRepository) + @id = id + @repository = repository + end + + def call + @repository.delete(@id) + end +end diff --git a/backend/app/usecases/movies/find.rb b/backend/app/usecases/movies/find.rb new file mode 100644 index 0000000..fce72c9 --- /dev/null +++ b/backend/app/usecases/movies/find.rb @@ -0,0 +1,22 @@ +class Movies::Find + def initialize(repository: MoviesRepository) + @repository = repository + end + + def call + @repository.all.map do |movie| + Movie.new( + id: movie.id, + director: movie.director, + writer: movie.writer, + title: movie.title, + producer: movie.producer, + production_company: movie.production_company, + cast: JSON.parse(movie.cast), + year: movie.year, + created_at: movie.created_at, + updated_at: movie.updated_at + ) + end + end +end diff --git a/backend/app/usecases/movies/find_by_id.rb b/backend/app/usecases/movies/find_by_id.rb new file mode 100644 index 0000000..3459c67 --- /dev/null +++ b/backend/app/usecases/movies/find_by_id.rb @@ -0,0 +1,23 @@ +class Movies::FindById + def initialize(id:, repository: MoviesRepository) + @id = id + @repository = repository + end + + def call + movie = @repository.find(@id) + + Movie.new( + id: movie.id, + director: movie.director, + writer: movie.writer, + title: movie.title, + producer: movie.producer, + production_company: movie.production_company, + cast: JSON.parse(movie.cast), + year: movie.year, + created_at: movie.created_at, + updated_at: movie.updated_at + ) + end +end diff --git a/backend/app/usecases/movies/update.rb b/backend/app/usecases/movies/update.rb new file mode 100644 index 0000000..4a2e818 --- /dev/null +++ b/backend/app/usecases/movies/update.rb @@ -0,0 +1,23 @@ +class Movies::Update + def initialize(params:, movie: Movie, repository: MoviesRepository) + @params = params + @movie = movie + @repository = repository + end + + def call! + movie = @movie.new( + id: @params[:id], + director: @params[:director], + writer: @params[:writer], + title: @params[:title], + producer: @params[:producer], + production_company: @params[:production_company], + cast: @params[:cast], + year: @params[:year] + ) + + current_movie = @repository.find(movie.id) + current_movie.update!(movie.to_hash) + end +end diff --git a/backend/config/routes.rb b/backend/config/routes.rb index 5778fab..95e043d 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -8,5 +8,6 @@ get "/health", to: "health#index" resources :games, only: [:index, :create, :show, :update, :destroy] + resources :movies, only: [:index, :create, :show, :update, :destroy] end end diff --git a/backend/db/migrate/20231129121220_create_movies.rb b/backend/db/migrate/20231129121220_create_movies.rb new file mode 100644 index 0000000..252bc92 --- /dev/null +++ b/backend/db/migrate/20231129121220_create_movies.rb @@ -0,0 +1,15 @@ +class CreateMovies < ActiveRecord::Migration[7.0] + def change + create_table :movies do |t| + t.string :director + t.string :writer + t.string :title + t.string :producer + t.string :production_company + t.string :cast, array: true + t.integer :year + + t.timestamps + end + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index 0a773ab..aa80ff1 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_26_123520) do +ActiveRecord::Schema[7.0].define(version: 2023_11_29_121220) do create_table "games", force: :cascade do |t| t.string "title", null: false t.integer "year", null: false @@ -46,6 +46,18 @@ t.datetime "updated_at", null: false end + create_table "movies", force: :cascade do |t| + t.string "director" + t.string "writer" + t.string "title" + t.string "producer" + t.string "production_company" + t.string "cast" + t.integer "year" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "platforms", force: :cascade do |t| t.string "name", null: false t.integer "year", null: false diff --git a/backend/spec/models/movie_spec.rb b/backend/spec/models/movie_spec.rb new file mode 100644 index 0000000..c2472ac --- /dev/null +++ b/backend/spec/models/movie_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe Movie do + describe '.to_hash' do + let(:movie_id) { 1 } + let(:movie) { + described_class.new( + id: movie_id, + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + ) + } + + context 'when movie id is present' do + it 'returns movie hash' do + expect(movie.to_hash).to eq({ + id: movie_id, + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + }) + end + end + + context 'when movie id is not present' do + let(:movie_id) { nil } + + it 'returns movie hash without id' do + expect(movie.to_hash).to eq({ + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + }) + end + end + end +end diff --git a/backend/spec/repositories/movies_repository_spec.rb b/backend/spec/repositories/movies_repository_spec.rb new file mode 100644 index 0000000..4ace6f0 --- /dev/null +++ b/backend/spec/repositories/movies_repository_spec.rb @@ -0,0 +1,10 @@ +require "rails_helper" + +RSpec.describe MoviesRepository, type: :model do + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:year) } + it { is_expected.to validate_presence_of(:director) } + it { is_expected.to validate_presence_of(:writer) } + it { is_expected.to validate_presence_of(:producer) } + it { is_expected.to validate_presence_of(:cast) } +end diff --git a/backend/spec/requests/movies/create_spec.rb b/backend/spec/requests/movies/create_spec.rb new file mode 100644 index 0000000..8288266 --- /dev/null +++ b/backend/spec/requests/movies/create_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" + +RSpec.describe "Movies", type: :request do + let(:response_json) { JSON.parse(response.body) } + + let(:params) do + { + "movie" => { + "director" => "George Lucas", + "writer" => "George Lucas", + "title" => "Star Wars", + "producer" => "George Lucas", + "production_company" => "Lucasfilm", + "cast" => ["Han Solo", "Princess Leia"], + "year" => 1977 + } + } + end + + describe "POST /api/movies" do + context "when create params are valid" do + it "responds with 201" do + expect { post "/api/movies", params: params } + .to change { MoviesRepository.count }.by(1) + + expect(response).to have_http_status(:created) + end + end + + context "when create params are invalid" do + it "responds with 422" do + post "/api/movies", params: { "movie" => { "title" => "", "year" => "" } } + + expect(response_json).to eq({ "error" => "Could not create a movie" }) + expect(response).to have_http_status(:unprocessable_entity) + end + end + end +end diff --git a/backend/spec/requests/movies/destroy_spec.rb b/backend/spec/requests/movies/destroy_spec.rb new file mode 100644 index 0000000..2301919 --- /dev/null +++ b/backend/spec/requests/movies/destroy_spec.rb @@ -0,0 +1,36 @@ +require "rails_helper" + +RSpec.describe "Movies", type: :request do + describe "DELETE /api/movies/:id" do + context "when movie exists" do + let!(:movie) { + Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + } + + it "responds with 204" do + expect { delete "/api/movies/#{movie.id}" } + .to change { MoviesRepository.count }.by(-1) + + expect(response).to have_http_status(:no_content) + end + end + + context "when movie does not exist" do + it "responds with 204" do + delete "/api/movies/123" + + expect(response).to have_http_status(:no_content) + end + end + end +end diff --git a/backend/spec/requests/movies/index_spec.rb b/backend/spec/requests/movies/index_spec.rb new file mode 100644 index 0000000..16db58d --- /dev/null +++ b/backend/spec/requests/movies/index_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe "Movies", type: :request do + let(:response_json) { JSON.parse(response.body) } + + describe "GET /api/movies" do + context "when there are no movies" do + it "responds with an empty array" do + get "/api/movies" + + expect(response_json).to eq([]) + expect(response).to have_http_status(:ok) + end + end + + context "when there are movies" do + let!(:game) { + Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + } + + it "responds with an array of movies" do + get "/api/movies" + + expect(response_json.first).to include({ + "title" => "Star Wars", + "cast" => ["Han Solo", "Princess Leia"], + "year" => 1977 + }) + expect(response).to have_http_status(:ok) + end + end + end +end diff --git a/backend/spec/requests/movies/show_spec.rb b/backend/spec/requests/movies/show_spec.rb new file mode 100644 index 0000000..f49bbd3 --- /dev/null +++ b/backend/spec/requests/movies/show_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe "Movies", type: :request do + let(:response_json) { JSON.parse(response.body) } + + describe "GET /api/movies/:id" do + context "when there is a movie" do + let!(:game) { + Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + } + + it "responds with a movie" do + get "/api/movies/#{game.id}" + + expect(response_json).to include({ + "title" => "Star Wars", + "cast" => ["Han Solo", "Princess Leia"], + "year" => 1977 + }) + expect(response).to have_http_status(:ok) + end + end + + context "when there is no movie" do + it "responds with an error" do + get "/api/movies/1" + + expect(response_json).to eq({ "error" => "Movie not found" }) + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/backend/spec/requests/movies/update_spec.rb b/backend/spec/requests/movies/update_spec.rb new file mode 100644 index 0000000..022e007 --- /dev/null +++ b/backend/spec/requests/movies/update_spec.rb @@ -0,0 +1,54 @@ +require "rails_helper" + +RSpec.describe "Movies", type: :request do + let(:response_json) { JSON.parse(response.body) } + let(:create_params) { + { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + } + let(:update_params) do + { + "movie" => { + "title": "Star Wars 2" + } + } + end + + describe "PATCH /api/movies/:id" do + context "when movie exists" do + let!(:movie) { Movies::Create.new(params: create_params).call! } + + it "responds with 200" do + patch "/api/movies/#{movie.id}", params: update_params + + expect(response).to have_http_status(:no_content) + end + end + + context "when movie does not exist" do + it "responds with 404" do + patch "/api/movies/1", params: update_params + + expect(response).to have_http_status(:not_found) + end + end + + context "when update params are invalid" do + let!(:movie) { Movies::Create.new(params: create_params).call! } + + it "responds with 422" do + patch "/api/movies/#{movie.id}", params: { "movie" => { "title" => "", "year" => "" } } + + expect(response_json).to eq({ "error" => "Could not update a movie" }) + expect(response).to have_http_status(:unprocessable_entity) + end + end + end +end diff --git a/backend/spec/usecases/movies/create_spec.rb b/backend/spec/usecases/movies/create_spec.rb new file mode 100644 index 0000000..8458ba0 --- /dev/null +++ b/backend/spec/usecases/movies/create_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +RSpec.describe Movies::Create do + it "creates a movie" do + movie = described_class.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + expect(movie.id).to be_present + expect(movie.director).to eq("George Lucas") + expect(movie.writer).to eq("George Lucas") + expect(movie.title).to eq("Star Wars") + expect(movie.producer).to eq("George Lucas") + expect(movie.production_company).to eq("Lucasfilm") + expect(movie.cast).to eq(["Han Solo", "Princess Leia"]) + expect(movie.year).to eq(1977) + expect(movie.created_at).to be_present + expect(movie.updated_at).to be_present + end +end diff --git a/backend/spec/usecases/movies/delete_spec.rb b/backend/spec/usecases/movies/delete_spec.rb new file mode 100644 index 0000000..08125f4 --- /dev/null +++ b/backend/spec/usecases/movies/delete_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +RSpec.describe Movies::Delete do + it "deletes a movie" do + movie = Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + expect(Movies::Find.new.call).not_to be_empty + + described_class.new(id: movie.id).call + + expect(Movies::Find.new.call).to be_empty + end +end diff --git a/backend/spec/usecases/movies/find_by_id_spec.rb b/backend/spec/usecases/movies/find_by_id_spec.rb new file mode 100644 index 0000000..a1fc210 --- /dev/null +++ b/backend/spec/usecases/movies/find_by_id_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +RSpec.describe Movies::FindById do + it "returns a movie" do + movie = Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + movie = described_class.new(id: movie.id).call + + expect(movie.id).to eq(movie.id) + end +end diff --git a/backend/spec/usecases/movies/find_spec.rb b/backend/spec/usecases/movies/find_spec.rb new file mode 100644 index 0000000..32892f6 --- /dev/null +++ b/backend/spec/usecases/movies/find_spec.rb @@ -0,0 +1,22 @@ +require "rails_helper" + +RSpec.describe Movies::Find do + it "returns movies" do + movie = Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + movies = described_class.new.call + + expect(movies.first).to be_a(Movie) + expect(movies.count).to eq(1) + end +end diff --git a/backend/spec/usecases/movies/update_spec.rb b/backend/spec/usecases/movies/update_spec.rb new file mode 100644 index 0000000..a1c0eb6 --- /dev/null +++ b/backend/spec/usecases/movies/update_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe Movies::Update do + it "updates a movie" do + movie = Movies::Create.new( + params: { + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + described_class.new( + params: { + id: movie.id, + director: "George Lucas", + writer: "George Lucas", + title: "Star Wars: The Old Republic", + producer: "George Lucas", + production_company: "Lucasfilm", + cast: ["Han Solo", "Princess Leia"], + year: 1977 + } + ).call! + + updated_movie = Movies::FindById.new(id: movie.id).call + + expect(updated_movie.id).to eq(movie.id) + expect(updated_movie.director).to eq("George Lucas") + expect(updated_movie.writer).to eq("George Lucas") + expect(updated_movie.title).to eq("Star Wars: The Old Republic") + expect(updated_movie.producer).to eq("George Lucas") + expect(updated_movie.production_company).to eq("Lucasfilm") + expect(updated_movie.cast).to eq(["Han Solo", "Princess Leia"]) + expect(updated_movie.year).to eq(1977) + expect(updated_movie.created_at).to eq(movie.created_at) + expect(updated_movie.updated_at).to be > movie.updated_at + end +end