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