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.
364 lines
8.5 KiB
Markdown
Executable File
364 lines
8.5 KiB
Markdown
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
|
|
|
|
```ruby
|
|
# 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
|
|
|
|
```ruby
|
|
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
|
|
```ruby
|
|
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
|
|
```ruby
|
|
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
|
|
```ruby
|
|
# 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
|
|
```ruby
|
|
# 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
|
|
```erb
|
|
<!-- 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
|
|
|
|
```ruby
|
|
# 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
|
|
|