From ed659c423ed54f37cab6c8dea220d6dad4249520 Mon Sep 17 00:00:00 2001 From: kbe Date: Sat, 16 Aug 2025 09:00:38 +0200 Subject: [PATCH] Prepare dev instructure --- Dockerfile | 61 +- Gemfile | 2 + Gemfile.lock | 14 + app/controllers/pages_controller.rb | 9 + app/helpers/pages_helper.rb | 2 + .../controllers/logout_controller.js | 55 + app/views/pages/home.html.erb | 2 + app/views/pages/legals.html.erb | 2 + config/database.yml | 6 +- config/routes.rb | 2 +- docker-compose.yml | 64 + docs/architecture.txt | 1055 +++++++++++++++++ docs/branch-naming.txt | 142 +++ env.example | 32 + test/controllers/pages_controller_test.rb | 13 + 15 files changed, 1434 insertions(+), 27 deletions(-) create mode 100644 app/controllers/pages_controller.rb create mode 100644 app/helpers/pages_helper.rb create mode 100644 app/javascript/controllers/logout_controller.js create mode 100644 app/views/pages/home.html.erb create mode 100644 app/views/pages/legals.html.erb create mode 100644 docker-compose.yml create mode 100644 docs/architecture.txt create mode 100644 docs/branch-naming.txt create mode 100644 env.example create mode 100644 test/controllers/pages_controller_test.rb diff --git a/Dockerfile b/Dockerfile index be188ce..73f1e11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,51 +2,51 @@ # check=error=true # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: -# docker build -t aperonight . -# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name aperonight aperonight +# docker build -t myapp . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name myapp myapp # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html # Make sure RUBY_VERSION matches the Ruby version in .ruby-version -ARG RUBY_VERSION=3.4.4 -FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base +ARG RUBY_VERSION=3.4.1 +FROM docker.io/library/ruby:$RUBY_VERSION AS base # Rails app lives here WORKDIR /rails # Install base packages RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl default-mysql-client libjemalloc2 libvips && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives + apt-get install --no-install-recommends -y curl libjemalloc2 libvips && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives # Set production environment ENV RAILS_ENV="production" \ - BUNDLE_DEPLOYMENT="1" \ - BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" # Throw-away build stage to reduce size of final image FROM base AS build # Install packages needed to build gems and node modules RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential default-libmysqlclient-dev git libyaml-dev node-gyp pkg-config python-is-python3 && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives + apt-get install --no-install-recommends -y build-essential git libyaml-dev node-gyp pkg-config python-is-python3 libmariadb-dev && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install JavaScript dependencies -ARG NODE_VERSION=24.4.1 -ARG YARN_VERSION=1.22.22 +ARG NODE_VERSION=18.19.0 +ARG YARN_VERSION=latest ENV PATH=/usr/local/node/bin:$PATH RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ - /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ - npm install -g yarn@$YARN_VERSION && \ - rm -rf /tmp/node-build-master + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + rm -rf /tmp/node-build-master +RUN corepack enable && yarn set version $YARN_VERSION # Install application gems COPY Gemfile Gemfile.lock ./ RUN bundle install && \ - rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ - bundle exec bootsnap precompile --gemfile + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile # Install node modules COPY package.json yarn.lock ./ @@ -68,19 +68,34 @@ RUN rm -rf node_modules # Final stage for app image FROM base +ENV NODE_PATH=/usr/local/node +ENV PATH=/usr/local/node/bin:$PATH + +# Install packages needed to build gems and node modules (for ci/cd) +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git libyaml-dev node-gyp pkg-config python-is-python3 mariadb-client && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Install rails gem +RUN gem install rails + # Copy built artifacts: gems, application COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" COPY --from=build /rails /rails +COPY --from=build "${NODE_PATH}" "${NODE_PATH}" + +# Create a writeable node modules directory +# RUN mkdir node_modules # Run and own only the runtime files as a non-root user for security -RUN groupadd --system --gid 1000 rails && \ - useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ - chown -R rails:rails db log storage tmp -USER 1000:1000 +#RUN groupadd --system --gid 1000 rails && \ +# useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ +# chown -R rails:rails db log storage tmp node_modules +#USER 1000:1000 # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] # Start server via Thruster by default, this can be overwritten at runtime EXPOSE 80 -CMD ["./bin/thrust", "./bin/rails", "server"] +CMD ["./bin/thrust", "./bin/rails", "server", "-b", "0.0.0.0"] diff --git a/Gemfile b/Gemfile index 89f8ae5..2a35a2e 100644 --- a/Gemfile +++ b/Gemfile @@ -63,3 +63,5 @@ group :test do gem "capybara" gem "selenium-webdriver" end + +gem "devise", "~> 4.9" diff --git a/Gemfile.lock b/Gemfile.lock index 0c7fe95..46c9a9c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,6 +76,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) ast (2.4.3) base64 (0.3.0) + bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) benchmark (0.4.1) bigdecimal (3.2.2) @@ -103,6 +104,12 @@ GEM debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) dotenv (3.1.8) drb (2.2.3) ed25519 (1.4.0) @@ -183,6 +190,7 @@ GEM racc (~> 1.4) nokogiri (1.18.9-x86_64-linux-musl) racc (~> 1.4) + orm_adapter (0.5.0) ostruct (0.6.3) parallel (1.27.0) parser (3.3.9.0) @@ -249,6 +257,9 @@ GEM regexp_parser (2.11.2) reline (0.6.2) io-console (~> 0.5) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.4.1) rubocop (1.79.2) json (~> 2.3) @@ -328,6 +339,8 @@ GEM unicode-emoji (4.0.4) uri (1.0.3) useragent (0.16.11) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -358,6 +371,7 @@ DEPENDENCIES capybara cssbundling-rails debug + devise (~> 4.9) jbuilder jsbundling-rails kamal diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb new file mode 100644 index 0000000..db9cd1f --- /dev/null +++ b/app/controllers/pages_controller.rb @@ -0,0 +1,9 @@ +class PagesController < ApplicationController + # Display homepage + def home + end + + # Display legal page + def legals + end +end diff --git a/app/helpers/pages_helper.rb b/app/helpers/pages_helper.rb new file mode 100644 index 0000000..2c057fd --- /dev/null +++ b/app/helpers/pages_helper.rb @@ -0,0 +1,2 @@ +module PagesHelper +end diff --git a/app/javascript/controllers/logout_controller.js b/app/javascript/controllers/logout_controller.js new file mode 100644 index 0000000..cedb53c --- /dev/null +++ b/app/javascript/controllers/logout_controller.js @@ -0,0 +1,55 @@ +// app/javascript/controllers/logout_controller.js +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static values = { + url: String, + }; + + connect() { + // Optional: Add confirmation message + //console.log("Hello LogoutController, Stimulus!", this.element); + // this.element.dataset.confirm = "Êtes-vous sûr de vouloir vous déconnecter ?"; + } + + signOut(event) { + event.preventDefault(); + console.log("LogoutController#signOut mounted"); + + // Ensure user wants to disconnect with a confirmation request + // if (this.hasUrlValue && !confirm(this.element.dataset.confirm)) { return; } + + // Retrieve the csrf token from header + const csrfToken = document.querySelector("[name='csrf-token']").content; + + // Define url to redirect user when action is valid + const url = this.hasUrlValue ? this.urlValue : this.element.href; + + // Use fetch to send logout request + fetch(url, { + method: "DELETE", + headers: { + "X-CSRF-Token": csrfToken, + Accept: "application/json", + "Content-Type": "application/json", + }, + credentials: "same-origin", + }) + .then((response) => { + // console.log(this.element.dataset.loginUrlValue); // By default, we does not return anything. + + // By default the response does not include any url. + // Redirect to default login page (loginUrlValue) + if (response.redirected) { + window.location.href = response.url; + } else if (this.element.dataset.loginUrlValue) { + window.location.href = this.element.dataset.loginUrlValue; + return; + } + window.location.href = "/"; + }) + .catch((error) => { + console.error("Error during sign out:", error); + }); + } +} diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb new file mode 100644 index 0000000..3453cf2 --- /dev/null +++ b/app/views/pages/home.html.erb @@ -0,0 +1,2 @@ +

Pages#home

+

Find me in app/views/pages/home.html.erb

diff --git a/app/views/pages/legals.html.erb b/app/views/pages/legals.html.erb new file mode 100644 index 0000000..95e6e83 --- /dev/null +++ b/app/views/pages/legals.html.erb @@ -0,0 +1,2 @@ +

Pages#legals

+

Find me in app/views/pages/legals.html.erb

diff --git a/config/database.yml b/config/database.yml index edf90e8..3094b64 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,11 +10,11 @@ # https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default - adapter: mysql2 + adapter: mysql2 # Moved out from trilogy encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - username: root - password: + username: <%= ENV.fetch("DB_USERNAME") { "root" } %> + password: <%= ENV.fetch("DB_PASSWORD") { "root" } %> host: <%= ENV.fetch("DB_HOST") { "127.0.0.1" } %> development: diff --git a/config/routes.rb b/config/routes.rb index 48254e8..d9aad93 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,5 +10,5 @@ Rails.application.routes.draw do # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker # Defines the root path route ("/") - # root "posts#index" + root "pages#home" end diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fd812e5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,64 @@ +volumes: + mariadb_data: + gem_home: + node_modules_home: + +networks: + default: + +services: + mariadb: + image: mariadb:11.7.2-noble + env_file: .env + restart: unless-stopped + volumes: + - mariadb_data:/var/lib/mysql + #- ./mysql:/var/lib/mysql" + ports: + - "${FORWARD_DB_PORT:-3306}:3306" + environment: + MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD:-root}" + # MYSQL_ROOT_HOST: "%" + MYSQL_PORT: "${DB_PORT:-3306}" + MYSQL_DATABASE: "${DB_DATABASE:-aperonight}" + MYSQL_USER: "${DB_USERNAME:-aperonight}" + MYSQL_PASSWORD: "${DB_PASSWORD:-aperonight}" + #MYSQL_ALLOW_EMPTY_PASSWORD: 1 + networks: + - default + #command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci + + rails: + build: + context: . + dockerfile: Dockerfile + env_file: .env + restart: unless-stopped + volumes: + - ./:/rails + - gem_home:/usr/local/bundle + - node_modules_home:/rails/node_modules + #- ./log:/var/log + #- ./log/supervisor:/var/log/supervisor + ports: + #- 80:80 + - 3000:3000 + #- 5000:5000 + depends_on: + - mariadb + networks: + - default + #tty: true + #command: /opt/src/bin/dev + + mailhog: + image: corpusops/mailhog:v1.0.1 + # environment: + # - "mh_auth_file=/opt/mailhog/passwd.conf" + volumes: + - ./data/mailhog:/opt/mailhog + ports: + - 1025:1025 # smtp port 25 + - 8025:8025 # web mail access + networks: + - default diff --git a/docs/architecture.txt b/docs/architecture.txt new file mode 100644 index 0000000..8e4188b --- /dev/null +++ b/docs/architecture.txt @@ -0,0 +1,1055 @@ +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). + +==================== + +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. + +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 promotors: +* allow them to create and program parties +* aggregate parties from other website (shogun, bizouk, weezevent) + +Give advices on this application and give the arhitecture to implement. + +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** + +```ruby +# User +create_table :users do |t| + t.string :email + t.string :password_digest + t.string :role # customer or promoter + t.timestamps +end + +# Party +create_table :parties 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 + +# Ticket +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.boolean :used, default: false + t.timestamps +end + +# Payment +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. **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 + +```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 Party < ApplicationRecord + belongs_to :promoter, class_name: 'User' + has_many :tickets + has_many :bundles +end + +class Ticket < ApplicationRecord + belongs_to :party + belongs_to :user + belongs_to :bundle + 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 + +class Bundle < ApplicationRecord + belongs_to :party + has_many :tickets +end +``` + +### Updated Controllers + +#### PartiesController + +```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 + @bundles = @party.bundles + end + + def new + @party = Party.new + @party.bundles.build + 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 + @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 + @party = Party.find(params[:id]) + end + + def party_params + params.require(:party).permit(:name, :description, :start_time, :end_time, :location, bundles_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 + +```ruby +class TicketsController < ApplicationController + before_action :authenticate_user! + before_action :set_party, 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 + + private + + def set_party + @party = Party.find(params[:party_id]) + end + + def ticket_params + params.require(:ticket).permit(:bundle_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: + +```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] +``` + +#### 3. **Configure PayPal** + +Create an initializer for 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'] +}) +``` + +#### 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. + +```erb + + + + + Scan Ticket + + + +

    Scan Ticket

    +
    +
    + + + +``` + +#### 3. **Create a Route for Ticket Validation** + +Add a route to handle the validation of scanned tickets: + +```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] + 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 +``` + +#### 4. **Create a Controller Action for Ticket Validation** + +Create an action in the `TicketsController` to handle the validation of scanned tickets: + +```ruby +# app/controllers/tickets_controller.rb +class TicketsController < ApplicationController + before_action :authenticate_user!, except: [:validate] + before_action :set_ticket, only: [:show, :edit, :update, :destroy] + + 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_ticket + @ticket = Ticket.find(params[:id]) + end +end +``` + +### Summary + +With these steps, you have a comprehensive architecture for your Ruby on Rails web application that allows \ No newline at end of file diff --git a/docs/branch-naming.txt b/docs/branch-naming.txt new file mode 100644 index 0000000..c8e1367 --- /dev/null +++ b/docs/branch-naming.txt @@ -0,0 +1,142 @@ +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). + +==================== + +Message 1 sur 2 de l'utilisateur - 16/08/2025, 08:06:58: +In a git Ruby on Rails web project, how to name branches? + +Mistral Small 3: +Naming branches in a Git Ruby on Rails web project (or any Git project) is an important aspect of maintaining a clean and understandable repository. Here are some best practices for naming branches: + +1. **Use Descriptive Names**: The branch name should clearly describe the purpose or the feature being worked on. This helps other developers understand the context of the branch at a glance. + +2. **Use Hyphens or Underscores**: Use hyphens (`-`) or underscores (`_`) to separate words in the branch name. This makes the name more readable. + +3. **Prefix with the Type of Change**: Prefix the branch name with a type indicator such as `feature`, `bugfix`, `hotfix`, `release`, `chore`, `refactor`, `docs`, etc. This helps in categorizing the branches. + +4. **Keep It Short but Meaningful**: While being descriptive, try to keep the branch name short to avoid clutter. + +5. **Avoid Special Characters**: Stick to alphanumeric characters and hyphens/underscores. Avoid using special characters like `@`, `#`, `$`, etc. + +6. **Use Lowercase Letters**: Consistently use lowercase letters to avoid case sensitivity issues. + +Here are some examples of well-named branches: + +- `feature/user-authentication` +- `bugfix/login-issue` +- `hotfix/critical-bug` +- `release/v1.2.0` +- `chore/update-dependencies` +- `refactor/user-model` +- `docs/update-readme` + +### Example Workflow + +1. **Creating a New Feature Branch**: + ```sh + git checkout -b feature/user-authentication + ``` + +2. **Creating a Bugfix Branch**: + ```sh + git checkout -b bugfix/login-issue + ``` + +3. **Creating a Hotfix Branch**: + ```sh + git checkout -b hotfix/critical-bug + ``` + +4. **Creating a Release Branch**: + ```sh + git checkout -b release/v1.2.0 + ``` + +5. **Creating a Chore Branch**: + ```sh + git checkout -b chore/update-dependencies + ``` + +6. **Creating a Refactor Branch**: + ```sh + git checkout -b refactor/user-model + ``` + +7. **Creating a Docs Branch**: + ```sh + git checkout -b docs/update-readme + ``` + +By following these guidelines, you can maintain a well-organized and understandable branch structure in your Git repository, making collaboration and code management much easier. + +-------------------- + +Message 2 sur 2 de l'utilisateur - 16/08/2025, 08:08:31: +Is it a good practice to use main, develop and functionnal names for branches? + +Mistral Small 3: +Using `main`, `develop`, and feature branches is a common and widely accepted practice in Git workflows, particularly in the context of the Git Flow or GitHub Flow methodologies. Here's a breakdown of these branches and why they are useful: + +1. **`main` Branch**: + - **Purpose**: The `main` branch (or sometimes called `master`) is the stable branch that always contains the production-ready code. It represents the current state of the project that is deployed to production. + - **Usage**: This branch should only contain code that has been thoroughly tested and is ready for release. It is typically protected to prevent direct commits and requires pull requests for changes. + - **Example**: `git checkout main` + +2. **`develop` Branch**: + - **Purpose**: The `develop` branch is used for integrating features and preparing for the next release. It serves as a staging area for features that are in progress but not yet ready for production. + - **Usage**: Developers merge their feature branches into `develop` for integration testing. This branch can be less stable than `main` but should still be in a reasonably good state. + - **Example**: `git checkout develop` + +3. **Feature Branches**: + - **Purpose**: Feature branches are used to develop new features, bug fixes, or any other isolated work. Each feature branch is created from `develop` and merged back into `develop` once the work is complete. + - **Usage**: These branches allow for parallel development without interfering with the stability of `develop` or `main`. They are typically named descriptively, as discussed earlier (e.g., `feature/user-authentication`, `bugfix/login-issue`). + - **Example**: `git checkout -b feature/user-authentication` + +### Example Workflow + +1. **Create a Feature Branch**: + ```sh + git checkout develop + git pull origin develop + git checkout -b feature/user-authentication + ``` + +2. **Develop the Feature**: + - Make changes, commit them, and push the branch to the remote repository. + ```sh + git add . + git commit -m "Add user authentication feature" + git push origin feature/user-authentication + ``` + +3. **Merge the Feature Branch into `develop`**: + - Once the feature is complete and tested, create a pull request to merge it into `develop`. + ```sh + git checkout develop + git pull origin develop + git merge feature/user-authentication + git push origin develop + ``` + +4. **Prepare for Release**: + - When preparing for a new release, create a release branch from `develop`. + ```sh + git checkout -b release/v1.2.0 + ``` + +5. **Merge Release Branch into `main`**: + - After testing the release branch, merge it into `main` and tag the release. + ```sh + git checkout main + git merge release/v1.2.0 + git tag -a v1.2.0 -m "Release version 1.2.0" + git push origin main --tags + ``` + +### Benefits + +- **Stability**: The `main` branch remains stable and always contains production-ready code. +- **Integration**: The `develop` branch allows for continuous integration of features, making it easier to test and integrate new work. +- **Isolation**: Feature branches isolate work, allowing multiple developers to work on different features simultaneously without conflicts. + +Using `main`, `develop`, and feature branches is a good practice as it helps maintain a clean, organized, and manageable repository, facilitating better collaboration and code management. \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..c5fd9f0 --- /dev/null +++ b/env.example @@ -0,0 +1,32 @@ +# Application data +RAILS_ENV=production +SECRET_KEY_BASE=a3f5c6e7b8d9e0f1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7 +DEVISE_SECRET_KEY=your_devise_secret_key_here + +# Database Configuration for production and development +DB_HOST=mariadb +DB_ROOT_PASSWORD=root +DB_DATABASE=aperonight +DB_USERNAME=aperonight +DB_PASSWORD=aperonight + +# Test database +DB_TEST_ADAPTER=sqlite3 +DB_TEST_DATABASE=aperonight_test +DB_TEST_USERNAME=root +DB_TEST_USERNAME=root + +# Mailer Configuration (for Devise and tests) +MAILER_DEFAULT_URL_OPTIONS=http://localhost:3000 +# Test environment will use MailHog by default on 127.0.0.1:1025 +SMTP_ADDRESS=127.0.0.1 +SMTP_PORT=1025 +# Optional auth (usually not required for MailHog) +# SMTP_USER_NAME= +# SMTP_PASSWORD= +# SMTP_DOMAIN=localhost +SMTP_AUTHENTICATION=plain +SMTP_ENABLE_STARTTLS=false + +# Application variables +STRIPE_API_KEY=1337 \ No newline at end of file diff --git a/test/controllers/pages_controller_test.rb b/test/controllers/pages_controller_test.rb new file mode 100644 index 0000000..90d433f --- /dev/null +++ b/test/controllers/pages_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class PagesControllerTest < ActionDispatch::IntegrationTest + test "should get home" do + get pages_home_url + assert_response :success + end + + test "should get legals" do + get pages_legals_url + assert_response :success + end +end