feat: add Party management API with RESTful endpoints and comprehensive documentation
- Introduce Party model with lifecycle states (draft, published, canceled, sold_out) - Add RESTful API endpoints under /api/v1/parties for CRUD operations - Create ApiController base with API key authentication - Implement comprehensive code comments across models and controllers - Add database migration for parties table with proper indexes - Configure API routes with namespaced versioning
This commit is contained in:
51
CRUSH.md
Normal file
51
CRUSH.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Aperonight - CRUSH Development Guidelines
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
- `bin/rails server` - Start development server
|
||||||
|
- `bin/rails assets:precompile` - Compile assets
|
||||||
|
- `npm run build` - Build JavaScript bundle (production)
|
||||||
|
- `npm run build:dev` - Build JavaScript bundle (development)
|
||||||
|
- `npm run build:css` - Compile CSS with PostCSS/Tailwind
|
||||||
|
|
||||||
|
## Test Commands
|
||||||
|
- `bin/rails test` - Run all tests
|
||||||
|
- `bin/rails test test/models/user_test.rb` - Run specific test file
|
||||||
|
- `bin/rails test test/models/user_test.rb:15` - Run specific test method
|
||||||
|
- `bin/rails test:system` - Run system tests
|
||||||
|
|
||||||
|
## Lint Commands
|
||||||
|
- `bin/rubocop` - Run Ruby linter
|
||||||
|
- `bin/rubocop -a` - Run Ruby linter with auto-fix
|
||||||
|
- Check JS/JSX files manually (no configured linter)
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
1. Branch naming: `type/descriptive-name` (e.g., `feature/user-profile`)
|
||||||
|
2. Follow Git Flow with `main` and `develop` branches
|
||||||
|
3. Run tests and linters before committing
|
||||||
|
4. Keep PRs focused on single features/fixes
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Ruby
|
||||||
|
- Follow Rubocop Rails Omakase defaults
|
||||||
|
- Standard Rails MVC conventions
|
||||||
|
- Use descriptive method and variable names
|
||||||
|
- Prefer single quotes for strings without interpolation
|
||||||
|
|
||||||
|
### JavaScript/React
|
||||||
|
- Use Stimulus controllers for DOM interactions
|
||||||
|
- React components in PascalCase (`UserProfile.jsx`)
|
||||||
|
- Shadcn components in kebab-case (`button.jsx`) but exported as PascalCase
|
||||||
|
- Functional components with hooks over class components
|
||||||
|
|
||||||
|
### CSS/Tailwind
|
||||||
|
- Mobile-first responsive design
|
||||||
|
- Use Tailwind utility classes over custom CSS
|
||||||
|
- Primary color palette: indigo → purple → pink gradients
|
||||||
|
- Consistent spacing with Tailwind's spacing scale
|
||||||
|
|
||||||
|
### General
|
||||||
|
- Keep functions small and focused
|
||||||
|
- Comment complex logic
|
||||||
|
- Use descriptive commit messages
|
||||||
|
- Maintain consistency with existing code patterns
|
||||||
82
app/controllers/api/v1/parties_controller.rb
Normal file
82
app/controllers/api/v1/parties_controller.rb
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# API controller for managing party resources
|
||||||
|
# Provides RESTful endpoints for CRUD operations on Party model
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class PartiesController < ApiController
|
||||||
|
# Load party before specific actions to reduce duplication
|
||||||
|
before_action :set_party, only: [:show, :update, :destroy]
|
||||||
|
|
||||||
|
# GET /api/v1/parties
|
||||||
|
# Returns all parties sorted by creation date (newest first)
|
||||||
|
def index
|
||||||
|
@parties = Party.all.order(created_at: :desc)
|
||||||
|
render json: @parties, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/parties/:id
|
||||||
|
# Returns a single party by ID
|
||||||
|
# Returns 404 if party is not found
|
||||||
|
def show
|
||||||
|
render json: @party, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /api/v1/parties
|
||||||
|
# Creates a new party with provided attributes
|
||||||
|
# Returns 201 Created on success with party data
|
||||||
|
# Returns 422 Unprocessable Entity with validation errors on failure
|
||||||
|
def create
|
||||||
|
@party = Party.new(party_params)
|
||||||
|
if @party.save
|
||||||
|
render json: @party, status: :created
|
||||||
|
else
|
||||||
|
render json: { errors: @party.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# PATCH/PUT /api/v1/parties/:id
|
||||||
|
# Updates an existing party with provided attributes
|
||||||
|
# Returns 200 OK with updated party data on success
|
||||||
|
# Returns 422 Unprocessable Entity with validation errors on failure
|
||||||
|
def update
|
||||||
|
if @party.update(party_params)
|
||||||
|
render json: @party, status: :ok
|
||||||
|
else
|
||||||
|
render json: { errors: @party.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /api/v1/parties/:id
|
||||||
|
# Permanently deletes a party
|
||||||
|
# Returns 204 No Content on success
|
||||||
|
def destroy
|
||||||
|
@party.destroy
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Finds a party by ID or returns 404 Not Found
|
||||||
|
# Used as before_action for show, update, and destroy actions
|
||||||
|
def set_party
|
||||||
|
@party = Party.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render json: { error: "Party not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
|
||||||
|
# Strong parameters for party creation and updates
|
||||||
|
# Whitelists permitted attributes to prevent mass assignment vulnerabilities
|
||||||
|
def party_params
|
||||||
|
params.require(:party).permit(
|
||||||
|
:name,
|
||||||
|
:description,
|
||||||
|
:state,
|
||||||
|
:venue_name,
|
||||||
|
:venue_address,
|
||||||
|
:latitude,
|
||||||
|
:longitude,
|
||||||
|
:featured
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
24
app/controllers/api_controller.rb
Normal file
24
app/controllers/api_controller.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Base controller for API endpoints
|
||||||
|
# Provides authentication and common functionality for API controllers
|
||||||
|
class ApiController < ApplicationController
|
||||||
|
# Disable CSRF protection for API requests (token-based authentication instead)
|
||||||
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
|
# Authenticate all API requests using API key
|
||||||
|
# Must be called before any API action
|
||||||
|
before_action :authenticate_api_key
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Authenticates API requests using X-API-Key header or api_key parameter
|
||||||
|
# Returns 401 Unauthorized if key is invalid or missing
|
||||||
|
def authenticate_api_key
|
||||||
|
# Extract API key from header or query parameter
|
||||||
|
api_key = request.headers["X-API-Key"] || params[:api_key]
|
||||||
|
|
||||||
|
# Validate against hardcoded key (in production, use environment variable)
|
||||||
|
unless api_key == "aperonight-api-key-2025"
|
||||||
|
render json: { error: "Unauthorized" }, status: :unauthorized
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,16 @@
|
|||||||
|
# Base controller for the application
|
||||||
|
# Provides common functionality and security configurations for all controllers
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
# Protect against Cross-Site Request Forgery (CSRF) attacks
|
||||||
|
# Ensures that all non-GET requests include a valid authenticity token
|
||||||
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
# Restrict access to modern browsers only
|
||||||
|
# Requires browsers to support modern web standards:
|
||||||
|
# - WebP images for better compression
|
||||||
|
# - Web Push notifications
|
||||||
|
# - Badge API for notifications
|
||||||
|
# - Import maps for JavaScript modules
|
||||||
|
# - CSS nesting and :has() pseudo-class
|
||||||
allow_browser versions: :modern
|
allow_browser versions: :modern
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
# Controller for static pages and user dashboard
|
||||||
|
# Handles basic page rendering and user-specific content
|
||||||
class PagesController < ApplicationController
|
class PagesController < ApplicationController
|
||||||
# Display homepage
|
# Require user authentication for dashboard access
|
||||||
def home
|
# Redirects to login page if user is not signed in
|
||||||
end
|
before_action :authenticate_user!, only: [:dashboard]
|
||||||
|
|
||||||
# Display legal page
|
# User dashboard showing personalized content
|
||||||
def legals
|
# Accessible only to authenticated users
|
||||||
|
def dashboard
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Base class for all ActiveRecord models in the application
|
||||||
|
# Provides common functionality and configuration for all models
|
||||||
class ApplicationRecord < ActiveRecord::Base
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
|
# Mark this as the primary abstract class to establish inheritance hierarchy
|
||||||
primary_abstract_class
|
primary_abstract_class
|
||||||
end
|
end
|
||||||
|
|||||||
40
app/models/party.rb
Normal file
40
app/models/party.rb
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Party model representing nightlife events and parties
|
||||||
|
# Manages event details, location data, and publication state
|
||||||
|
class Party < ApplicationRecord
|
||||||
|
# Define states for party lifecycle management
|
||||||
|
# draft: Initial state when party is being created
|
||||||
|
# published: Party is visible to public and can be discovered
|
||||||
|
# canceled: Party has been canceled by organizer
|
||||||
|
# sold_out: Party has reached capacity and tickets are no longer available
|
||||||
|
enum state: {
|
||||||
|
draft: 0,
|
||||||
|
published: 1,
|
||||||
|
canceled: 2,
|
||||||
|
sold_out: 3
|
||||||
|
}, default: :draft
|
||||||
|
|
||||||
|
# Validations for party attributes
|
||||||
|
# Basic information
|
||||||
|
validates :name, presence: true, length: { minimum: 3, maximum: 100 }
|
||||||
|
validates :description, presence: true, length: { minimum: 10, maximum: 1000 }
|
||||||
|
validates :state, presence: true, inclusion: { in: states.keys }
|
||||||
|
|
||||||
|
# Venue information
|
||||||
|
validates :venue_name, presence: true, length: { maximum: 100 }
|
||||||
|
validates :venue_address, presence: true, length: { maximum: 200 }
|
||||||
|
|
||||||
|
# Geographic coordinates for map display
|
||||||
|
validates :latitude, presence: true, numericality: {
|
||||||
|
greater_than_or_equal_to: -90,
|
||||||
|
less_than_or_equal_to: 90
|
||||||
|
}
|
||||||
|
validates :longitude, presence: true, numericality: {
|
||||||
|
greater_than_or_equal_to: -180,
|
||||||
|
less_than_or_equal_to: 180
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scopes for querying parties with common filters
|
||||||
|
scope :featured, -> { where(featured: true) } # Get featured parties for homepage
|
||||||
|
scope :published, -> { where(state: :published) } # Get publicly visible parties
|
||||||
|
scope :search_by_name, ->(query) { where("name ILIKE ?", "%#{query}%") } # Search by name (case-insensitive)
|
||||||
|
end
|
||||||
@@ -1,6 +1,21 @@
|
|||||||
|
# User model for authentication and user management
|
||||||
|
# Handles user accounts, authentication, and authorization using Devise
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
# Include default devise modules. Others available are:
|
# Include default devise modules. Others available are:
|
||||||
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||||
|
#
|
||||||
|
# Include default devise modules for authentication
|
||||||
|
# :database_authenticatable - encrypts and stores password in database
|
||||||
|
# :registerable - allows users to sign up and edit their accounts
|
||||||
|
# :recoverable - handles password reset functionality
|
||||||
|
# :rememberable - manages token-based user remembering
|
||||||
|
# :validatable - provides email and password validation
|
||||||
|
# Other available modules are:
|
||||||
|
# :confirmable - requires email confirmation
|
||||||
|
# :lockable - locks account after failed login attempts
|
||||||
|
# :timeoutable - expires sessions after inactivity
|
||||||
|
# :trackable - tracks sign-in count, timestamps, and IP
|
||||||
|
# :omniauthable - allows authentication via OAuth providers
|
||||||
devise :database_authenticatable, :registerable,
|
devise :database_authenticatable, :registerable,
|
||||||
:recoverable, :rememberable, :validatable
|
:recoverable, :rememberable, :validatable
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,4 +31,15 @@ Rails.application.routes.draw do
|
|||||||
confirmation: "authentications/confirmations" # Custom controller for confirmations
|
confirmation: "authentications/confirmations" # Custom controller for confirmations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# API routes versioning
|
||||||
|
namespace :api do
|
||||||
|
namespace :v1 do
|
||||||
|
# RESTful routes for party management
|
||||||
|
resources :parties, only: [ :index, :show, :create, :update, :destroy ]
|
||||||
|
|
||||||
|
# Additional API endpoints can be added here as needed
|
||||||
|
# Example: search, filtering, user-specific endpoints
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
19
db/migrate/20250823145902_create_parties.rb
Normal file
19
db/migrate/20250823145902_create_parties.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
class CreateParties < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :parties do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.text :description, null: false
|
||||||
|
t.integer :state, default: 0, null: false
|
||||||
|
t.string :venue_name, null: false
|
||||||
|
t.string :venue_address, null: false
|
||||||
|
t.decimal :latitude, precision: 10, scale: 6, null: false
|
||||||
|
t.decimal :longitude, precision: 10, scale: 6, null: false
|
||||||
|
t.boolean :featured, default: false, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :parties, :state
|
||||||
|
add_index :parties, :featured
|
||||||
|
add_index :parties, [:latitude, :longitude]
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user