class GamesController < ApplicationController before_action :require_authentication before_action :set_game, only: [ :show, :edit, :update, :destroy ] def index @games = current_user.games.includes(:platform, :genres, :collections) # Filtering @games = @games.by_platform(params[:platform_id]) if params[:platform_id].present? @games = @games.by_genre(params[:genre_id]) if params[:genre_id].present? @games = @games.where(format: params[:format]) if params[:format].present? @games = @games.where(completion_status: params[:completion_status]) if params[:completion_status].present? @games = @games.search(params[:search]) if params[:search].present? # Sorting @games = case params[:sort] when "alphabetical" then @games.alphabetical when "recent" then @games.recent when "rated" then @games.rated else @games.alphabetical end @games = @games.page(params[:page]).per(25) # For filters @platforms = Platform.order(:name) @genres = Genre.order(:name) end def show end def new @game = current_user.games.build @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) end def create @game = current_user.games.build(game_params) if @game.save # If game was created with IGDB ID, sync the metadata sync_igdb_metadata_after_create if @game.igdb_id.present? redirect_to @game, notice: "Game was successfully created." else @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) render :new, status: :unprocessable_entity end end def edit @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) end def update if @game.update(game_params) redirect_to @game, notice: "Game was successfully updated." else @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) render :edit, status: :unprocessable_entity end end def destroy @game.destroy redirect_to games_path, notice: "Game was successfully deleted." end def import @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) end def bulk_edit @game_ids = params[:game_ids] || [] if @game_ids.empty? redirect_to games_path, alert: "Please select at least one game to edit." return end @games = current_user.games.where(id: @game_ids) @platforms = Platform.order(:name) @genres = Genre.order(:name) @collections = current_user.collections.order(:name) end def bulk_update @game_ids = params[:game_ids] || [] if @game_ids.empty? redirect_to games_path, alert: "No games selected." return end @games = current_user.games.where(id: @game_ids) updated_count = 0 @games.each do |game| updates = {} # Only update fields that have values provided updates[:completion_status] = params[:completion_status] if params[:completion_status].present? updates[:location] = params[:location] if params[:location].present? updates[:condition] = params[:condition] if params[:condition].present? # Handle collection assignment if params[:collection_action].present? case params[:collection_action] when "add" if params[:collection_ids].present? game.collection_ids = (game.collection_ids + params[:collection_ids].map(&:to_i)).uniq end when "remove" if params[:collection_ids].present? game.collection_ids = game.collection_ids - params[:collection_ids].map(&:to_i) end when "replace" game.collection_ids = params[:collection_ids] if params[:collection_ids].present? end end # Handle genre assignment if params[:genre_action].present? case params[:genre_action] when "add" if params[:genre_ids].present? game.genre_ids = (game.genre_ids + params[:genre_ids].map(&:to_i)).uniq end when "remove" if params[:genre_ids].present? game.genre_ids = game.genre_ids - params[:genre_ids].map(&:to_i) end when "replace" game.genre_ids = params[:genre_ids] if params[:genre_ids].present? end end if game.update(updates) updated_count += 1 end end redirect_to games_path, notice: "Successfully updated #{updated_count} game(s)." end def bulk_create require "csv" results = { created: 0, failed: 0, errors: [] } if params[:csv_file].present? csv_text = params[:csv_file].read csv = CSV.parse(csv_text, headers: true) csv.each_with_index do |row, index| platform = Platform.find_by(name: row["platform"]) || Platform.find_by(abbreviation: row["platform"]) unless platform results[:failed] += 1 results[:errors] << "Row #{index + 2}: Platform '#{row['platform']}' not found" next end game = current_user.games.build( title: row["title"], platform: platform, format: row["format"]&.downcase || "physical", date_added: row["date_added"] || Date.current, completion_status: row["completion_status"]&.downcase, user_rating: row["user_rating"], condition: row["condition"]&.downcase, price_paid: row["price_paid"], location: row["location"], digital_store: row["digital_store"], notes: row["notes"] ) # Handle genres if row["genres"].present? genre_names = row["genres"].split("|").map(&:strip) genres = Genre.where(name: genre_names) game.genres = genres end if game.save results[:created] += 1 else results[:failed] += 1 results[:errors] << "Row #{index + 2}: #{game.errors.full_messages.join(", ")}" end end flash[:notice] = "Created #{results[:created]} games. Failed: #{results[:failed]}" flash[:alert] = results[:errors].join("
").html_safe if results[:errors].any? redirect_to games_path else redirect_to import_games_path, alert: "Please select a CSV file to upload." end end def search_igdb query = params[:q].to_s.strip platform_id = params[:platform_id] if query.length < 2 render json: [] return end begin service = IgdbService.new platform = platform_id.present? ? Platform.find_by(id: platform_id) : nil # Search IGDB (limit to 10 results for autocomplete) results = service.search_game(query, platform, 10) # Format results for autocomplete formatted_results = results.map do |result| # Map IGDB genres to our local genre IDs genre_ids = map_igdb_genres_to_ids(result[:genres] || []) { igdb_id: result[:igdb_id], name: result[:name], platform: result[:platform_name], year: result[:release_year], cover_url: result[:cover_url], summary: result[:summary], genres: result[:genres], genre_ids: genre_ids, confidence: result[:confidence_score] } end render json: formatted_results rescue => e Rails.logger.error("IGDB search error: #{e.message}") render json: [], status: :internal_server_error end end def search_locations query = params[:q].to_s.strip # Get unique locations from user's games that match the query locations = current_user.games .where.not(location: [nil, ""]) .where("location ILIKE ?", "%#{query}%") .select(:location) .distinct .order(:location) .limit(10) .pluck(:location) render json: locations end def search_stores query = params[:q].to_s.strip # Get unique digital stores from user's games that match the query stores = current_user.games .where.not(digital_store: [nil, ""]) .where("digital_store ILIKE ?", "%#{query}%") .select(:digital_store) .distinct .order(:digital_store) .limit(10) .pluck(:digital_store) render json: stores end private def set_game @game = current_user.games.includes(:igdb_game).find(params[:id]) end def sync_igdb_metadata_after_create # Fetch full game data from IGDB service = IgdbService.new igdb_data = service.get_game(@game.igdb_id) return unless igdb_data # Create or update IgdbGame record igdb_game = IgdbGame.find_or_create_by!(igdb_id: @game.igdb_id) do |ig| ig.name = igdb_data["name"] ig.slug = igdb_data["slug"] ig.summary = igdb_data["summary"] ig.first_release_date = igdb_data["first_release_date"] ? Time.at(igdb_data["first_release_date"]).to_date : nil # Extract cover URL cover_url = igdb_data.dig("cover", "url")&.split("/")&.last&.sub(".jpg", "") ig.cover_url = cover_url ig.last_synced_at = Time.current end igdb_game.increment_match_count! # Update game with IGDB metadata @game.update( igdb_matched_at: Time.current, igdb_match_status: "matched", igdb_match_confidence: 100.0 ) # Map and assign genres if igdb_data["genres"].present? genre_names = igdb_data["genres"].map { |g| g["name"] } assign_igdb_genres_to_game(genre_names) end rescue => e Rails.logger.error("Failed to sync IGDB metadata: #{e.message}") end def map_igdb_genres_to_ids(genre_names) return [] if genre_names.blank? # Genre mapping (same as in IgdbMatchSuggestion) genre_mappings = { "Role-playing (RPG)" => "RPG", "Fighting" => "Fighting", "Shooter" => "Shooter", "Platform" => "Platformer", "Puzzle" => "Puzzle", "Racing" => "Racing", "Real Time Strategy (RTS)" => "Strategy", "Simulator" => "Simulation", "Sport" => "Sports", "Strategy" => "Strategy", "Adventure" => "Adventure", "Indie" => "Indie", "Arcade" => "Arcade", "Hack and slash/Beat 'em up" => "Action" } genre_ids = [] genre_names.each do |igdb_genre_name| # Try exact match local_genre = Genre.find_by("LOWER(name) = ?", igdb_genre_name.downcase) # Try mapped name if local_genre.nil? && genre_mappings[igdb_genre_name] mapped_name = genre_mappings[igdb_genre_name] local_genre = Genre.find_by("LOWER(name) = ?", mapped_name.downcase) end genre_ids << local_genre.id if local_genre end genre_ids end def assign_igdb_genres_to_game(genre_names) genre_ids = map_igdb_genres_to_ids(genre_names) genre_ids.each do |genre_id| genre = Genre.find(genre_id) @game.genres << genre unless @game.genres.include?(genre) end end def game_params params.require(:game).permit( :title, :platform_id, :format, :date_added, :completion_status, :user_rating, :notes, :condition, :price_paid, :location, :digital_store, :custom_entry, :igdb_id, genre_ids: [], collection_ids: [] ) end end