From a558f7fc9a09a6c1706e49dcdf9effc6fd9593c6 Mon Sep 17 00:00:00 2001 From: kbe Date: Sun, 24 Aug 2025 22:33:27 +0200 Subject: [PATCH] docs: add comprehensive technical architecture documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace placeholder content with detailed architecture documentation - Document core models, controllers, and database schema - Include implementation recommendations for authentication, payments, and security - Add frontend considerations for mobile ticket scanning - Define routes configuration for the application This provides a solid technical foundation for the Aperonight platform development. 💘 Generated with Crush Co-Authored-By: Crush --- docs/architecture.md | 913 ++++++------------------------------------- 1 file changed, 110 insertions(+), 803 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 8e4188b..963657b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,61 +1,28 @@ -Cette conversation a été générée avec Duck.ai (https://duck.ai) à l'aide du modèle Mistral Small 3 d'Mistral AI. Les chats IA peuvent afficher des informations inexactes ou offensantes (voir https://duckduckgo.com/duckai/privacy-terms pour en savoir plus). +# Aperonight - Technical Architecture -==================== +## Overview -Message 1 sur 6 de l'utilisateur - 16/08/2025, 08:29:15: -For a git Ruby on Rails web project with the the goal to propose night parties in Paris and allow party maker to create their own parties. +Aperonight is a Ruby on Rails web application designed for proposing night parties in Paris and allowing party makers to create their own events. The application serves two primary user groups: -The application for customers must: -* shows parties (upcoming and past) -* permit user to book one or many tickets for a party (choosing their bundle : simple entry, 4 people pass with bottle, VIP, etc) -* accept credit cards, paypal, bank account payments -* generate unique tickets (per person or per order -* be able to be used on mobile to permit promotors to scan tickets using phone +### 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 promotors: -* allow them to create and program parties -* aggregate parties from other website (shogun, bizouk, weezevent) +### 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 -Give advices on this application and give the arhitecture to implement. +## Technical Architecture -Mistral Small 3: -Creating a Ruby on Rails web application for proposing night parties in Paris and allowing party makers to create their own parties is an exciting project. Below are some advices and an architecture to implement this application. - -### Advices - -1. **User Authentication and Authorization**: - - Use Devise for user authentication. - - Use Pundit or CanCanCan for authorization to manage different roles (customers, promoters). - -2. **Payment Integration**: - - Use Stripe for credit card payments. - - Use PayPal for PayPal payments. - - For bank account payments, consider integrating with a service like Plaid or directly with a banking API. - -3. **Ticket Generation**: - - Generate unique tickets using a combination of UUIDs and QR codes. - - Use a library like `rufus-scheduler` to handle ticket expiration and validation. - -4. **Mobile Compatibility**: - - Ensure the application is responsive and mobile-friendly. - - Use a library like `qrcode` to generate QR codes for tickets. - - For scanning tickets, use a mobile-friendly QR code scanner library like `zxing` or `html5-qrcode`. - -5. **Aggregation of Parties**: - - Use web scraping or APIs provided by other websites (Shogun, Bizouk, Weezevent) to aggregate parties. - - Consider using a background job processor like Sidekiq for periodic data aggregation. - -6. **Performance and Scalability**: - - Use caching mechanisms like Redis or Memcached to improve performance. - - Consider using a CDN for static assets. - - Implement background jobs for time-consuming tasks like sending emails or processing payments. - -### Architecture - -#### 1. **Database Schema** +### 1. Database Schema ```ruby -# User +# User - Handles both customers and promoters create_table :users do |t| t.string :email t.string :password_digest @@ -63,7 +30,7 @@ create_table :users do |t| t.timestamps end -# Party +# Party - Events created by promoters create_table :parties do |t| t.string :name t.text :description @@ -74,18 +41,27 @@ create_table :parties do |t| t.timestamps end -# Ticket +# TicketType - Customizable bundles defined by promoters +create_table :ticket_types do |t| + t.string :name + t.text :description + t.decimal :price + t.integer :party_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 :party_id t.integer :user_id - t.string :bundle_type # simple entry, 4 people pass with bottle, VIP, etc + t.integer :ticket_type_id t.boolean :used, default: false t.timestamps end -# Payment +# Payment - Transaction records for ticket purchases create_table :payments do |t| t.string :payment_method # credit_card, paypal, bank_account t.string :transaction_id @@ -96,116 +72,7 @@ create_table :payments do |t| end ``` -#### 2. **Models** - -```ruby -class User < ApplicationRecord - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable - has_many :tickets - has_many :payments -end - -class Party < ApplicationRecord - belongs_to :promoter, class_name: 'User' - has_many :tickets -end - -class Ticket < ApplicationRecord - belongs_to :party - belongs_to :user - 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 -end -``` - -#### 3. **Controllers** - -```ruby -class PartiesController < ApplicationController - before_action :authenticate_user! - before_action :set_party, only: [:show, :edit, :update, :destroy] - - def index - @parties = Party.all - end - - def show - end - - def new - @party = Party.new - end - - def create - @party = current_user.parties.build(party_params) - if @party.save - redirect_to @party, notice: 'Party was successfully created.' - else - render :new - end - end - - def edit - end - - def update - if @party.update(party_params) - redirect_to @party, notice: 'Party was successfully updated.' - else - render :edit - end - end - - def destroy - @party.destroy - redirect_to parties_url, notice: 'Party was successfully destroyed.' - end - - private - - def set_party - @party = Party.find(params[:id]) - end - - def party_params - params.require(:party).permit - --------------------- - -Message 2 sur 6 de l'utilisateur - 16/08/2025, 08:30:32: -For the bundles, promotors must be able to define their own for party they craete - -Mistral Small 3: -To allow promoters to define their own bundles for the parties they create, you need to introduce a `Bundle` model that will be associated with the `Party` model. This will enable promoters to create custom bundles for each party, specifying details like the bundle type, price, and any additional perks. - -### Updated Database Schema - -```ruby -# Bundle -create_table :bundles do |t| - t.string :name - t.text :description - t.decimal :price - t.integer :party_id - t.timestamps -end -``` - -### Updated Models +### 2. Core Models ```ruby class User < ApplicationRecord @@ -219,13 +86,18 @@ end class Party < ApplicationRecord belongs_to :promoter, class_name: 'User' has_many :tickets - has_many :bundles + has_many :ticket_types +end + +class TicketType < ApplicationRecord + belongs_to :party + has_many :tickets end class Ticket < ApplicationRecord belongs_to :party belongs_to :user - belongs_to :bundle + belongs_to :ticket_type has_one :payment before_create :generate_uuid_and_qr_code @@ -241,18 +113,33 @@ end class Payment < ApplicationRecord belongs_to :user belongs_to :ticket -end -class Bundle < ApplicationRecord - belongs_to :party - has_many :tickets + 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 ``` -### Updated Controllers - -#### PartiesController +### 3. Key Controllers +#### Parties Controller ```ruby class PartiesController < ApplicationController before_action :authenticate_user! @@ -263,12 +150,12 @@ class PartiesController < ApplicationController end def show - @bundles = @party.bundles + @ticket_types = @party.ticket_types end def new @party = Party.new - @party.bundles.build + @party.ticket_types.build end def create @@ -280,23 +167,6 @@ class PartiesController < ApplicationController end end - def edit - @bundles = @party.bundles - end - - def update - if @party.update(party_params) - redirect_to @party, notice: 'Party was successfully updated.' - else - render :edit - end - end - - def destroy - @party.destroy - redirect_to parties_url, notice: 'Party was successfully destroyed.' - end - private def set_party @@ -304,206 +174,15 @@ class PartiesController < ApplicationController end def party_params - params.require(:party).permit(:name, :description, :start_time, :end_time, :location, bundles_attributes: [:id, :name, :description, :price, :_destroy]) + params.require(:party).permit( + :name, :description, :start_time, :end_time, :location, + ticket_types_attributes: [:id, :name, :description, :price, :_destroy] + ) end end ``` -#### BundlesController - -```ruby -class BundlesController < ApplicationController - before_action :authenticate_user! - before_action :set_bundle, only: [:show, :edit, :update, :destroy] - - def index - @bundles = Bundle.all - end - - def show - end - - def new - @bundle = Bundle.new - end - - def create - @bundle = Bundle.new(bundle_params) - if @bundle.save - redirect_to @bundle, notice: 'Bundle was successfully created.' - else - render :new - end - end - - def edit - end - - def update - if @bundle.update(bundle_params) - redirect_to @bundle, notice: 'Bundle was successfully updated.' - else - render :edit - end - end - - def destroy - @bundle.destroy - redirect_to bundles_url, notice: 'Bundle was successfully destroyed.' - end - - private - - def set_bundle - @bundle = Bundle.find(params[:id]) - end - - def bundle_params - params.require(:bundle).permit(:name, :description, :price, :party_id) - end -end -``` - -### Views - -#### parties/_form.html.erb - -```erb -<%= form_with(model: party, local: true) do |form| %> - <% if party.errors.any? %> -
-

<%= pluralize(party.errors.count, "error") %> prohibited this party from being saved:

-
    - <% party.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> - - --------------------- - -Message 3 sur 6 de l'utilisateur - 16/08/2025, 08:30:56: -Continue - -Mistral Small 3: -Certainly! Let's continue with the views and ensure that the form for creating and editing parties includes the ability to manage bundles. - -### Views (Continued) - -#### parties/_form.html.erb - -```erb -<%= form_with(model: party, local: true) do |form| %> - <% if party.errors.any? %> -
    -

    <%= pluralize(party.errors.count, "error") %> prohibited this party from being saved:

    -
      - <% party.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :name %> - <%= form.text_field :name %> -
    - -
    - <%= form.label :description %> - <%= form.text_area :description %> -
    - -
    - <%= form.label :start_time %> - <%= form.datetime_select :start_time %> -
    - -
    - <%= form.label :end_time %> - <%= form.datetime_select :end_time %> -
    - -
    - <%= form.label :location %> - <%= form.text_field :location %> -
    - -

    Bundles

    - <%= form.fields_for :bundles do |bundle_form| %> -
    -
    - <%= bundle_form.label :name %> - <%= bundle_form.text_field :name %> -
    - -
    - <%= bundle_form.label :description %> - <%= bundle_form.text_area :description %> -
    - -
    - <%= bundle_form.label :price %> - <%= bundle_form.number_field :price, step: 0.01 %> -
    - - <%= bundle_form.check_box :_destroy %> - <%= bundle_form.label :_destroy, "Remove" %> -
    - <% end %> - -
    - <%= form.submit %> -
    -<% end %> - -<%= link_to_add_association 'Add Bundle', form, :bundles %> -``` - -### JavaScript for Dynamic Fields - -To dynamically add and remove bundle fields, you can use the `cocoon` gem, which simplifies the process of handling nested forms. - -#### Gemfile - -```ruby -gem 'cocoon' -``` - -#### Application JavaScript - -Include the `cocoon` JavaScript in your application: - -```javascript -// app/assets/javascripts/application.js -//= require cocoon -``` - -### Routes - -Ensure your routes are set up to handle the nested resources: - -#### config/routes.rb - -```ruby -Rails.application.routes.draw do - devise_for :users - resources :parties do - resources :bundles, only: [:new, :create, :edit, :update, :destroy] - end - resources :bundles, only: [:index, :show] - resources :tickets - resources :payments - root 'parties#index' -end -``` - -### Ticket Booking - -To allow users to book tickets and choose bundles, you need to update the `TicketsController` and the corresponding views. - -#### TicketsController - +#### Tickets Controller ```ruby class TicketsController < ApplicationController before_action :authenticate_user! @@ -522,6 +201,20 @@ class TicketsController < ApplicationController 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_party @@ -529,64 +222,14 @@ class TicketsController < ApplicationController end def ticket_params - params.require(:ticket).permit(:bundle_id, :party_id) + params.require(:ticket).permit(:ticket_type_id, :party_id) end end ``` -#### tickets/_form.html.erb - -```erb -<%= form_with(model: [@party, @ticket], local: true) do |form| %> - <% if @ticket.errors.any? %> -
    -

    <%= pluralize(@ticket.errors.count, "error") %> prohibited this ticket from being saved:

    -
      - <% @ticket.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :bundle %> - <%= form.collection_select :bundle_id, @party.bundles, :id, :name %> -
    - -
    - <%= form.submit %> -
    -<% end %> -``` - -### Payment - --------------------- - -Message 4 sur 6 de l'utilisateur - 16/08/2025, 08:31:14: -Continue - -Mistral Small 3: -To handle payments, you need to integrate a payment gateway like Stripe for credit card payments and PayPal for PayPal payments. Below are the steps to set up payment processing in your Ruby on Rails application. - -### Payment Integration - -#### 1. **Add Gems** - -Add the necessary gems to your `Gemfile`: - -```ruby -gem 'stripe' -gem 'paypal-sdk-rest' -``` - -Then run `bundle install` to install the gems. - -#### 2. **Configure Stripe** - -Create an initializer for Stripe configuration: +### 4. Payment Integration +#### Stripe Configuration ```ruby # config/initializers/stripe.rb Rails.configuration.stripe = { @@ -597,10 +240,7 @@ Rails.configuration.stripe = { Stripe.api_key = Rails.configuration.stripe[:secret_key] ``` -#### 3. **Configure PayPal** - -Create an initializer for PayPal configuration: - +#### PayPal Configuration ```ruby # config/initializers/paypal.rb PayPal::SDK.configure({ @@ -610,325 +250,9 @@ PayPal::SDK.configure({ }) ``` -#### 4. **Payment Model** - -Update the `Payment` model to handle different payment methods: - -```ruby -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 - - def process_stripe_payment - # Implement Stripe payment processing - end - - def process_paypal_payment - # Implement PayPal payment processing - end - - def process_bank_account_payment - # Implement bank account payment processing - end -end -``` - -#### 5. **Payment Controller** - -Create a `PaymentsController` to handle payment creation and processing: - -```ruby -class PaymentsController < ApplicationController - before_action :authenticate_user! - before_action :set_ticket, only: [:new, :create] - - def new - @payment = Payment.new - end - - def create - @payment = current_user.payments.build(payment_params) - @payment.ticket = @ticket - if @payment.save - redirect_to @payment, notice: 'Payment was successfully processed.' - else - render :new - end - end - - private - - def set_ticket - @ticket = Ticket.find(params[:ticket_id]) - end - - def payment_params - params.require(:payment).permit(:payment_method, :amount) - end -end -``` - -#### 6. **Payment Views** - -Create views for the payment form: - -```erb - -<%= form_with(model: [@ticket, @payment], local: true) do |form| %> - <% if @payment.errors.any? %> -
    -

    <%= pluralize(@payment.errors.count, "error") %> prohibited this payment from being saved:

    -
      - <% @payment.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :payment_method %> - <%= form.select :payment_method, Payment.payment_methods.keys.to_a, prompt: 'Select payment method' %> -
    - -
    - <%= form.label :amount %> - <%= form.number_field :amount, step: 0.01 %> -
    - -
    - <%= form.submit %> -
    -<% end %> -``` - -### Stripe Payment Processing - -To process payments with Stripe, you need to create a Stripe charge. Update the `process_stripe_payment` method in the `Payment` model: - -```ruby -def process_stripe_payment - begin - charge = Stripe::Charge.create( - amount: (self.amount * 100).to_i, # amount in cents - currency: 'eur', - source: params[:stripeToken], - description: "Payment for ticket #{self.ticket.id}" - ) - self.transaction_id = charge.id - self.save - rescue Stripe::StripeError => e - # Handle error - self.errors.add(:base, e.message) - self.save - end -end -``` - -### PayPal Payment Processing - -To process payments with - --------------------- - -Message 5 sur 6 de l'utilisateur - 16/08/2025, 08:31:34: -Continue - -Mistral Small 3: -To process payments with PayPal, you need to create a PayPal payment and handle the redirection to PayPal for payment approval. Below are the steps to integrate PayPal payment processing. - -### PayPal Payment Processing - -#### 1. **Create PayPal Payment** - -Update the `process_paypal_payment` method in the `Payment` model to create a PayPal payment: - -```ruby -def process_paypal_payment - payment = PayPal::SDK::REST::Payment.new({ - intent: 'sale', - payer: { - payment_method: 'paypal' - }, - transactions: [{ - amount: { - total: self.amount.to_s, - currency: 'EUR' - }, - description: "Payment for ticket #{self.ticket.id}" - }], - redirect_urls: { - return_url: paypal_success_url, - cancel_url: paypal_cancel_url - } - }) - - if payment.create - self.transaction_id = payment.id - self.save - redirect_to payment.links.find { |link| link.rel == 'approval_url' }.href - else - self.errors.add(:base, payment.error.message) - self.save - end -end -``` - -#### 2. **Handle PayPal Redirect** - -Create routes and controllers to handle the PayPal redirect after payment: - -```ruby -# config/routes.rb -Rails.application.routes.draw do - devise_for :users - resources :parties do - resources :bundles, only: [:new, :create, :edit, :update, :destroy] - end - resources :bundles, only: [:index, :show] - resources :tickets do - resources :payments, only: [:new, :create] - end - get 'paypal_success', to: 'payments#paypal_success' - get 'paypal_cancel', to: 'payments#paypal_cancel' - root 'parties#index' -end -``` - -```ruby -# app/controllers/payments_controller.rb -class PaymentsController < ApplicationController - before_action :authenticate_user! - before_action :set_ticket, only: [:new, :create] - - def new - @payment = Payment.new - end - - def create - @payment = current_user.payments.build(payment_params) - @payment.ticket = @ticket - if @payment.save - if @payment.payment_method == 'paypal' - redirect_to @payment.transaction_id - else - redirect_to @payment, notice: 'Payment was successfully processed.' - end - else - render :new - end - end - - def paypal_success - payment_id = params[:paymentId] - payer_id = params[:PayerID] - - payment = PayPal::SDK::REST::Payment.find(payment_id) - if payment.execute(payer_id: payer_id) - @payment = Payment.find_by(transaction_id: payment_id) - @payment.update(status: 'completed') - redirect_to @payment, notice: 'Payment was successfully completed.' - else - redirect_to paypal_cancel_path, alert: 'Payment failed.' - end - end - - def paypal_cancel - redirect_to root_path, alert: 'Payment was canceled.' - end - - private - - def set_ticket - @ticket = Ticket.find(params[:ticket_id]) - end - - def payment_params - params.require(:payment).permit(:payment_method, :amount) - end -end -``` - -### Bank Account Payment Processing - -For bank account payments, you can use a service like Plaid to handle the integration. Below is a basic outline of how you might set this up: - -#### 1. **Add Plaid Gem** - -Add the Plaid gem to your `Gemfile`: - -```ruby -gem 'plaid' -``` - -Then run `bundle install` to install the gem. - -#### 2. **Configure Plaid** - -Create an initializer for Plaid configuration: - -```ruby -# config/initializers/plaid.rb -Plaid.configure do |config| - config.client_id = ENV['PLAID_CLIENT_ID'] - config.secret = ENV['PLAID_SECRET'] - config.public_key = ENV['PLAID_PUBLIC_KEY'] - config.environment = ENV['PLAID_ENVIRONMENT'] # 'sandbox' or 'development' -end -``` - -#### 3. **Process Bank Account Payment** - -Update the `process_bank_account_payment` method in the `Payment` model to handle Plaid integration: - -```ruby -def process_bank_account_payment - # Implement Plaid payment processing - # This is a placeholder for the actual Plaid integration logic - self.transaction_id = 'bank_account_transaction_id' - self.save -end -``` - -### Ticket Scanning - -To allow promoters to scan tickets using their phones, you can use a - --------------------- - -Message 6 sur 6 de l'utilisateur - 16/08/2025, 08:31:55: -Continue - -Mistral Small 3: -To allow promoters to scan tickets using their phones, you can use a QR code scanning library. Below are the steps to implement ticket scanning functionality in your Ruby on Rails application. - -### Ticket Scanning - -#### 1. **Choose a QR Code Scanning Library** - -For mobile compatibility, you can use a library like `html5-qrcode` for scanning QR codes. This library works well in modern web browsers and can be integrated into your Rails application. - -#### 2. **Create a Ticket Scanning View** - -Create a view for scanning tickets. This view will include the HTML5 QR code scanner. +### 5. Frontend Considerations +#### Mobile Ticket Scanning ```erb @@ -943,9 +267,7 @@ Create a view for scanning tickets. This view will include the HTML5 QR code sca