diff --git a/Gemfile b/Gemfile index 922c125a..2541ace6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source "https://rubygems.org" -ruby "3.3.4" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.2.1" diff --git a/Gemfile.lock b/Gemfile.lock index f9cdcfa1..4783057e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -524,8 +524,5 @@ DEPENDENCIES tzinfo-data uglifier -RUBY VERSION - ruby 3.3.4p94 - BUNDLED WITH 2.5.18 diff --git a/app/assets/stylesheets/games.css b/app/assets/stylesheets/games.css new file mode 100644 index 00000000..b126a87e --- /dev/null +++ b/app/assets/stylesheets/games.css @@ -0,0 +1,98 @@ +/* app/assets/stylesheets/pages.css */ + +.container { + padding: 20px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} + +h2 { + font-size: 24px; +} + +.btn-login { + background-color: #4caf50; + color: white; + padding: 10px 20px; + text-align: center; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.btn-admin { + background-color: red; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropbtn { + background-color: #4caf50; + color: white; + padding: 10px 20px; + font-size: 16px; + border: none; + cursor: pointer; + border-radius: 5px; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; +} + +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + background-color: #f1f1f1; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.dropdown:hover .dropbtn { + background-color: #3e8e41; +} + +.game-list { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin-top: 20px; + max-width: 1600px; + margin: 0 auto; +} + +.game-item { + padding: 15px; + border: 1px solid #ddd; + text-align: center; + border-radius: 8px; +} + +.btn-play { + background-color: #4caf50; + color: white; + padding: 10px 20px; + text-align: center; + border: none; + border-radius: 5px; + cursor: pointer; +} diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb new file mode 100644 index 00000000..63dddf2f --- /dev/null +++ b/app/controllers/games_controller.rb @@ -0,0 +1,75 @@ +class GamesController < ApplicationController + before_action :set_game, only: %i[ show edit update destroy ] + + # GET /games or /games.json + def index + @games = Game.all + end + + # GET /games/1 or /games/1.json + def show + @game = Game.find(params[:id]) + redirect_to send(@game.game_path) + end + + def demo_game + end + + # GET /games/new + def new + @game = Game.new + end + + # GET /games/1/edit + def edit + end + + # POST /games or /games.json + def create + @game = Game.new(game_params) + + respond_to do |format| + if @game.save + format.html { redirect_to @game, notice: "Game was successfully created." } + format.json { render :show, status: :created, location: @game } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @game.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /games/1 or /games/1.json + def update + respond_to do |format| + if @game.update(game_params) + format.html { redirect_to @game, notice: "Game was successfully updated." } + format.json { render :show, status: :ok, location: @game } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @game.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /games/1 or /games/1.json + def destroy + @game.destroy! + + respond_to do |format| + format.html { redirect_to games_path, status: :see_other, notice: "Game was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_game + @game = Game.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def game_params + params.require(:game).permit(:name, :game_path) + end +end diff --git a/app/helpers/games_helper.rb b/app/helpers/games_helper.rb new file mode 100644 index 00000000..2ef8c1f4 --- /dev/null +++ b/app/helpers/games_helper.rb @@ -0,0 +1,2 @@ +module GamesHelper +end diff --git a/app/models/game.rb b/app/models/game.rb new file mode 100644 index 00000000..a58d4f13 --- /dev/null +++ b/app/models/game.rb @@ -0,0 +1,3 @@ +class Game < ApplicationRecord + validates :name, presence: true +end \ No newline at end of file diff --git a/app/views/games/_form.html.erb b/app/views/games/_form.html.erb new file mode 100644 index 00000000..e9f5d203 --- /dev/null +++ b/app/views/games/_form.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: game) do |form| %> + <% if game.errors.any? %> +
+

<%= pluralize(game.errors.count, "error") %> prohibited this game from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :name, style: "display: block" %> + <%= form.text_field :name %> +
+ +
+ <%= form.label :game_path, style: "display: block" %> + <%= form.text_field :game_path %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/games/_game.html.erb b/app/views/games/_game.html.erb new file mode 100644 index 00000000..5bd20154 --- /dev/null +++ b/app/views/games/_game.html.erb @@ -0,0 +1,12 @@ +
+

+ Name: + <%= game.name %> +

+ +

+ Game path: + <%= game.game_path %> +

+ +
diff --git a/app/views/games/_game.json.jbuilder b/app/views/games/_game.json.jbuilder new file mode 100644 index 00000000..d8eea494 --- /dev/null +++ b/app/views/games/_game.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! game, :id, :name, :game_path, :created_at, :updated_at +json.url game_url(game, format: :json) diff --git a/app/views/games/_games.html.erb b/app/views/games/_games.html.erb new file mode 100644 index 00000000..83e94078 --- /dev/null +++ b/app/views/games/_games.html.erb @@ -0,0 +1,14 @@ +

<%= notice %>

+<% content_for :title, "Games" %> + +

Games

+ +
+ <% @games.each do |game| %> +
+

<%= game.name %>

+ <%= link_to 'Play', game_path(game), class: 'btn btn-play' %> +
+ <% end %> +
+ \ No newline at end of file diff --git a/app/views/games/demo_game.html.erb b/app/views/games/demo_game.html.erb new file mode 100644 index 00000000..a73fa041 --- /dev/null +++ b/app/views/games/demo_game.html.erb @@ -0,0 +1,5 @@ +

<%= notice %>

+ +
+ Demo Game Page +
diff --git a/app/views/games/edit.html.erb b/app/views/games/edit.html.erb new file mode 100644 index 00000000..287b0a9f --- /dev/null +++ b/app/views/games/edit.html.erb @@ -0,0 +1,12 @@ +<% content_for :title, "Editing game" %> + +

Editing game

+ +<%= render "form", game: @game %> + +
+ +
+ <%= link_to "Show this game", @game %> | + <%= link_to "Back to games", games_path %> +
diff --git a/app/views/games/index.html.erb b/app/views/games/index.html.erb new file mode 100644 index 00000000..98a709b9 --- /dev/null +++ b/app/views/games/index.html.erb @@ -0,0 +1,32 @@ +
+ +
+

Hello, <%="Guest" %>!

+ +
+ <%# <% if true %> + <%# TODO, using if-else to display login button or account info button %> + + + <%# <% else %> + + <%= link_to 'Log In', '#', class: 'btn btn-login' %> + <%# <% end %> + + <% if true %> + <%# TODO, only appear if the user is admin %> + <%= link_to 'All Users for Admin', '#', class: 'btn btn-login btn-admin' %> + <% end%> +
+
+ + + +<%= render "games" %> \ No newline at end of file diff --git a/app/views/games/index.json.jbuilder b/app/views/games/index.json.jbuilder new file mode 100644 index 00000000..e6305a05 --- /dev/null +++ b/app/views/games/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @games, partial: "games/game", as: :game diff --git a/app/views/games/new.html.erb b/app/views/games/new.html.erb new file mode 100644 index 00000000..47228e5d --- /dev/null +++ b/app/views/games/new.html.erb @@ -0,0 +1,11 @@ +<% content_for :title, "New game" %> + +

New game

+ +<%= render "form", game: @game %> + +
+ +
+ <%= link_to "Back to games", games_path %> +
diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb new file mode 100644 index 00000000..0ac3785d --- /dev/null +++ b/app/views/games/show.html.erb @@ -0,0 +1,8 @@ +

<%= notice %>

+ +
+ <%= link_to "Edit this game", edit_game_path(@game) %> | + <%= link_to "Back to games", games_path %> + + <%= button_to "Destroy this game", @game, method: :delete %> +
diff --git a/app/views/games/show.json.jbuilder b/app/views/games/show.json.jbuilder new file mode 100644 index 00000000..c04bffe0 --- /dev/null +++ b/app/views/games/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "games/game", game: @game diff --git a/config/routes.rb b/config/routes.rb index 814cd36c..5d45bfc7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,4 +9,14 @@ get "/users/:id", to: "users#show", as: "user" get "/logout", to: "sessions#logout", as: "logout" get "/auth/google_oauth2/callback", to: "sessions#omniauth" + + resources :games + root :to => redirect('/games') + + get "up" => "rails/health#show", as: :rails_health_check + + ## stub paths to demo game landing page + get '/spellingbee/:id', to: 'games#demo_game', as: 'spellingbee' + get '/wordle/:id', to: 'games#demo_game', as: 'wordle' + get '/letterboxed/:id', to: 'games#demo_game', as: 'letterboxed' end diff --git a/db/migrate/20241002002856_create_games.rb b/db/migrate/20241002002856_create_games.rb new file mode 100644 index 00000000..f07419e5 --- /dev/null +++ b/db/migrate/20241002002856_create_games.rb @@ -0,0 +1,9 @@ +class CreateGames < ActiveRecord::Migration[7.2] + def change + create_table :games do |t| + t.string 'name' + t.string 'game_path' + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d07c3c39..4fa8327e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -21,4 +21,11 @@ t.string "provider" t.index ["email"], name: "index_users_on_email", unique: true end + + create_table "games", force: :cascade do |t| + t.string "name" + t.string "game_path" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end end diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed9..75227866 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,3 +7,13 @@ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| # MovieGenre.find_or_create_by!(name: genre_name) # end + +initial_games = [ + {:name => 'Spelling Bee', :game_path => 'spellingbee_path'}, + {:name => 'Wordle', :game_path => 'wordle_path'}, + {:name => 'Letter Boxed', :game_path => 'letterboxed_path'} +] + +initial_games.each do |game| + Game.find_or_create_by!(game) +end diff --git a/features/.DS_Store b/features/.DS_Store new file mode 100644 index 00000000..c5cf507b Binary files /dev/null and b/features/.DS_Store differ diff --git a/test/controllers/games_controller_test.rb b/test/controllers/games_controller_test.rb new file mode 100644 index 00000000..90bfc200 --- /dev/null +++ b/test/controllers/games_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class GamesControllerTest < ActionDispatch::IntegrationTest + setup do + @game = games(:one) + end + + test "should get index" do + get games_url + assert_response :success + end + + test "should get new" do + get new_game_url + assert_response :success + end + + test "should create game" do + assert_difference("Game.count") do + post games_url, params: { game: { game_path: @game.game_path, name: @game.name } } + end + + assert_redirected_to game_url(Game.last) + end + + test "should show game" do + get game_url(@game) + assert_response :success + end + + test "should get edit" do + get edit_game_url(@game) + assert_response :success + end + + test "should update game" do + patch game_url(@game), params: { game: { game_path: @game.game_path, name: @game.name } } + assert_redirected_to game_url(@game) + end + + test "should destroy game" do + assert_difference("Game.count", -1) do + delete game_url(@game) + end + + assert_redirected_to games_url + end +end diff --git a/test/controllers/pages_controller_test.rb b/test/controllers/pages_controller_test.rb new file mode 100644 index 00000000..d975bb19 --- /dev/null +++ b/test/controllers/pages_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class PagesControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get pages_index_url + assert_response :success + end +end diff --git a/test/fixtures/games.yml b/test/fixtures/games.yml new file mode 100644 index 00000000..382f6d84 --- /dev/null +++ b/test/fixtures/games.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + description: MyText + +two: + name: MyString + description: MyText diff --git a/test/models/game_test.rb b/test/models/game_test.rb new file mode 100644 index 00000000..6628fae0 --- /dev/null +++ b/test/models/game_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class GameTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/system/games_test.rb b/test/system/games_test.rb new file mode 100644 index 00000000..1b7d1439 --- /dev/null +++ b/test/system/games_test.rb @@ -0,0 +1,43 @@ +require "application_system_test_case" + +class GamesTest < ApplicationSystemTestCase + setup do + @game = games(:one) + end + + test "visiting the index" do + visit games_url + assert_selector "h1", text: "Games" + end + + test "should create game" do + visit games_url + click_on "New game" + + fill_in "Game path", with: @game.game_path + fill_in "Name", with: @game.name + click_on "Create Game" + + assert_text "Game was successfully created" + click_on "Back" + end + + test "should update Game" do + visit game_url(@game) + click_on "Edit this game", match: :first + + fill_in "Game path", with: @game.game_path + fill_in "Name", with: @game.name + click_on "Update Game" + + assert_text "Game was successfully updated" + click_on "Back" + end + + test "should destroy Game" do + visit game_url(@game) + click_on "Destroy this game", match: :first + + assert_text "Game was successfully destroyed" + end +end