Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> This commit refactors the entire application to replace the 'parties' concept with 'events'. All controllers, models, views, and related files have been updated to reflect this change. The parties table has been replaced with an events table, and all related functionality has been updated accordingly.
8.5 KiB
Executable File
8.5 KiB
Executable File
Aperonight - Technical Architecture
Overview
Aperonight is a Ruby on Rails web application designed for proposing night parties in Paris and allowing event makers to create their own events. The application serves two primary user groups:
For Customers:
- View upcoming and past parties
- Book tickets with customizable bundles (simple entry, VIP, group passes, etc.)
- Complete secure payments via credit card, PayPal, or bank transfer
- Access mobile-friendly interface for ticket management
- Receive unique, scannable tickets (QR codes)
For Promoters:
- Create and schedule parties
- Define custom ticket bundles and pricing
- Aggregate events from external platforms (Shogun, Bizouk, Weezevent)
- Scan tickets at events using mobile devices
Technical Architecture
1. Database Schema
# User - Handles both customers and promoters
create_table :users do |t|
t.string :email
t.string :password_digest
t.string :role # customer or promoter
t.timestamps
end
# Event - Events created by promoters
create_table :events do |t|
t.string :name
t.text :description
t.datetime :start_time
t.datetime :end_time
t.string :location
t.integer :promoter_id
t.timestamps
end
# TicketType - Customizable bundles defined by promoters
create_table :ticket_types do |t|
t.string :name
t.text :description
t.decimal :price
t.integer :event_id
t.timestamps
end
# Ticket - Individual ticket instances purchased by customers
create_table :tickets do |t|
t.string :uuid
t.string :qr_code
t.integer :event_id
t.integer :user_id
t.integer :ticket_type_id
t.boolean :used, default: false
t.timestamps
end
# Payment - Transaction records for ticket purchases
create_table :payments do |t|
t.string :payment_method # credit_card, paypal, bank_account
t.string :transaction_id
t.integer :user_id
t.integer :ticket_id
t.decimal :amount
t.timestamps
end
2. Core Models
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tickets
has_many :payments
has_many :parties, foreign_key: 'promoter_id'
end
class Event < ApplicationRecord
belongs_to :promoter, class_name: 'User'
has_many :tickets
has_many :ticket_types
end
class TicketType < ApplicationRecord
belongs_to :event
has_many :tickets
end
class Ticket < ApplicationRecord
belongs_to :event
belongs_to :user
belongs_to :ticket_type
has_one :payment
before_create :generate_uuid_and_qr_code
private
def generate_uuid_and_qr_code
self.uuid = SecureRandom.uuid
self.qr_code = RQRCode::QRCode.new(self.uuid).as_svg
end
end
class Payment < ApplicationRecord
belongs_to :user
belongs_to :ticket
enum payment_method: {
credit_card: 'credit_card',
paypal: 'paypal',
bank_account: 'bank_account'
}
after_create :process_payment
private
def process_payment
case self.payment_method
when 'credit_card'
process_stripe_payment
when 'paypal'
process_paypal_payment
when 'bank_account'
process_bank_account_payment
end
end
end
3. Key Controllers
Parties Controller
class PartiesController < ApplicationController
before_action :authenticate_user!
before_action :set_event, only: [:show, :edit, :update, :destroy]
def index
@parties = Event.all
end
def show
@ticket_types = @event.ticket_types
end
def new
@event = Event.new
@event.ticket_types.build
end
def create
@event = current_user.parties.build(event_params)
if @event.save
redirect_to @event, notice: 'Event was successfully created.'
else
render :new
end
end
private
def set_event
@event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(
:name, :description, :start_time, :end_time, :location,
ticket_types_attributes: [:id, :name, :description, :price, :_destroy]
)
end
end
Tickets Controller
class TicketsController < ApplicationController
before_action :authenticate_user!
before_action :set_event, only: [:new, :create]
def new
@ticket = Ticket.new
end
def create
@ticket = current_user.tickets.build(ticket_params)
if @ticket.save
redirect_to @ticket, notice: 'Ticket was successfully booked.'
else
render :new
end
end
def scan
end
def validate
qr_code = params[:qr_code]
ticket = Ticket.find_by(qr_code: qr_code)
if ticket && !ticket.used
ticket.update(used: true)
render json: { valid: true }
else
render json: { valid: false }
end
end
private
def set_event
@event = Event.find(params[:event_id])
end
def ticket_params
params.require(:ticket).permit(:ticket_type_id, :event_id)
end
end
4. Payment Integration
Stripe Configuration
# config/initializers/stripe.rb
Rails.configuration.stripe = {
publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
secret_key: ENV['STRIPE_SECRET_KEY']
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]
PayPal Configuration
# config/initializers/paypal.rb
PayPal::SDK.configure({
mode: ENV['PAYPAL_MODE'], # 'sandbox' or 'live'
client_id: ENV['PAYPAL_CLIENT_ID'],
client_secret: ENV['PAYPAL_CLIENT_SECRET']
})
5. Frontend Considerations
Mobile Ticket Scanning
<!-- app/views/tickets/scan.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>Scan Ticket</title>
<script src="https://unpkg.com/html5-qrcode"></script>
</head>
<body>
<h1>Scan Ticket</h1>
<div id="reader" width="500"></div>
<div id="result"></div>
<script>
function docReady(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
docReady(function () {
var resultContainer = document.getElementById('result');
var lastResult, countResults = 0;
function onScanSuccess(qrCodeMessage) {
if (qrCodeMessage !== lastResult) {
++countResults;
lastResult = qrCodeMessage;
resultContainer.innerHTML = `<span class="label">Last scanned QR Code: </span> <a href="${qrCodeMessage}">${qrCodeMessage}</a>`;
fetch('/tickets/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ qr_code: qrCodeMessage })
})
.then(response => response.json())
.then(data => {
if (data.valid) {
resultContainer.innerHTML += '<p>Ticket is valid.</p>';
} else {
resultContainer.innerHTML += '<p>Ticket is invalid.</p>';
}
})
.catch(error => {
console.error('Error:', error);
});
}
}
var html5QrcodeScanner = new Html5QrcodeScanner(
"reader", { fps: 10, qrbox: 250 });
html5QrcodeScanner.render(onScanSuccess);
});
</script>
</body>
</html>
6. Routes Configuration
# config/routes.rb
Rails.application.routes.draw do
devise_for :users
resources :parties do
resources :ticket_types, only: [:new, :create, :edit, :update, :destroy]
end
resources :ticket_types, only: [:index, :show]
resources :tickets do
resources :payments, only: [:new, :create]
collection do
post 'validate'
end
end
get 'paypal_success', to: 'payments#paypal_success'
get 'paypal_cancel', to: 'payments#paypal_cancel'
get 'tickets/scan', to: 'tickets#scan'
root 'parties#index'
end
Implementation Recommendations
Authentication & Authorization
- Use Devise for user authentication
- Implement Pundit or CanCanCan for role-based access control
- Distinguish clearly between customer and promoter permissions
Payment Processing
- Integrate Stripe for credit card payments
- Add PayPal support through official SDK
- Consider Plaid for bank account integration
Performance & Scalability
- Implement Redis for caching frequently accessed data
- Use CDN for static assets (images, CSS, JS)
- Employ background job processing (Sidekiq) for emails and payments
- Optimize database queries with proper indexing
Security Considerations
- Validate all user inputs
- Sanitize HTML output to prevent XSS
- Secure payment processing with PCI compliance
- Implement rate limiting for API endpoints
- Regular security audits and dependency updates