├── .github ├── ISSUE_TEMPLATE │ └── -分類-タイトル.md └── workflows │ ├── ci-cd-prod.yml │ └── ci-cd.yml ├── .gitignore ├── DEMO.md ├── DEVELOPMENT.md ├── LICENSE.md ├── README.md ├── infra ├── dev │ ├── app │ │ └── Dockerfile │ ├── db │ │ └── Dockerfile │ └── web │ │ ├── Dockerfile │ │ └── default.conf ├── prod │ ├── app │ │ └── Dockerfile │ └── web │ │ ├── Dockerfile │ │ └── default.conf └── stg │ ├── app │ └── Dockerfile │ └── web │ ├── Dockerfile │ └── default.conf └── src ├── .dockerignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Procfile.dev ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── art-sa2-logo.png │ │ ├── illustration-slider.jpeg │ │ ├── illustration1.jpg │ │ ├── illustration2.jpg │ │ ├── illustration3.jpg │ │ ├── painting-slider.jpeg │ │ ├── painting1.jpg │ │ ├── painting2.jpg │ │ ├── painting3.jpg │ │ ├── photography-slider.jpeg │ │ ├── photography1.jpg │ │ ├── photography2.jpg │ │ ├── photography3.jpg │ │ ├── sample_image.jpg │ │ ├── sculpture-slider.jpeg │ │ ├── sculpture1.jpg │ │ ├── sculpture2.jpg │ │ ├── sculpture3.jpg │ │ └── top-sample.jpg │ └── stylesheets │ │ ├── application.css │ │ ├── application.tailwind.css │ │ └── tagify.css ├── channels │ ├── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ └── chat_channel.rb ├── controllers │ ├── admin │ │ ├── chats_controller.rb │ │ ├── products │ │ │ └── tags_controller.rb │ │ ├── products_controller.rb │ │ └── shippings_controller.rb │ ├── admin_accounts │ │ ├── confirmations_controller.rb │ │ ├── omniauth_callbacks_controller.rb │ │ ├── passwords_controller.rb │ │ ├── registrations_controller.rb │ │ ├── sessions_controller.rb │ │ └── unlocks_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── customer │ │ ├── accounts_controller.rb │ │ ├── carts_controller.rb │ │ ├── chats_controller.rb │ │ ├── checkouts_controller.rb │ │ ├── contacts_controller.rb │ │ ├── download_products_controller.rb │ │ ├── favorite_products_controller.rb │ │ ├── orders_controller.rb │ │ ├── product_reviews_controller.rb │ │ ├── products_controller.rb │ │ ├── search_products_controller.rb │ │ └── wish_products_controller.rb │ ├── customer_accounts │ │ ├── confirmations_controller.rb │ │ ├── omniauth_callbacks_controller.rb │ │ ├── passwords_controller.rb │ │ ├── registrations_controller.rb │ │ ├── sessions_controller.rb │ │ └── unlocks_controller.rb │ ├── customer_tests_controller.rb │ ├── home_controller.rb │ ├── samples_controller.rb │ └── webhooks │ │ └── stripes_controller.rb ├── helpers │ ├── application_helper.rb │ ├── samples_helper.rb │ └── wish_products_helper.rb ├── javascript │ ├── .eslintrc.js │ ├── admin_chat.ts │ ├── application.js │ ├── channels │ │ ├── chat_channel.js │ │ ├── consumer.js │ │ └── index.js │ ├── checkout_button.ts │ ├── clipboard.ts │ ├── controllers │ │ ├── application.js │ │ ├── hello_controller.js │ │ └── index.js │ ├── customer_chat.ts │ ├── product_form.ts │ ├── sample.ts │ └── theme.ts ├── jobs │ └── application_job.rb ├── mailers │ ├── application_mailer.rb │ ├── contact_mailer.rb │ ├── notification_mailer.rb │ └── test_mailer.rb ├── models │ ├── address.rb │ ├── admin.rb │ ├── admin_account.rb │ ├── application_record.rb │ ├── chat.rb │ ├── concerns │ │ ├── .keep │ │ └── jwt_authenticable.rb │ ├── contact.rb │ ├── customer.rb │ ├── customer_account.rb │ ├── download_product.rb │ ├── favorite_product.rb │ ├── order.rb │ ├── order_item.rb │ ├── prefecture.rb │ ├── product.rb │ ├── product_category.rb │ ├── product_review.rb │ ├── shipping.rb │ ├── tag.rb │ ├── wish_product.rb │ └── wish_product_token.rb └── views │ ├── .eslintrc.js │ ├── admin │ ├── chats │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── products │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── tags │ │ │ └── index.html.erb │ └── shippings │ │ ├── edit.html.erb │ │ └── index.html.erb │ ├── admin_accounts │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── email_changed.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.erb │ └── unlocks │ │ └── new.html.erb │ ├── customer │ ├── accounts │ │ ├── edit.html.erb │ │ └── show.html.erb │ ├── carts │ │ └── show.html.erb │ ├── chats │ │ └── show.html.erb │ ├── checkouts │ │ └── success.html.erb │ ├── contacts │ │ ├── mailer │ │ │ ├── send_contact_mail.html.erb │ │ │ ├── send_contact_mail.text.erb │ │ │ ├── send_user_confirm_mail.html.erb │ │ │ └── send_user_confirm_mail.text.erb │ │ └── new.html.erb │ ├── download_products │ │ └── index.html.erb │ ├── favorite_products │ │ └── index.html.erb │ ├── orders │ │ └── index.html.erb │ ├── product_reviews │ │ ├── _product_info.html.erb │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── products │ │ └── show.html.erb │ ├── search_products │ │ └── index.html.erb │ └── wish_products │ │ ├── _wish_products.html.erb │ │ ├── index.html.erb │ │ └── share.html.erb │ ├── customer_accounts │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── email_changed.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.erb │ └── unlocks │ │ └── new.html.erb │ ├── customer_tests │ └── index.html.erb │ ├── home │ └── index.html.erb │ ├── kaminari │ ├── _first_page.html.erb │ ├── _gap.html.erb │ ├── _last_page.html.erb │ ├── _next_page.html.erb │ ├── _page.html.erb │ ├── _paginator.html.erb │ └── _prev_page.html.erb │ ├── layouts │ ├── _flash.html.erb │ ├── admin │ │ ├── _footer.html.erb │ │ └── _navbar.html.erb │ ├── application.html.erb │ ├── customer │ │ ├── _footer.html.erb │ │ └── _navbar.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── notification_mailer │ ├── order_complete_email.html.erb │ ├── order_complete_email.text.erb │ ├── shipping_email.html.erb │ └── shipping_email.text.erb │ ├── samples │ └── index.html.erb │ └── shared │ ├── _error_messages.html.erb │ ├── _flash.html.erb │ ├── _flash_error_messages.html.erb │ └── _footer.html.erb ├── bin ├── backup_to_s3.sh ├── bundle ├── cleanup_volumes.sh ├── dev ├── docker-dev-entrypoint ├── docker-entrypoint ├── docker-prod-entrypoint.sh ├── importmap ├── rails ├── rake └── setup ├── compose.prod.yml ├── compose.stg.yml ├── compose.yml ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── database.yml.ci ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── importmap.rb ├── initializers │ ├── acts_as_taggable_on_ransack.rb │ ├── assets.rb │ ├── content_security_policy.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── kaminari_config.rb │ ├── permissions_policy.rb │ └── stripe.rb ├── locales │ ├── devise.en.yml │ ├── devise.ja.yml │ ├── devise.views.ja.yml │ ├── en.yml │ ├── ja.yml │ └── model.ja.yml ├── puma.rb ├── routes.rb └── storage.yml ├── db ├── migrate │ ├── 20240503003220_devise_create_admin_accounts.rb │ ├── 20240503013829_create_addresses.rb │ ├── 20240505123328_create_products.rb │ ├── 20240506064945_change_stock_in_products.rb │ ├── 20240506073146_add_status_to_products.rb │ ├── 20240506102646_add_stripe_product_id_to_products.rb │ ├── 20240511071352_add_creator_to_products.rb │ ├── 20240512011256_devise_create_customer_accounts.rb │ ├── 20240512032905_create_product_categories.rb │ ├── 20240512033205_add_product_category_ref_to_products.rb │ ├── 20240512101719_add_stripe_price_id_to_products.rb │ ├── 20240515113000_create_customers.rb │ ├── 20240515120013_create_wish_products.rb │ ├── 20240522090039_add_addressable_to_addresses.rb │ ├── 20240523234426_create_favorite_products.rb │ ├── 20240524133513_add_unique_index_to_favorite_products.rb │ ├── 20240525023349_update_customer_and_customer_account_tables.rb │ ├── 20240525231108_create_product_reviews.rb │ ├── 20240526084641_add_stripe_customer_id_to_customers.rb │ ├── 20240526225446_create_active_storage_tables.active_storage.rb │ ├── 20240527081952_create_orders.rb │ ├── 20240527082654_create_order_items.rb │ ├── 20240529235025_create_download_products.rb │ ├── 20240529235325_add_type_to_products.rb │ ├── 20240531110338_add_download_url_to_download_products.rb │ ├── 20240601015501_create_shippings.rb │ ├── 20240602005424_add_unique_index_to_shippings_tracking_number.rb │ ├── 20240603074543_add_shipping_name_to_customer_accounts.rb │ ├── 20240603075400_add_phone_number_to_customer_accounts.rb │ ├── 20240603081220_modify_addresses.rb │ ├── 20240604084709_change_zip_code_type_in_addresses_table.rb │ ├── 20240606035355_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb │ ├── 20240606035356_add_missing_unique_indices.acts_as_taggable_on_engine.rb │ ├── 20240606035357_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb │ ├── 20240606035358_add_missing_taggable_index.acts_as_taggable_on_engine.rb │ ├── 20240606035359_change_collation_for_tag_names.acts_as_taggable_on_engine.rb │ ├── 20240606035360_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb │ ├── 20240606035361_add_tenant_to_taggings.acts_as_taggable_on_engine.rb │ ├── 20240610002709_create_contacts.rb │ ├── 20240610062552_add_customer_id_to_contacts.rb │ ├── 20240612074748_create_chats.rb │ ├── 20240612084818_create_admins.rb │ ├── 20240612094107_add_admin_ref_to_admin_accounts.rb │ ├── 20240621062234_change_price_and_description_in_products.rb │ ├── 20240623082100_create_wish_product_tokens.rb │ └── 20240624054816_add_released_at_to_products.rb ├── schema.rb └── seeds.rb ├── lib ├── active_storage │ └── service │ │ └── tenant_s3_service.rb ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── channels │ └── chat_channel_spec.rb ├── factories │ ├── addresses.rb │ ├── admin_accounts.rb │ ├── admins.rb │ ├── chats.rb │ ├── contacts.rb │ ├── customer_accounts.rb │ ├── customers.rb │ ├── favorite_products.rb │ ├── order_items.rb │ ├── orders.rb │ ├── product_categories.rb │ ├── product_reviews.rb │ ├── products.rb │ ├── shippings.rb │ ├── wish_product_tokens.rb │ └── wish_products.rb ├── fixtures │ └── files │ │ ├── invalid_image.txt │ │ ├── large_image.jpg │ │ ├── valid_digital_file.zip │ │ └── valid_image.jpg ├── mailers │ ├── previews │ │ └── test_mailer_preview.rb │ └── test_mailer_spec.rb ├── models │ ├── address_spec.rb │ ├── admin_account_spec.rb │ ├── chat_spec.rb │ ├── contact_spec.rb │ ├── customer_account_spec.rb │ ├── customer_spec.rb │ ├── download_product_spec.rb │ ├── favorite_product_spec.rb │ ├── order_item_spec.rb │ ├── order_spec.rb │ ├── product_category_spec.rb │ ├── product_review_spec.rb │ ├── product_spec.rb │ ├── shipping_spec.rb │ ├── wish_product_spec.rb │ └── wish_product_token_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── storage └── .keep ├── tailwind.config.js ├── test ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb ├── controllers │ ├── .keep │ └── samples_controller_test.rb ├── fixtures │ ├── articles.yml │ └── files │ │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ ├── .keep │ └── previews │ │ └── notification_mailer_preview.rb ├── models │ └── .keep ├── system │ └── .keep └── test_helper.rb ├── tmp └── .keep ├── vendor ├── .keep └── javascript │ └── .keep └── yarn.lock /.github/ISSUE_TEMPLATE/-分類-タイトル.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "【分類】タイトル" 3 | about: issueを作成する際のテンプレートです。 4 | title: "【分類】タイトル" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 概要 11 | 12 | 対応issueの概要を記載してください。 13 | 例. 14 | 以下機能要件に従い実装する 15 | - 機能要件xx 16 | 17 | ## 目的 18 | 19 | 対応issueの目的を記載してください。 20 | 例.機能要件を満した認証システムを作成するため 21 | 22 | ## タスク 23 | 24 | 細かいタスクがある場合は、書き出してください。 25 | - [ ] タスク1 26 | - [ ] タスク2 27 | 28 | ## その他 29 | 30 | 参考記事や検討、打ち上げ、共有事項などがあれば記載してください。 31 | 32 | ### 参考 33 | - [GitHubのissue、こう書かれているとめちゃくちゃ嬉しい。良いissueの書き方とは](https://note.com/koushikagawa/n/n500e2f4d4019) 34 | - [Githubにissueのテンプレートを作成する方法](https://note.com/koushikagawa/n/n0dcc592d3045) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 seiichikick0404, AkinoJoey, Aki158 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /infra/dev/app/Dockerfile: -------------------------------------------------------------------------------- 1 | #Docker Hubからruby:3.3.0のイメージをプルする 2 | ARG RUBY_VERSION=3.3.0 3 | FROM registry.docker.com/library/ruby:$RUBY_VERSION 4 | 5 | #debian系のためapt-getを使用してnode.jsとyarnをインストール 6 | RUN apt-get update -qq && \ 7 | curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ 8 | apt-get install -y nodejs vim default-mysql-client libvips && \ 9 | npm install --global yarn 10 | 11 | #docker内の作業ディレクトリを作成&設定 12 | WORKDIR /E-Commerce-Webapp-with-Stripe-Sync 13 | 14 | COPY Gemfile Gemfile.lock ./ 15 | RUN bundle install 16 | 17 | # srcをE-Commerce-Webapp-with-Stripe-Syncにコピー 18 | COPY ./ /E-Commerce-Webapp-with-Stripe-Sync 19 | 20 | # データベースのセットアップを実行 21 | ENTRYPOINT ["/E-Commerce-Webapp-with-Stripe-Sync/bin/docker-dev-entrypoint"] 22 | 23 | 24 | -------------------------------------------------------------------------------- /infra/dev/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:8.0.36 -------------------------------------------------------------------------------- /infra/dev/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.25.4 -------------------------------------------------------------------------------- /infra/dev/web/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | proxy_pass http://app:3000; 6 | proxy_set_header Host $host; 7 | proxy_set_header X-Real-IP $remote_addr; 8 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 9 | proxy_set_header X-Forwarded-Proto $scheme; 10 | } 11 | 12 | location /assets/ { 13 | root /E-Commerce-Webapp-with-Stripe-Sync/public; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /infra/prod/app/Dockerfile: -------------------------------------------------------------------------------- 1 | # ベースイメージ 2 | FROM ruby:3.3.0 3 | ARG RUBYGEMS_VERSION=3.3.20 4 | 5 | #debian系のためapt-getを使用してnode.jsとyarnをインストール 6 | RUN apt-get update -qq && \ 7 | curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ 8 | apt-get install -y nodejs vim default-mysql-client libvips && \ 9 | npm install --global yarn 10 | 11 | #docker内の作業ディレクトリを作成&設定 12 | WORKDIR /E-Commerce-Webapp-with-Stripe-Sync 13 | 14 | # srcをE-Commerce-Webapp-with-Stripe-Syncにコピー 15 | COPY ./src /E-Commerce-Webapp-with-Stripe-Sync 16 | 17 | # esbuildをインストール 18 | RUN yarn add esbuild 19 | 20 | # docker-prod-entrypoint.shをコピー 21 | COPY ./src/bin/docker-prod-entrypoint.sh /usr/local/bin/ 22 | 23 | # docker-prod-entrypoint.shを実行可能にする 24 | RUN chmod +x /usr/local/bin/docker-prod-entrypoint.sh 25 | 26 | # エントリポイントを設定 27 | ENTRYPOINT ["/usr/local/bin/docker-prod-entrypoint.sh"] -------------------------------------------------------------------------------- /infra/prod/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # ベースイメージ 2 | FROM nginx:1.25.4 3 | 4 | # Nginxをフォアグラウンド起動(デフォルトでは、デーモンで起動されるが、コンテナが止まるため) 5 | CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf 6 | -------------------------------------------------------------------------------- /infra/prod/web/default.conf: -------------------------------------------------------------------------------- 1 | # プロキシ先の指定 2 | # Nginxが受け取ったリクエストをバックエンドのpumaに送信 3 | upstream myapp { 4 | # ソケット通信したいのでpuma.sockを指定 5 | server unix:///E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets/puma.sock; 6 | } 7 | 8 | server { 9 | listen 80; 10 | server_name art-sa2.com; 11 | 12 | # ドキュメントルートの指定 13 | root /E-Commerce-Webapp-with-Stripe-Sync/public; 14 | 15 | client_max_body_size 100m; 16 | error_page 404 /404.html; 17 | error_page 505 502 503 504 /500.html; 18 | try_files $uri/index.html $uri @myapp; 19 | keepalive_timeout 5; 20 | 21 | # リバースプロキシ関連の設定 22 | location @myapp { 23 | proxy_set_header X-Real-IP $remote_addr; 24 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 25 | proxy_set_header Host $http_host; 26 | proxy_pass http://myapp; 27 | } 28 | 29 | # WebSocketの設定 30 | location /cable { 31 | proxy_pass http://myapp; 32 | proxy_http_version 1.1; 33 | proxy_set_header Upgrade $http_upgrade; 34 | proxy_set_header Connection "Upgrade"; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /infra/stg/app/Dockerfile: -------------------------------------------------------------------------------- 1 | # ベースイメージ 2 | FROM ruby:3.3.0 3 | ARG RUBYGEMS_VERSION=3.3.20 4 | 5 | #debian系のためapt-getを使用してnode.jsとyarnをインストール 6 | RUN apt-get update -qq && \ 7 | curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ 8 | apt-get install -y nodejs vim default-mysql-client libvips && \ 9 | npm install --global yarn 10 | 11 | #docker内の作業ディレクトリを作成&設定 12 | WORKDIR /E-Commerce-Webapp-with-Stripe-Sync 13 | 14 | # srcをE-Commerce-Webapp-with-Stripe-Syncにコピー 15 | COPY ./src /E-Commerce-Webapp-with-Stripe-Sync 16 | 17 | # esbuildをインストール 18 | RUN yarn add esbuild 19 | 20 | # docker-prod-entrypoint.shをコピー 21 | COPY ./src/bin/docker-prod-entrypoint.sh /usr/local/bin/ 22 | 23 | # docker-prod-entrypoint.shを実行可能にする 24 | RUN chmod +x /usr/local/bin/docker-prod-entrypoint.sh 25 | 26 | # エントリポイントを設定 27 | ENTRYPOINT ["/usr/local/bin/docker-prod-entrypoint.sh"] -------------------------------------------------------------------------------- /infra/stg/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # ベースイメージ 2 | FROM nginx:1.25.4 3 | 4 | # Nginxをフォアグラウンド起動(デフォルトでは、デーモンで起動されるが、コンテナが止まるため) 5 | CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf 6 | -------------------------------------------------------------------------------- /infra/stg/web/default.conf: -------------------------------------------------------------------------------- 1 | # プロキシ先の指定 2 | # Nginxが受け取ったリクエストをバックエンドのpumaに送信 3 | upstream myapp { 4 | # ソケット通信したいのでpuma.sockを指定 5 | server unix:///E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets/puma.sock; 6 | } 7 | 8 | server { 9 | listen 80; 10 | server_name art-sa2-stg.com; 11 | 12 | # ドキュメントルートの指定 13 | root /E-Commerce-Webapp-with-Stripe-Sync/public; 14 | 15 | client_max_body_size 100m; 16 | error_page 404 /404.html; 17 | error_page 505 502 503 504 /500.html; 18 | try_files $uri/index.html $uri @myapp; 19 | keepalive_timeout 5; 20 | 21 | # リバースプロキシ関連の設定 22 | location @myapp { 23 | proxy_set_header X-Real-IP $remote_addr; 24 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 25 | proxy_set_header Host $http_host; 26 | proxy_pass http://myapp; 27 | } 28 | 29 | # WebSocketの設定 30 | location /cable { 31 | proxy_pass http://myapp; 32 | proxy_http_version 1.1; 33 | proxy_set_header Upgrade $http_upgrade; 34 | proxy_set_header Connection "Upgrade"; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all environment files (except templates). 10 | /.env* 11 | !/.env*.erb 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore socketsfiles, but keep the directory. 24 | /tmp/sockets/* 25 | !/tmp/sockets 26 | 27 | # Ignore pidfiles, but keep the directory. 28 | /tmp/pids/* 29 | !/tmp/pids/.keep 30 | 31 | # Ignore storage (uploaded files in development and any SQLite databases). 32 | /storage/* 33 | !/storage/.keep 34 | /tmp/storage/* 35 | !/tmp/storage/.keep 36 | 37 | # Ignore assets. 38 | /node_modules/ 39 | /app/assets/builds/* 40 | !/app/assets/builds/.keep 41 | /public/assets 42 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["plugin:tailwindcss/recommended"], 4 | parserOptions: { 5 | sourceType: "module", 6 | ecmaVersion: "latest", 7 | }, 8 | 9 | // flowbiteで独自のクラスを定義していることが多いのでこのルールはオフにする 10 | rules: { 11 | "tailwindcss/no-custom-classname": "off", 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /src/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation -------------------------------------------------------------------------------- /src/.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # rubocop_todo.yml -------------------------------------------------------------------------------- /src/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3.0 2 | -------------------------------------------------------------------------------- /src/Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3000 -b '0.0.0.0' 2 | js: yarn build --watch 3 | css: yarn build:css --watch -------------------------------------------------------------------------------- /src/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /src/app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/builds/.keep -------------------------------------------------------------------------------- /src/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../../vendor/javascript .js 4 | //= link_tree ../builds 5 | -------------------------------------------------------------------------------- /src/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/.keep -------------------------------------------------------------------------------- /src/app/assets/images/art-sa2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/art-sa2-logo.png -------------------------------------------------------------------------------- /src/app/assets/images/illustration-slider.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/illustration-slider.jpeg -------------------------------------------------------------------------------- /src/app/assets/images/illustration1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/illustration1.jpg -------------------------------------------------------------------------------- /src/app/assets/images/illustration2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/illustration2.jpg -------------------------------------------------------------------------------- /src/app/assets/images/illustration3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/illustration3.jpg -------------------------------------------------------------------------------- /src/app/assets/images/painting-slider.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/painting-slider.jpeg -------------------------------------------------------------------------------- /src/app/assets/images/painting1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/painting1.jpg -------------------------------------------------------------------------------- /src/app/assets/images/painting2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/painting2.jpg -------------------------------------------------------------------------------- /src/app/assets/images/painting3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/painting3.jpg -------------------------------------------------------------------------------- /src/app/assets/images/photography-slider.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/photography-slider.jpeg -------------------------------------------------------------------------------- /src/app/assets/images/photography1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/photography1.jpg -------------------------------------------------------------------------------- /src/app/assets/images/photography2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/photography2.jpg -------------------------------------------------------------------------------- /src/app/assets/images/photography3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/photography3.jpg -------------------------------------------------------------------------------- /src/app/assets/images/sample_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/sample_image.jpg -------------------------------------------------------------------------------- /src/app/assets/images/sculpture-slider.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/sculpture-slider.jpeg -------------------------------------------------------------------------------- /src/app/assets/images/sculpture1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/sculpture1.jpg -------------------------------------------------------------------------------- /src/app/assets/images/sculpture2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/sculpture2.jpg -------------------------------------------------------------------------------- /src/app/assets/images/sculpture3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/sculpture3.jpg -------------------------------------------------------------------------------- /src/app/assets/images/top-sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/assets/images/top-sample.jpg -------------------------------------------------------------------------------- /src/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_tree ../builds/ 15 | *= require_self 16 | */ 17 | -------------------------------------------------------------------------------- /src/app/assets/stylesheets/application.tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/app/channels/chat_channel.rb: -------------------------------------------------------------------------------- 1 | class ChatChannel < ApplicationCable::Channel 2 | def subscribed 3 | stream_from 'chat_channel' 4 | end 5 | 6 | def unsubscribed 7 | # Any cleanup needed when channel is unsubscribed 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/app/controllers/admin/products/tags_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::Products::TagsController < ApplicationController 2 | before_action :authenticate_admin_account! 3 | 4 | def index 5 | @admin = true 6 | @tags = Product.tags_on(:tags) 7 | end 8 | 9 | def destroy 10 | ActsAsTaggableOn::Tag.destroy(params[:id]) 11 | 12 | redirect_to admin_products_tags_path, status: :see_other, notice: 'タグを削除しました' 13 | end 14 | 15 | def whitelist 16 | tags = Product.tag_counts_on(:tags).where('name LIKE ?', "#{Product.sanitize_sql_like(params[:name])}%") 17 | whitelist = tags.map(&:name) 18 | 19 | render json: whitelist 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/app/controllers/admin/shippings_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ShippingsController < ApplicationController 2 | before_action :authenticate_admin_account! 3 | 4 | def index 5 | @admin = true 6 | @search = Shipping.ransack(params[:q]) 7 | @shippings = @search.result(distinct: true).order(updated_at: :desc).page(params[:page]) 8 | end 9 | 10 | def edit 11 | @admin = true 12 | @shipping = Shipping.find(params[:id]) 13 | end 14 | 15 | def update 16 | @admin = true 17 | @shipping = Shipping.find(params[:id]) 18 | 19 | if @shipping.update(shipping_params) 20 | @shipping.update(status: :shipped, shipping_at: Time.current) 21 | 22 | expires_now 23 | redirect_to edit_admin_shipping_path(@shipping), notice: '商品発送の通知メールを送信しました' 24 | 25 | email = @shipping.order.guest_email? ? @shipping.order.guest_email : @shipping.order.customer.customer_account.email 26 | 27 | # 発送通知メールを顧客に送信 28 | NotificationMailer.with(email:, shipping: @shipping).shipping_email.deliver_later 29 | else 30 | flash.now[:alert] = '正しい値を入力してください' 31 | render :edit, status: :unprocessable_entity 32 | 33 | end 34 | end 35 | 36 | private 37 | 38 | def shipping_params 39 | params.require(:shipping).permit(:carrier, :tracking_number) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::ConfirmationsController < Devise::ConfirmationsController 4 | # GET /resource/confirmation/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/confirmation 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/confirmation?confirmation_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | protected 20 | 21 | # The path used after resending confirmation instructions. 22 | def after_resending_confirmation_instructions_path_for(_resource_name) 23 | new_admin_account_confirmation_path 24 | end 25 | 26 | # The path used after confirmation. 27 | def after_confirmation_path_for(_resource_name, _resource) 28 | sign_in(resource) 29 | # TODO: 遷移先をダッシュボードに変更する 30 | sample_path 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController 4 | # You should configure your model like this: 5 | # devise :omniauthable, omniauth_providers: [:twitter] 6 | 7 | # You should also create an action method in this controller like this: 8 | # def twitter 9 | # end 10 | 11 | # More info at: 12 | # https://github.com/heartcombo/devise#omniauth 13 | 14 | # GET|POST /resource/auth/twitter 15 | # def passthru 16 | # super 17 | # end 18 | 19 | # GET|POST /users/auth/twitter/callback 20 | # def failure 21 | # super 22 | # end 23 | 24 | # protected 25 | 26 | # The path used when OmniAuth fails 27 | # def after_omniauth_failure_path_for(scope) 28 | # super(scope) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::PasswordsController < Devise::PasswordsController 4 | # GET /resource/password/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/password 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/password/edit?reset_password_token=abcdef 15 | # def edit 16 | # super 17 | # end 18 | 19 | # PUT /resource/password 20 | # def update 21 | # super 22 | # end 23 | 24 | protected 25 | 26 | def after_resetting_password_path_for(_resource) 27 | new_admin_account_session_path 28 | end 29 | 30 | # The path used after sending reset password instructions 31 | def after_sending_reset_password_instructions_path_for(_resource_name) 32 | new_admin_account_password_path 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::RegistrationsController < Devise::RegistrationsController 4 | before_action :configure_sign_up_params, only: [:create] 5 | 6 | # GET /resource/sign_up 7 | # def new 8 | # super 9 | # end 10 | 11 | # POST /resource 12 | # def create 13 | # super 14 | # end 15 | 16 | # GET /resource/edit 17 | # def edit 18 | # super 19 | # end 20 | 21 | # PUT /resource 22 | # def update 23 | # super 24 | # end 25 | 26 | # DELETE /resource 27 | # def destroy 28 | # super 29 | # end 30 | 31 | # GET /resource/cancel 32 | # Forces the session data which is usually expired after sign 33 | # in to be expired now. This is useful if the user wants to 34 | # cancel oauth signing in/up in the middle of the process, 35 | # removing all OAuth session data. 36 | # def cancel 37 | # super 38 | # end 39 | 40 | protected 41 | 42 | # If you have extra params to permit, append them to the sanitizer. 43 | def configure_sign_up_params 44 | devise_parameter_sanitizer.permit(:sign_up, keys: [:user_name]) 45 | end 46 | 47 | # If you have extra params to permit, append them to the sanitizer. 48 | # def configure_account_update_params 49 | # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) 50 | # end 51 | 52 | # The path used after sign up. 53 | # def after_sign_up_path_for(resource) 54 | # super(resource) 55 | # end 56 | 57 | # The path used after sign up for inactive accounts. 58 | def after_inactive_sign_up_path_for(_resource) 59 | new_admin_account_registration_path 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::SessionsController < Devise::SessionsController 4 | # before_action :configure_sign_in_params, only: [:create] 5 | 6 | # GET /resource/sign_in 7 | # def new 8 | # super 9 | # end 10 | 11 | # POST /resource/sign_in 12 | # def create 13 | # super 14 | # end 15 | 16 | # DELETE /resource/sign_out 17 | def destroy 18 | super do 19 | flash.clear 20 | end 21 | end 22 | 23 | # ログイン後のリダイレクト先 24 | def after_sign_in_path_for(_resource) 25 | admin_products_path 26 | end 27 | 28 | # ログアウト後のリダイレクト先 29 | def after_sign_out_path_for(_resource) 30 | admin_account_session_path 31 | end 32 | 33 | # protected 34 | 35 | # If you have extra params to permit, append them to the sanitizer. 36 | # def configure_sign_in_params 37 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) 38 | # end 39 | end 40 | -------------------------------------------------------------------------------- /src/app/controllers/admin_accounts/unlocks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminAccounts::UnlocksController < Devise::UnlocksController 4 | # GET /resource/unlock/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/unlock 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/unlock?unlock_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | # protected 20 | 21 | # The path used after sending unlock password instructions 22 | # def after_sending_unlock_instructions_path_for(resource) 23 | # super(resource) 24 | # end 25 | 26 | # The path used after unlocking the resource 27 | # def after_unlock_path_for(resource) 28 | # super(resource) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /src/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :current_cart 3 | before_action :cart_items_count 4 | before_action :current_customer, if: :customer_account_signed_in? 5 | before_action :current_admin, if: :admin_account_signed_in? 6 | before_action :set_search 7 | 8 | private 9 | 10 | # 現在のカートを取得するメソッド 11 | def current_cart 12 | # @current_cartが未定義またはnilの場合、session[:cart]を使用し、それも未定義またはnilの場合は空のハッシュを設定する 13 | @current_cart ||= session[:cart] ||= {} 14 | end 15 | 16 | def cart_items_count 17 | @cart_items_count = session[:cart_items_count] ||= @current_cart.values.sum 18 | end 19 | 20 | def reset_cart 21 | session[:cart] = {} 22 | session[:cart_items_count] = 0 23 | @cart_items_count = session[:cart_items_count] 24 | @current_cart = session[:cart] 25 | end 26 | 27 | def current_customer 28 | @current_customer ||= Customer.find_by(id: current_customer_account&.customer_id) 29 | end 30 | 31 | def set_search 32 | @search = Product.ransack(params[:q]) 33 | end 34 | 35 | def current_admin 36 | @current_admin ||= Admin.find_by(id: current_admin_account&.admin_id) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /src/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /src/app/controllers/customer/accounts_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::AccountsController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | 4 | def show 5 | @customer = true 6 | end 7 | 8 | def edit 9 | @customer = true 10 | @current_customer = current_customer 11 | end 12 | 13 | def update 14 | @customer = true 15 | @current_customer = current_customer 16 | if @current_customer.update(customer_params) 17 | Stripe::Customer.update(@current_customer.stripe_customer_id, 18 | shipping: { name: @current_customer.customer_account.shipping_name, 19 | address: { country: 'JP', postal_code: @current_customer.address.zip_code, 20 | state: @current_customer.address.prefecture&.name, 21 | line1: @current_customer.address.street_address, line2: @current_customer.address.street_address_2 } }) 22 | redirect_to account_path, notice: 'アカウント情報が更新されました' 23 | else 24 | render :edit 25 | end 26 | end 27 | 28 | private 29 | 30 | def customer_params 31 | params.require(:customer).permit(customer_account_attributes: %i[id user_name shipping_name email phone_number], 32 | address_attributes: %i[id zip_code prefecture_id street_address street_address_2]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/app/controllers/customer/chats_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::ChatsController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | def show 4 | @customer = true 5 | @websocket_url = Rails.env.production? ? ENV.fetch('WEBSOCKET_URL', nil) : 'ws://localhost:8080/chat' 6 | @chat = current_customer.chat 7 | 8 | if @chat.blank? 9 | chat = @current_customer.create_chat(status: :waiting_for_admin) 10 | ActionCable.server.broadcast 'chat_channel', { action: 'create', chat:, customer_account: @current_customer.customer_account } 11 | end 12 | 13 | if @current_customer && customer_has_valid_token? 14 | @current_customer.chat.update(status: :waiting_for_admin) 15 | ActionCable.server.broadcast 'chat_channel', { action: 'create', chat: @chat, customer_account: @current_customer.customer_account } 16 | else 17 | # 有効なトークンを持っていない場合は再作成 18 | token = @current_customer.generate_jwt 19 | cookies.signed[:customer_jwt] = { 20 | value: token, 21 | httponly: true, 22 | secure: Rails.env.production?, # 本番環境では true 23 | same_site: :strict 24 | } 25 | end 26 | end 27 | 28 | def token 29 | render json: { token: cookies.signed[:customer_jwt] } 30 | end 31 | 32 | def update_status 33 | @chat = current_customer_account.customer.chat 34 | if @chat.update(status: params[:status]) 35 | ActionCable.server.broadcast 'chat_channel', { 36 | action: 'update_status', 37 | chat: @chat.as_json 38 | } 39 | render json: { status: 'ok' } 40 | else 41 | render json: { status: 'error', message: @chat.errors.full_messages }, status: :unprocessable_entity 42 | end 43 | end 44 | 45 | private 46 | 47 | def customer_has_valid_token? 48 | token = cookies.signed[:customer_jwt] 49 | decoded_token = Customer.decode_jwt(token) 50 | decoded_token.present? && decoded_token['customer_id'].to_i == @current_customer.id 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/app/controllers/customer/contacts_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::ContactsController < ApplicationController 2 | before_action :set_contact_info, only: %i[new] 3 | 4 | def new 5 | @customer = true 6 | @contact = Contact.new 7 | @errors = flash[:errors] 8 | end 9 | 10 | def create 11 | @contact = Contact.new(contact_params) 12 | if @contact.save 13 | ContactMailer.send_contact_mail(@contact).deliver_now # 管理者向けメール送信 14 | ContactMailer.send_user_confirm_mail(@contact).deliver_now # ユーザー向けの確認メール送信 15 | redirect_to root_path, notice: 'メールが送信されました' 16 | else 17 | flash[:errors] = @contact.errors.full_messages 18 | redirect_to new_contact_path(@contact) 19 | end 20 | end 21 | 22 | private 23 | 24 | def set_contact_info 25 | return unless customer_account_signed_in? 26 | 27 | @contact_name = current_customer_account.shipping_name 28 | @contact_email = current_customer_account.email 29 | end 30 | 31 | def contact_params 32 | params.require(:contact).permit(:email, :name, :message) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/app/controllers/customer/download_products_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::DownloadProductsController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | 4 | def index 5 | @customer = true 6 | @download_products = current_customer.download_products.order(updated_at: :desc).page(params[:page]) 7 | @average_ratings = {} 8 | 9 | @download_products.each do |download_product| 10 | @average_ratings[download_product.product.id] = download_product.product.product_reviews.average(:rating).to_i 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/app/controllers/customer/favorite_products_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::FavoriteProductsController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | 4 | def index 5 | @customer = true 6 | @favorite_products = current_customer_account.customer.favorite_products.joins(:product).page(params[:page]) 7 | @average_ratings = {} 8 | 9 | @favorite_products.each do |favorite_product| 10 | @average_ratings[favorite_product.product.id] = favorite_product.product.product_reviews.average(:rating).to_i 11 | end 12 | end 13 | 14 | def create 15 | product = Product.find(params[:product_id]) 16 | favorite_product = current_customer_account.customer.favorite_products.new(product:) 17 | 18 | if favorite_product.save 19 | redirect_to product_path(product.id) 20 | else 21 | redirect_to products_path, alert: 'Failed to add product to favorites.' 22 | end 23 | end 24 | 25 | def destroy 26 | favorite_product = current_customer_account.customer.favorite_products.find(params[:id]) 27 | favorite_product.destroy 28 | 29 | redirect_to favorite_products_path, notice: 'Product removed from favorites.' 30 | rescue ActiveRecord::RecordNotFound 31 | redirect_to favorite_products_path, alert: 'Favorite product not found.' 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/app/controllers/customer/orders_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::OrdersController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | def index 4 | @customer = true 5 | @orders = current_customer.orders.order(order_date: :desc).page(params[:page]) 6 | @product_reviews = @orders.each_with_object({}) do |order, hash| 7 | order.order_items.each do |order_item| 8 | product = order_item.product 9 | review = ProductReview.find_by(product:, customer: @current_customer) 10 | hash[product.id] = review if review 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/app/controllers/customer/products_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::ProductsController < ApplicationController 2 | before_action :set_product, only: %i[show] 3 | 4 | def show 5 | @customer = true 6 | @product_reviews = @product.product_reviews.order(created_at: :desc).includes(customer: :customer_account).limit(5) 7 | @average_rating = @product.product_reviews.average(:rating).to_i 8 | @remaining_stock = @product.remaining_stock 9 | end 10 | 11 | def set_product 12 | @product = Product.find_by(id: params[:id], status: 'published') 13 | return unless @product.nil? 14 | 15 | flash[:alert] = '商品が見つかりません' 16 | redirect_to root_path 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/app/controllers/customer/search_products_controller.rb: -------------------------------------------------------------------------------- 1 | class Customer::SearchProductsController < ApplicationController 2 | def index 3 | @customer = true 4 | apply_filters 5 | 6 | @keyword = "検索結果 : #{@search.name_or_description_or_creator_or_product_category_name_or_tags_name_cont}" if 7 | @search.name_or_description_or_creator_or_product_category_name_or_tags_name_cont.present? 8 | @products = @search.result(distinct: true).where(status: 'published').page(params[:page]) 9 | @average_ratings = get_average_ratings(@products) 10 | 11 | @tag_names = Tag.joins(:taggings).where(taggings: { taggable_id: Product.where(status: 'published').map(&:id), 12 | taggable_type: 'Product' }).distinct.map(&:name) 13 | @creators = Product.where(status: 'published').map(&:creator) 14 | @categories = ProductCategory.all.map(&:name) 15 | end 16 | 17 | private 18 | 19 | def get_average_ratings(products) 20 | return {} if products.blank? 21 | 22 | average_ratings = {} 23 | 24 | products.each do |product| 25 | average_ratings[product.id] = product.product_reviews.average(:rating).to_i 26 | end 27 | 28 | average_ratings 29 | end 30 | 31 | def apply_filters 32 | set_default_sorting 33 | sanitize_search_keywords 34 | convert_date_filters 35 | end 36 | 37 | def set_default_sorting 38 | # デフォルトの並び替えを高評価順で設定する 39 | @search.sorts = 'average_rating desc' if @search.sorts.empty? 40 | end 41 | 42 | def sanitize_search_keywords 43 | # 不要な全角空白があれば、削除する 44 | return if @search.name_or_description_or_creator_or_product_category_name_or_tags_name_cont.nil? 45 | 46 | @search.name_or_description_or_creator_or_product_category_name_or_tags_name_cont = 47 | @search.name_or_description_or_creator_or_product_category_name_or_tags_name_cont.gsub(/ /, ' ').strip 48 | end 49 | 50 | def convert_date_filters 51 | # 日付のみ扱うように変換 52 | @search.released_at_gteq = @search.released_at_gteq.to_date if @search.released_at_gteq.present? 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/app/controllers/customer_accounts/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomerAccounts::ConfirmationsController < Devise::ConfirmationsController 4 | # GET /resource/confirmation/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/confirmation 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/confirmation?confirmation_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | protected 20 | 21 | # The path used after resending confirmation instructions. 22 | def after_resending_confirmation_instructions_path_for(_resource_name) 23 | new_customer_account_confirmation_path 24 | end 25 | 26 | # The path used after confirmation. 27 | def after_confirmation_path_for(_resource_name, resource) 28 | sign_in(resource) 29 | root_path 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/app/controllers/customer_accounts/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomerAccounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController 4 | # You should configure your model like this: 5 | # devise :omniauthable, omniauth_providers: [:twitter] 6 | 7 | # You should also create an action method in this controller like this: 8 | # def twitter 9 | # end 10 | 11 | # More info at: 12 | # https://github.com/heartcombo/devise#omniauth 13 | 14 | # GET|POST /resource/auth/twitter 15 | # def passthru 16 | # super 17 | # end 18 | 19 | # GET|POST /users/auth/twitter/callback 20 | # def failure 21 | # super 22 | # end 23 | 24 | # protected 25 | 26 | # The path used when OmniAuth fails 27 | # def after_omniauth_failure_path_for(scope) 28 | # super(scope) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /src/app/controllers/customer_accounts/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomerAccounts::PasswordsController < Devise::PasswordsController 4 | # GET /resource/password/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/password 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/password/edit?reset_password_token=abcdef 15 | # def edit 16 | # super 17 | # end 18 | 19 | # PUT /resource/password 20 | # def update 21 | # super 22 | # end 23 | 24 | protected 25 | 26 | def after_resetting_password_path_for(_resource) 27 | new_customer_account_session_path 28 | end 29 | 30 | # The path used after sending reset password instructions 31 | def after_sending_reset_password_instructions_path_for(_resource_name) 32 | new_customer_account_password_path 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/app/controllers/customer_accounts/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomerAccounts::SessionsController < Devise::SessionsController 4 | # before_action :configure_sign_in_params, only: [:create] 5 | 6 | # GET /resource/sign_in 7 | # def new 8 | # super 9 | # end 10 | 11 | # POST /resource/sign_in 12 | # def create 13 | # super 14 | # end 15 | 16 | # DELETE /resource/sign_out 17 | def destroy 18 | super do 19 | reset_session 20 | flash.clear 21 | end 22 | end 23 | 24 | # ログイン後のリダイレクト先 25 | def after_sign_in_path_for(_resource) 26 | root_path 27 | end 28 | 29 | # ログアウト後のリダイレクト先 30 | def after_sign_out_path_for(_resource) 31 | customer_account_session_path 32 | end 33 | 34 | # protected 35 | 36 | # If you have extra params to permit, append them to the sanitizer. 37 | # def configure_sign_in_params 38 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) 39 | # end 40 | end 41 | -------------------------------------------------------------------------------- /src/app/controllers/customer_accounts/unlocks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomerAccounts::UnlocksController < Devise::UnlocksController 4 | # GET /resource/unlock/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/unlock 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/unlock?unlock_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | # protected 20 | 21 | # The path used after sending unlock password instructions 22 | # def after_sending_unlock_instructions_path_for(resource) 23 | # super(resource) 24 | # end 25 | 26 | # The path used after unlocking the resource 27 | # def after_unlock_path_for(resource) 28 | # super(resource) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /src/app/controllers/customer_tests_controller.rb: -------------------------------------------------------------------------------- 1 | class CustomerTestsController < ApplicationController 2 | before_action :authenticate_customer_account! 3 | 4 | def index; end 5 | end 6 | -------------------------------------------------------------------------------- /src/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | # アピールしたい商品はここで調整する 4 | @customer = true 5 | @featured_products = Product.where(status: :published).order(updated_at: :desc).limit(3) 6 | @popular_products = Product.where(status: :published).order('RAND()').limit(3) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/app/controllers/samples_controller.rb: -------------------------------------------------------------------------------- 1 | class SamplesController < ApplicationController 2 | before_action :authenticate_admin_account! 3 | def index; end 4 | end 5 | -------------------------------------------------------------------------------- /src/app/helpers/samples_helper.rb: -------------------------------------------------------------------------------- 1 | module SamplesHelper 2 | end 3 | -------------------------------------------------------------------------------- /src/app/helpers/wish_products_helper.rb: -------------------------------------------------------------------------------- 1 | module WishProductsHelper 2 | end 3 | -------------------------------------------------------------------------------- /src/app/javascript/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | files: ["*.ts", "*.tsx", "*.js"], 5 | parser: "@typescript-eslint/parser", 6 | }, 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | import "@hotwired/turbo-rails" 3 | import "./controllers" 4 | import "flowbite/dist/flowbite.turbo.js" 5 | import "./channels" 6 | -------------------------------------------------------------------------------- /src/app/javascript/channels/consumer.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `bin/rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /src/app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Import all the channels to be used by Action Cable 2 | import "./chat_channel" 3 | -------------------------------------------------------------------------------- /src/app/javascript/checkout_button.ts: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbo:load", function () { 2 | document.querySelectorAll(".add-to-cart-button").forEach(function (button) { 3 | button.addEventListener("click", function (event) { 4 | event.preventDefault(); 5 | 6 | let productItem = button.closest(".product-item"); 7 | let selectedQuantity = ( 8 | productItem!.querySelector(".quantity") 9 | ); 10 | let cartQuantity = ( 11 | productItem!.querySelector(".cart-quantity") 12 | ); 13 | let cartForm = ( 14 | productItem!.querySelector(".cart-form") 15 | ); 16 | 17 | cartQuantity.value = selectedQuantity.value; 18 | cartForm.submit(); 19 | }); 20 | }); 21 | 22 | document.querySelectorAll(".checkout-button").forEach(function (button) { 23 | button.addEventListener("click", function (event) { 24 | event.preventDefault(); 25 | 26 | let productItem = button.closest(".product-item"); 27 | let selectedQuantity = ( 28 | productItem!.querySelector(".quantity") 29 | ); 30 | let checkoutQuantity = ( 31 | productItem!.querySelector(".checkout-quantity") 32 | ); 33 | let checkoutForm = ( 34 | productItem!.querySelector(".checkout-form") 35 | ); 36 | 37 | checkoutQuantity.value = selectedQuantity.value; 38 | checkoutForm.submit(); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/javascript/clipboard.ts: -------------------------------------------------------------------------------- 1 | { 2 | // clipboard.ts 3 | const shareLink = document.getElementById("share-link") as HTMLAnchorElement; 4 | 5 | if (shareLink) { 6 | shareLink.addEventListener("click", (event: MouseEvent) => { 7 | event.preventDefault(); 8 | const url = shareLink.getAttribute("data-url"); 9 | 10 | if (url) { 11 | navigator.clipboard 12 | .writeText(url) 13 | .then(() => { 14 | showToast("リンクがクリップボードにコピーされました: " + url); 15 | }) 16 | .catch((err) => { 17 | console.error("リンクのコピーに失敗しました: ", err); 18 | }); 19 | } 20 | }); 21 | } 22 | 23 | function showToast(message: string) { 24 | const toastContainer = document.getElementById("toast-container"); 25 | if (toastContainer) { 26 | const toast = document.createElement("div"); 27 | toast.className = 28 | "toast bg-green-500 text-white p-4 rounded-lg shadow-md transition-opacity duration-300 ease-in-out"; 29 | toast.innerText = message; 30 | 31 | toastContainer.appendChild(toast); 32 | 33 | setTimeout(() => { 34 | toast.classList.add("opacity-100"); 35 | }, 100); 36 | 37 | setTimeout(() => { 38 | toast.classList.remove("opacity-100"); 39 | setTimeout(() => { 40 | toast.remove(); 41 | }, 300); 42 | }, 3000); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /src/app/javascript/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.element.textContent = "Hello World!" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // This file is auto-generated by ./bin/rails stimulus:manifest:update 2 | // Run that command whenever you add a new controller or create them with 3 | // ./bin/rails generate stimulus controllerName 4 | 5 | import { application } from "./application" 6 | 7 | import HelloController from "./hello_controller" 8 | application.register("hello", HelloController) 9 | -------------------------------------------------------------------------------- /src/app/javascript/product_form.ts: -------------------------------------------------------------------------------- 1 | import Tagify from "@yaireo/tagify"; 2 | 3 | document.addEventListener("DOMContentLoaded", function () { 4 | const productTypeSelect = document.getElementById( 5 | "product_product_type" 6 | ) as HTMLSelectElement; 7 | const digitalFileField = document.getElementById( 8 | "digital_file_field" 9 | ) as HTMLElement; 10 | 11 | const tagInput = document.getElementById( 12 | "product_tag_list" 13 | ) as HTMLInputElement; 14 | 15 | const tagify = new Tagify(tagInput, { 16 | originalInputValueFormat: (valuesArr: { value: string }[]) => 17 | valuesArr.map((item: { value: string }) => item.value).join(","), 18 | whitelist: [], 19 | }); 20 | 21 | let controller: AbortController | undefined; 22 | 23 | tagify.on("input", onTagInput); 24 | 25 | function onTagInput(e: CustomEvent): void { 26 | let value = e.detail.value; 27 | tagify.whitelist = null; 28 | 29 | // 以前のリクエストを中止 30 | if (controller) { 31 | controller.abort(); 32 | } 33 | 34 | // 新しいAbortControllerを作成 35 | controller = new AbortController(); 36 | 37 | tagify.loading(true); 38 | 39 | fetch(`/admin/products/tags/whitelist?name=${value}`, { 40 | signal: controller.signal, 41 | }) 42 | .then((response) => response.json()) 43 | .then((newWhitelist: string[]) => { 44 | // ホワイトリストを更新 45 | tagify.whitelist = newWhitelist; 46 | 47 | // ローディングアニメーションを非表示にしてドロップダウンを表示 48 | tagify.loading(false).dropdown.show(value); 49 | }) 50 | .catch((error) => { 51 | console.error("エラーが発生しました", error); 52 | }); 53 | } 54 | 55 | function toggleDigitalFileField() { 56 | if (productTypeSelect.value === "digital") { 57 | // 'digital'を正しい値に置き換える必要があります 58 | digitalFileField.style.display = "block"; 59 | } else { 60 | digitalFileField.style.display = "none"; 61 | } 62 | } 63 | 64 | productTypeSelect.addEventListener("change", toggleDigitalFileField); 65 | 66 | // 初期状態の設定 67 | toggleDigitalFileField(); 68 | }); 69 | -------------------------------------------------------------------------------- /src/app/javascript/sample.ts: -------------------------------------------------------------------------------- 1 | { 2 | let message: string = "Hello, TypeScript!!!"; 3 | console.log(message); 4 | } 5 | -------------------------------------------------------------------------------- /src/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /src/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: ENV.fetch('ADMIN_EMAIL', nil) 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /src/app/mailers/contact_mailer.rb: -------------------------------------------------------------------------------- 1 | class ContactMailer < ApplicationMailer 2 | def send_contact_mail(contact) 3 | @contact = contact 4 | mail(to: ENV.fetch('ADMIN_EMAIL', nil), subject: 'お問い合わせ') do |format| 5 | format.html { render template: 'customer/contacts/mailer/send_contact_mail' } 6 | format.text { render template: 'customer/contacts/mailer/send_contact_mail' } 7 | end 8 | end 9 | 10 | def send_user_confirm_mail(contact) 11 | @contact = contact 12 | mail(to: @contact.email, subject: 'お問い合わせの確認メール') do |format| 13 | format.html { render template: 'customer/contacts/mailer/send_user_confirm_mail' } 14 | format.text { render template: 'customer/contacts/mailer/send_user_confirm_mail' } 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/app/mailers/notification_mailer.rb: -------------------------------------------------------------------------------- 1 | class NotificationMailer < ApplicationMailer 2 | def shipping_email 3 | @email = params[:email] 4 | @shipping = params[:shipping] 5 | @order = @shipping.order 6 | mail(to: @email, subject: "ご注文番号 #{@order.order_number} の商品を発送いたしました。") 7 | end 8 | 9 | def order_complete_email 10 | @order = params[:order] 11 | @download_urls = params[:download_urls] || [] 12 | mail(to: params[:email], subject: "ご注文番号 #{@order.order_number} が完了しました") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/app/mailers/test_mailer.rb: -------------------------------------------------------------------------------- 1 | class TestMailer < ApplicationMailer 2 | default from: 'from@example.com' 3 | 4 | def send_test_email(email) 5 | mail(to: email, subject: 'Test Email from Rails', body: 'This is a test email sent from Rails.') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/app/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ApplicationRecord 2 | belongs_to :addressable, polymorphic: true 3 | 4 | extend ActiveHash::Associations::ActiveRecordExtensions 5 | belongs_to :prefecture 6 | 7 | with_options allow_blank: true do 8 | validates :zip_code, format: { with: /\A\d{3}-\d{4}\z/, message: 'の形式が不正です(例:123-4567)' } 9 | validates :prefecture_id, numericality: { only_integer: true, in: 1..47 } 10 | validates :street_address, length: { maximum: 200 } 11 | validates :street_address_2, length: { maximum: 200 } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/app/models/admin.rb: -------------------------------------------------------------------------------- 1 | class Admin < ApplicationRecord 2 | has_one :admin_account, dependent: :destroy 3 | has_one :address, as: :addressable, dependent: :destroy, inverse_of: :addressable 4 | 5 | include JwtAuthenticable 6 | 7 | def generate_jwt(customer_id) 8 | payload = { type: 'join', customer_id:, admin_id: id, exp: 8.hours.from_now.to_i } 9 | super(payload) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/app/models/admin_account.rb: -------------------------------------------------------------------------------- 1 | class AdminAccount < ApplicationRecord 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 4 | devise :database_authenticatable, :timeoutable, 5 | :rememberable, :validatable 6 | 7 | validates :password, format: { 8 | with: /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i 9 | }, on: :create 10 | 11 | belongs_to :admin 12 | end 13 | -------------------------------------------------------------------------------- /src/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /src/app/models/chat.rb: -------------------------------------------------------------------------------- 1 | class Chat < ApplicationRecord 2 | belongs_to :customer 3 | enum :status, { waiting_for_admin: 0, chatting: 1, offline: 2 } 4 | end 5 | -------------------------------------------------------------------------------- /src/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/app/models/concerns/.keep -------------------------------------------------------------------------------- /src/app/models/concerns/jwt_authenticable.rb: -------------------------------------------------------------------------------- 1 | # app/models/concerns/jwt_authenticable.rb 2 | module JwtAuthenticable 3 | extend ActiveSupport::Concern 4 | # インスタンスメソッド 5 | def generate_jwt(payload) 6 | hmac_secret = ENV.fetch('JWT_SECRET_KEY') 7 | JWT.encode(payload, hmac_secret, 'HS256') 8 | end 9 | 10 | class_methods do 11 | # クラスメソッド 12 | def decode_jwt(token) 13 | hmac_secret = ENV.fetch('JWT_SECRET_KEY') 14 | begin 15 | decoded = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) 16 | decoded[0] # デコードしたペイロードを返す 17 | rescue JWT::ExpiredSignature, JWT::DecodeError 18 | nil 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/app/models/contact.rb: -------------------------------------------------------------------------------- 1 | class Contact < ApplicationRecord 2 | belongs_to :customer, optional: true 3 | 4 | before_save { self.email = email.downcase } 5 | 6 | validates :name, presence: true, length: { maximum: 50 } 7 | validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP, message: 'の形式が不正です' } 8 | validates :message, presence: true, length: { maximum: 1000 } 9 | end 10 | -------------------------------------------------------------------------------- /src/app/models/customer.rb: -------------------------------------------------------------------------------- 1 | class Customer < ApplicationRecord 2 | has_many :wish_products, dependent: :destroy 3 | has_many :favorite_products, dependent: :destroy 4 | has_many :download_products, dependent: :destroy 5 | has_many :product_reviews, dependent: :destroy 6 | has_one :customer_account, dependent: :destroy 7 | has_one :address, as: :addressable, dependent: :destroy, inverse_of: :addressable 8 | has_many :orders, dependent: :destroy 9 | has_many :contacts, dependent: :destroy 10 | has_one :chat, dependent: :destroy 11 | 12 | accepts_nested_attributes_for :customer_account 13 | accepts_nested_attributes_for :address 14 | 15 | include JwtAuthenticable 16 | 17 | def generate_jwt 18 | payload = { type: 'create', customer_id: id, exp: 8.hours.from_now.to_i } 19 | super(payload) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/app/models/customer_account.rb: -------------------------------------------------------------------------------- 1 | class CustomerAccount < ApplicationRecord 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 4 | devise :database_authenticatable, :registerable, 5 | :recoverable, :rememberable, :validatable, 6 | :confirmable, :timeoutable 7 | 8 | validates :password, format: { 9 | with: /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i 10 | }, on: :create 11 | 12 | validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP, message: 'の形式が不正です' } 13 | 14 | with_options allow_blank: true do 15 | validates :user_name, length: { in: 3..20 }, format: { with: /\A[a-zA-Z0-9_]+\z/, message: 'は英数字とアンダースコアのみ使用できます' } 16 | validates :shipping_name, length: { maximum: 50 } 17 | validates :phone_number, format: { with: /\A(090|080|070)-\d{4}-\d{4}\z/, message: 'の形式が不正です' } 18 | end 19 | 20 | belongs_to :customer 21 | 22 | # Eメール認証後に実行するdeviseの関数をオーバーライド 23 | def after_confirmation 24 | create_stripe_customer 25 | end 26 | 27 | private 28 | 29 | def create_stripe_customer 30 | stripe_customer = Stripe::Customer.create({ 31 | name: user_name, 32 | email: 33 | }) 34 | customer = Customer.find_by(id: customer_id) 35 | customer.update(stripe_customer_id: stripe_customer.id) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /src/app/models/download_product.rb: -------------------------------------------------------------------------------- 1 | class DownloadProduct < ApplicationRecord 2 | belongs_to :customer 3 | belongs_to :product 4 | after_create :generate_download_url 5 | 6 | def generate_download_url 7 | self.download_url = Rails.application.routes.url_helpers.rails_blob_path( 8 | product.digital_file, 9 | only_path: false, 10 | disposition: 'attachment' 11 | ) 12 | save 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/app/models/favorite_product.rb: -------------------------------------------------------------------------------- 1 | class FavoriteProduct < ApplicationRecord 2 | belongs_to :customer 3 | belongs_to :product 4 | 5 | validates :customer_id, uniqueness: { scope: :product_id, message: 'この商品はすでにお気に入りリストに追加されています。' } 6 | end 7 | -------------------------------------------------------------------------------- /src/app/models/order.rb: -------------------------------------------------------------------------------- 1 | class Order < ApplicationRecord 2 | belongs_to :customer, optional: true 3 | has_many :order_items, dependent: :destroy 4 | 5 | with_options presence: true do 6 | validates :order_number, uniqueness: true, length: { is: 19 }, format: { with: /\A\d{3}-\d{7}-\d{7}\z/ } 7 | validates :total, numericality: { only_integer: true, greater_than_or_equal_to: 0 } 8 | validates :order_date 9 | validates :receipt_url, format: { with: /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/ } 10 | end 11 | 12 | validates :guest_email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_nil: true 13 | 14 | def self.generate_order_number 15 | generate_random_digit = ->(digit) { Array.new(digit) { rand(10) }.join } 16 | 17 | loop do 18 | new_order_number = "#{generate_random_digit.call(3)}-#{generate_random_digit.call(7)}-#{generate_random_digit.call(7)}" 19 | 20 | return new_order_number unless Order.exists?(order_number: new_order_number) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /src/app/models/order_item.rb: -------------------------------------------------------------------------------- 1 | class OrderItem < ApplicationRecord 2 | belongs_to :order 3 | belongs_to :product 4 | 5 | with_options presence: true do 6 | validates :quantity, numericality: { only_integer: true, greater_than: 0 } 7 | validates :order 8 | validates :product 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/app/models/prefecture.rb: -------------------------------------------------------------------------------- 1 | class Prefecture < ActiveHash::Base 2 | self.data = [ 3 | { id: 1, name: '北海道' }, { id: 2, name: '青森県' }, { id: 3, name: '岩手県' }, 4 | { id: 4, name: '宮城県' }, { id: 5, name: '秋田県' }, { id: 6, name: '山形県' }, 5 | { id: 7, name: '福島県' }, { id: 8, name: '茨城県' }, { id: 9, name: '栃木県' }, 6 | { id: 10, name: '群馬県' }, { id: 11, name: '埼玉県' }, { id: 12, name: '千葉県' }, 7 | { id: 13, name: '東京都' }, { id: 14, name: '神奈川県' }, { id: 15, name: '新潟県' }, 8 | { id: 16, name: '富山県' }, { id: 17, name: '石川県' }, { id: 18, name: '福井県' }, 9 | { id: 19, name: '山梨県' }, { id: 20, name: '長野県' }, { id: 21, name: '岐阜県' }, 10 | { id: 22, name: '静岡県' }, { id: 23, name: '愛知県' }, { id: 24, name: '三重県' }, 11 | { id: 25, name: '滋賀県' }, { id: 26, name: '京都府' }, { id: 27, name: '大阪府' }, 12 | { id: 28, name: '兵庫県' }, { id: 29, name: '奈良県' }, { id: 30, name: '和歌山県' }, 13 | { id: 31, name: '鳥取県' }, { id: 32, name: '島根県' }, { id: 33, name: '岡山県' }, 14 | { id: 34, name: '広島県' }, { id: 35, name: '山口県' }, { id: 36, name: '徳島県' }, 15 | { id: 37, name: '香川県' }, { id: 38, name: '愛媛県' }, { id: 39, name: '高知県' }, 16 | { id: 40, name: '福岡県' }, { id: 41, name: '佐賀県' }, { id: 42, name: '長崎県' }, 17 | { id: 43, name: '熊本県' }, { id: 44, name: '大分県' }, { id: 45, name: '宮崎県' }, 18 | { id: 46, name: '鹿児島県' }, { id: 47, name: '沖縄県' } 19 | ] 20 | end 21 | -------------------------------------------------------------------------------- /src/app/models/product_category.rb: -------------------------------------------------------------------------------- 1 | class ProductCategory < ApplicationRecord 2 | validates :name, presence: true 3 | 4 | # Ransackで検索可能な属性を指定するメソッド 5 | def self.ransackable_attributes(_auth_object = nil) 6 | ['name'] 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/app/models/product_review.rb: -------------------------------------------------------------------------------- 1 | class ProductReview < ApplicationRecord 2 | belongs_to :customer 3 | belongs_to :product 4 | 5 | validates :title, presence: true, length: { maximum: 50 } 6 | validates :review, presence: true, length: { maximum: 400 } 7 | validates :rating, presence: true, inclusion: { in: 1..5 } 8 | 9 | delegate :customer_account, to: :customer 10 | end 11 | -------------------------------------------------------------------------------- /src/app/models/shipping.rb: -------------------------------------------------------------------------------- 1 | class Shipping < ApplicationRecord 2 | belongs_to :order 3 | enum :status, { pending: 0, shipped: 1 }, default: :pending, validate: true 4 | enum :carrier, { sagawa: 0, yamato: 1, nihon_yubin: 2, seinou: 3, hukuyama: 4 } 5 | validates :carrier, inclusion: { in: carriers.keys.map(&:to_s) }, on: :update 6 | validates :tracking_number, uniqueness: true, length: { in: 8..18 }, format: { with: /\A[A-Z0-9]+\z/ }, on: :update 7 | 8 | # Ransackで検索可能な属性を指定するメソッド 9 | def self.ransackable_attributes(_auth_object = nil) 10 | %w[status] 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/app/models/tag.rb: -------------------------------------------------------------------------------- 1 | class Tag < ActsAsTaggableOn::Tag 2 | validate :validate_name 3 | 4 | TAG_NAME_MAX_LENGTH = 30 5 | def validate_name 6 | errors.add(:name, "は#{TAG_NAME_MAX_LENGTH}文字以内で入力してください") if name.length > TAG_NAME_MAX_LENGTH 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/app/models/wish_product.rb: -------------------------------------------------------------------------------- 1 | class WishProduct < ApplicationRecord 2 | belongs_to :customer 3 | belongs_to :product 4 | 5 | validates :customer_id, uniqueness: { scope: :product_id, message: 'この商品はすでにウィッシュリストに追加されています。' } 6 | end 7 | -------------------------------------------------------------------------------- /src/app/models/wish_product_token.rb: -------------------------------------------------------------------------------- 1 | class WishProductToken < ApplicationRecord 2 | belongs_to :customer 3 | validates :token, length: { maximum: 255 }, uniqueness: true, presence: true 4 | 5 | def self.generate_token 6 | loop do 7 | random_token = SecureRandom.urlsafe_base64 8 | return random_token unless WishProductToken.exists?(token: random_token) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/app/views/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | files: ["*.html", "*.html.erb"], 5 | parser: "@angular-eslint/template-parser", 6 | }, 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/views/admin/products/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'layouts/flash' %> 2 |
3 |
4 |

5 | 商品情報の編集 6 |

7 | <%= render 'form', product: @product %> 8 |
9 |
10 | 11 | <%= javascript_include_tag "product_form", type: "module" %> 12 | -------------------------------------------------------------------------------- /src/app/views/admin/products/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'layouts/flash' %> 2 |
3 |
4 |

5 | 商品の追加 6 |

7 | <%= render 'form', product: @product %> 8 |
9 |
10 | <%= javascript_include_tag "product_form", type: "module" %> 11 | -------------------------------------------------------------------------------- /src/app/views/admin/products/tags/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'layouts/flash' %> 2 |
3 |
4 |

5 | タグ一覧 6 |

7 |
10 | <% if @tags.empty? %> 11 |

現在タグは1つもありません

12 | <% else %> 13 | <% @tags.each do |tag|%> 14 |
15 | <%= tag.name %> 16 | <%= link_to admin_products_tag_path(tag), data:{ turbo_method: :delete, turbo_confirm: "本当に削除してよろしいですか?"} do %> 17 | 23 | <% end %> 24 |
25 | <% end %> 26 | <% end %> 27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/app/views/admin_accounts/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 確認メール再送信 7 | 8 | 9 |
10 |
11 |

確認メール再送信

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: "space-y-4" }) do |f| %> 16 |
17 | <%= f.label :email, "メールアドレス", class: "block font-medium text-gray-700" %> 18 | <%= f.email_field :email, autofocus: true, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "email@example.com" %> 19 |
20 |
21 | <%= f.submit "確認メール再送信", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 22 |
23 | <% end %> 24 |
25 | <%= render "admin_accounts/shared/links" %> 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

こんにちは <%= @resource.email %> 様,

2 | 3 |

下のリンクをクリックして、メールアドレスの確認を完了してください。

4 | 5 |

<%= link_to 'メールアドレスを確認する', confirmation_url(@resource, confirmation_token: @token) %>

6 | 7 |

このメールに心当たりがない場合は、無視してください。

8 | 9 |

よろしくお願いします。

10 | 11 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

5 | <% else %> 6 |

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

7 | <% end %> 8 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

こんにちは <%= @resource.email %> 様,

2 | 3 |

パスワードの変更リクエストがありました。以下のリンクから新しいパスワードを設定してください。

4 | 5 |

<%= link_to 'パスワードを変更する', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

このリクエストに心当たりがない場合は、このメールを無視してください。リンクをクリックして新しいパスワードを作成するまで、パスワードは変更されません。

8 | 9 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | パスワード再設定 7 | 8 | 9 |
10 |
11 |

パスワード再設定

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "space-y-4" }) do |f| %> 16 | <%= f.hidden_field :reset_password_token %> 17 | 18 |
19 | <%= f.label :password, "新しいパスワード", class: "block font-medium text-gray-700" %> 20 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "半角英数記号8文字以上" %> 21 |
22 | 23 |
24 | <%= f.label :password_confirmation, "新しいパスワード(確認用)", class: "block font-medium text-gray-700" %> 25 | <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "パスワードと同じ文字列" %> 26 |
27 | 28 |
29 | <%= f.submit "パスワード変更", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 30 |
31 | <% end %> 32 |
33 | <%= render "admin_accounts/shared/links" %> 34 |
35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | パスワード再発行 7 | 8 | 9 |
10 |
11 |

パスワード再発行

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: "space-y-4" }) do |f| %> 16 |

17 | 入力されたメールアドレスへパスワードの再設定用メールを送信します。 18 |

19 |
20 | <%= f.label :email, "メールアドレス", class: "block font-medium text-gray-700" %> 21 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "email@example.com" %> 22 |
23 |
24 | <%= f.submit "送信", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 25 |
26 | <% end %> 27 |
28 | <%= render "admin_accounts/shared/links" %> 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "admin_accounts/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "new-password" %> 18 | <% if @minimum_password_length %> 19 |
20 | <%= @minimum_password_length %> characters minimum 21 | <% end %> 22 |
23 | 24 |
25 | <%= f.label :password_confirmation %>
26 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 27 |
28 | 29 |
30 | <%= f.label :current_password %> (we need your current password to confirm your changes)
31 | <%= f.password_field :current_password, autocomplete: "current-password" %> 32 |
33 | 34 |
35 | <%= f.submit "Update" %> 36 |
37 | <% end %> 38 | 39 |

Cancel my account

40 | 41 |
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
42 | 43 | <%= link_to "Back", :back %> 44 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 |
    10 | <% resource.errors.full_messages.each do |message| %> 11 |
  • <%= message %>
  • 12 | <% end %> 13 |
14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "ログインはこちら", new_session_path(resource_name), class: "text-sky-400" %>
3 | <% end %> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "アカウントを作成する", new_registration_path(resource_name), class: "text-sky-400" %>
7 | <% end %> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "パスワードを忘れた方", new_password_path(resource_name), class: "text-sky-400" %>
11 | <% end %> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "認証メールが届いていませんか?", new_confirmation_path(resource_name), class: "text-sky-400" %>
15 | <% end %> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end %> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /src/app/views/admin_accounts/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "admin_accounts/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "admin_accounts/shared/links" %> 17 | -------------------------------------------------------------------------------- /src/app/views/customer/checkouts/success.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | Success 8 |
9 |

支払いが完了しました

10 |
11 |
12 |
13 |
-------------------------------------------------------------------------------- /src/app/views/customer/contacts/mailer/send_contact_mail.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
お問い合わせがありました
3 |
4 |

氏名: <%= @contact.name %>

5 |

メールアドレス: <%= @contact.email %>

6 |

お問い合わせ内容:

7 |

<%= @contact.message %>

8 |
9 | 12 |
-------------------------------------------------------------------------------- /src/app/views/customer/contacts/mailer/send_contact_mail.text.erb: -------------------------------------------------------------------------------- 1 | お問い合わせがありました 2 | 3 | 氏名: 4 | <%= @contact.name %> 5 | 6 | メールアドレス: 7 | <%= @contact.email %> 8 | 9 | お問い合わせ内容: 10 | <%= @contact.message %> 11 | 12 | ※このメールは、自動生成されたメールの為、返信しないでください。 13 | -------------------------------------------------------------------------------- /src/app/views/customer/contacts/mailer/send_user_confirm_mail.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
お問い合わせを受け付けました
3 |
4 |

<%= @contact.name %> 様

5 |

お問い合わせありがとうございます。
以下の内容でお問い合わせを受け付けました。

6 |

-------------------------------------------------------------------

7 |

氏名: <%= @contact.name %>

8 |

メールアドレス: <%= @contact.email %>

9 |

お問い合わせ内容:

10 |

<%= @contact.message %>

11 |

-------------------------------------------------------------------

12 |
13 | 16 |
-------------------------------------------------------------------------------- /src/app/views/customer/contacts/mailer/send_user_confirm_mail.text.erb: -------------------------------------------------------------------------------- 1 | <%= @contact.name %> 様 2 | 3 | お問い合わせありがとうございます。 4 | 以下の内容でお問い合わせを受け付けました。 5 | 6 | ------------------------------------------------------------------- 7 | 8 | 氏名: 9 | <%= @contact.name %> 10 | 11 | メールアドレス: 12 | <%= @contact.email %> 13 | 14 | お問い合わせ内容: 15 | <%= @contact.message %> 16 | 17 | ------------------------------------------------------------------- 18 | 19 | ※このメールは自動生成されました。返信しないでください。 20 | -------------------------------------------------------------------------------- /src/app/views/customer/contacts/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

お問い合わせ

6 |

7 | お問い合わせフォームに必要事項を記入してください。
8 | 内容を確認後、メールアドレスへ担当者よりご連絡させて頂きます。 9 |

10 | 11 | 12 | <%= form_with(model: @contact, url: contacts_path(@contact), local: true, data: { turbo_confirm: "この内容で送信してよろしいですか?" }, class: "space-y-4 rounded-xl border border-gray-200 bg-white p-4 shadow-xl") do |form| %> 13 | <%= render 'shared/flash_error_messages', errors: @errors %> 14 | 15 |
16 | <%= form.label :name, "氏名" %> 17 | <%= form.text_field :name, placeholder: "氏名", value: @contact_name, class: "w-full rounded border border-gray-300 p-2", maxlength: 50, required: true %> 18 |
19 |
20 | <%= form.label :email, "メールアドレス"%> 21 | <%= form.text_field :email, placeholder: "email@example.com", value: @contact_email, class: "w-full rounded border border-gray-300 p-2", required: true %> 22 |
23 |
24 | <%= form.label :message, "お問い合わせ内容" %> 25 | <%= form.text_area :message, rows: 8, placeholder: "お問い合わせ内容", class: "w-full rounded border border-gray-300 p-2", maxlength: 1000, required: true %> 26 |
27 |
28 | <%= form.submit "送信する", class: "my-2 w-full rounded bg-yellow-400 py-2 hover:bg-yellow-500" %> 29 |
30 | <% end %> 31 |
32 |
33 |
-------------------------------------------------------------------------------- /src/app/views/customer/product_reviews/_product_info.html.erb: -------------------------------------------------------------------------------- 1 |
4 | 5 | <%= image_tag 'sample_image.jpg', class: 'block w-2/3 rounded-lg shadow-xl md:w-1/3' %> 6 | 7 |
10 | <%= link_to @product.name, @product, class: 'text-xl text-sky-400 hover:text-orange-400 hover:underline lg:text-2xl'%> 11 |

<%= @product.creator %>

12 |

リリース日 : <%= @product.released_at.strftime("%Y年%m月%d日") %>

13 |

総合評価 : <%= star_rating(@average_rating) %>

14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/views/customer/product_reviews/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'layouts/flash' %> 2 | 3 |
4 | <%= render 'product_info', product_review: @product_review %> 5 | 6 |
7 |
8 |

レビュー

9 | <% if @product_reviews.any? %> 10 | <% @product_reviews.each do |review| %> 11 |
12 | 13 |
14 | 15 | 16 | 17 |
18 |

<%= review.customer_account.user_name %>

19 |
20 |

投稿日 : <%= review.created_at.strftime("%Y年%m月%d日") %>

21 |
22 |

<%= star_rating(review.rating) %>

23 |

<%= review.title %>

24 |
25 |

<%=safe_join(review.review.split("\n"),tag(:br))%>

26 | <% end %> 27 | 28 | 29 | <%= paginate @product_reviews %> 30 | <% else %> 31 |

この商品にはまだレビューがありません。

32 | <% end %> 33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/app/views/customer/wish_products/share.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'layouts/flash' %> 2 | 3 |
4 | 5 |
6 |
7 |

8 | ウィッシュリスト 9 |

10 |
11 | 12 |
13 |
<%= @customer_account.user_name %>さんのウィッシュリスト
14 |
15 |
16 | <%= render 'wish_products', wish_products: @wish_products, average_ratings: @average_ratings, is_my_wishlist: nil %> 17 |
18 |
19 | <%= javascript_include_tag "clipboard", "data-turbo-track": "reload", type: "module" %> 20 | <%= javascript_include_tag 'checkout_button', type: 'module' %> 21 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 確認メール再送信 7 | 8 | 9 |
10 |
11 |

確認メール再送信

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: "space-y-4" }) do |f| %> 16 |
17 | <%= f.label :email, "メールアドレス", class: "block font-medium text-gray-700" %> 18 | <%= f.email_field :email, autofocus: true, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "email@example.com" %> 19 |
20 |
21 | <%= f.submit "確認メール再送信", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 22 |
23 | <% end %> 24 |
25 | <%= render "admin_accounts/shared/links" %> 26 |
27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

こんにちは <%= @resource.email %> 様,

2 | 3 |

下のリンクをクリックして、メールアドレスの確認を完了してください。

4 | 5 |

<%= link_to 'メールアドレスを確認する', confirmation_url(@resource, confirmation_token: @token) %>

6 | 7 |

このメールに心当たりがない場合は、無視してください。

8 | 9 |

よろしくお願いします。

-------------------------------------------------------------------------------- /src/app/views/customer_accounts/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

5 | <% else %> 6 |

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

7 | <% end %> 8 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

こんにちは <%= @resource.email %> 様,

2 | 3 |

パスワードの変更リクエストがありました。以下のリンクから新しいパスワードを設定してください。

4 | 5 |

<%= link_to 'パスワードを変更する', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

このリクエストに心当たりがない場合は、このメールを無視してください。リンクをクリックして新しいパスワードを作成するまで、パスワードは変更されません。

8 | 9 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | パスワード再設定 7 | 8 | 9 |
10 |
11 |

パスワード再設定

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "space-y-4" }) do |f| %> 16 | <%= f.hidden_field :reset_password_token %> 17 | 18 |
19 | <%= f.label :password, "新しいパスワード", class: "block font-medium text-gray-700" %> 20 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "半角英数記号8文字以上" %> 21 |
22 | 23 |
24 | <%= f.label :password_confirmation, "新しいパスワード(確認用)", class: "block font-medium text-gray-700" %> 25 | <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "パスワードと同じ文字列" %> 26 |
27 | 28 |
29 | <%= f.submit "パスワード変更", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 30 |
31 | <% end %> 32 |
33 | <%= render "admin_accounts/shared/links" %> 34 |
35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | パスワード再発行 7 | 8 | 9 |
10 |
11 |

パスワード再発行

12 | 13 | <%= render "shared/flash" %> 14 | 15 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: "space-y-4" }) do |f| %> 16 |

17 | 入力されたメールアドレスへパスワードの再設定用メールを送信します。 18 |

19 |
20 | <%= f.label :email, "メールアドレス", class: "block font-medium text-gray-700" %> 21 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm", placeholder: "email@example.com" %> 22 |
23 |
24 | <%= f.submit "送信", class: "w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-sky-400 hover:bg-sky-500" %> 25 |
26 | <% end %> 27 |
28 | <%= render "admin_accounts/shared/links" %> 29 |
30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "customer_accounts/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "new-password" %> 18 | <% if @minimum_password_length %> 19 |
20 | <%= @minimum_password_length %> characters minimum 21 | <% end %> 22 |
23 | 24 |
25 | <%= f.label :password_confirmation %>
26 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 27 |
28 | 29 |
30 | <%= f.label :current_password %> (we need your current password to confirm your changes)
31 | <%= f.password_field :current_password, autocomplete: "current-password" %> 32 |
33 | 34 |
35 | <%= f.submit "Update" %> 36 |
37 | <% end %> 38 | 39 |

Cancel my account

40 | 41 |
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
42 | 43 | <%= link_to "Back", :back %> 44 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 |
    10 | <% resource.errors.full_messages.each do |message| %> 11 |
  • <%= message %>
  • 12 | <% end %> 13 |
14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "ログインはこちら", new_session_path(resource_name), class: "text-sky-400" %>
3 | <% end %> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "アカウントを作成する", new_registration_path(resource_name), class: "text-sky-400" %>
7 | <% end %> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "パスワードを忘れた方", new_password_path(resource_name), class: "text-sky-400" %>
11 | <% end %> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "認証メールが届いていませんか?", new_confirmation_path(resource_name), class: "text-sky-400" %>
15 | <% end %> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end %> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /src/app/views/customer_accounts/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "customer_accounts/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "customer_accounts/shared/links" %> 17 | -------------------------------------------------------------------------------- /src/app/views/customer_tests/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 11 |
12 |
13 | 14 | 18 |
-------------------------------------------------------------------------------- /src/app/views/kaminari/_first_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "First" page 2 | - available local variables 3 | url: url to the first page 4 | current_page: a page object for the currently displayed page 5 | total_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | 10 | <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %> 11 | 12 | -------------------------------------------------------------------------------- /src/app/views/kaminari/_gap.html.erb: -------------------------------------------------------------------------------- 1 | <%# Non-link tag that stands for skipped pages... 2 | - available local variables 3 | current_page: a page object for the currently displayed page 4 | total_pages: total number of pages 5 | per_page: number of items to fetch per page 6 | remote: data-remote 7 | -%> 8 | <%= t('views.pagination.truncate').html_safe %> 9 | -------------------------------------------------------------------------------- /src/app/views/kaminari/_last_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Last" page 2 | - available local variables 3 | url: url to the last page 4 | current_page: a page object for the currently displayed page 5 | total_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | 10 | <%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %> 11 | 12 | -------------------------------------------------------------------------------- /src/app/views/kaminari/_next_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Next" page 2 | - available local variables 3 | url: url to the next page 4 | current_page: a page object for the currently displayed page 5 | total_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | 10 | <% if current_page.last? %> 11 | <%= t('views.pagination.next').html_safe %> 12 | <% else %> 13 | <%= link_to t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: "next flex h-10 items-center justify-center rounded-e-lg border border-gray-300 bg-white px-4 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /src/app/views/kaminari/_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link showing page number 2 | - available local variables 3 | page: a page object for "this" page 4 | url: url to this page 5 | current_page: a page object for the currently displayed page 6 | total_pages: total number of pages 7 | per_page: number of items to fetch per page 8 | remote: data-remote 9 | -%> 10 | 11 | <% if page.current? %> 12 | <%= page %> 13 | <% else %> 14 | <%= link_to page, url, remote: remote, rel: page.rel, class: "page flex h-10 items-center justify-center border border-gray-300 bg-white px-4 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" %> 15 | <% end %> -------------------------------------------------------------------------------- /src/app/views/kaminari/_paginator.html.erb: -------------------------------------------------------------------------------- 1 | <%# The container tag 2 | - available local variables 3 | current_page: a page object for the currently displayed page 4 | total_pages: total number of pages 5 | per_page: number of items to fetch per page 6 | remote: data-remote 7 | paginator: the paginator that renders the pagination tags inside 8 | -%> 9 | <%= paginator.render do -%> 10 |
11 | 36 |
37 | <% end -%> 38 | -------------------------------------------------------------------------------- /src/app/views/kaminari/_prev_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Previous" page 2 | - available local variables 3 | url: url to the previous page 4 | current_page: a page object for the currently displayed page 5 | total_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | <% if current_page.first? %> 10 | <%= t('views.pagination.previous').html_safe %> 11 | <% else %> 12 | <%= link_to t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: "prev ms-0 flex h-10 items-center justify-center rounded-s-lg border border-e-0 border-gray-300 bg-white px-4 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" %> 13 | <% end %> 14 | 15 | -------------------------------------------------------------------------------- /src/app/views/layouts/_flash.html.erb: -------------------------------------------------------------------------------- 1 | <% if flash[:notice] || flash[:alert] %> 2 | <% message = flash[:notice] || flash[:alert] %> 3 | <% alert_class = flash[:notice] ? 'text-blue-800 bg-blue-50 dark:text-blue-400' : 'text-red-800 bg-red-50 dark:text-red-400' %> 4 | 13 | <% end %> -------------------------------------------------------------------------------- /src/app/views/layouts/admin/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

© 2024 Art-SA2

4 |
5 |
-------------------------------------------------------------------------------- /src/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Art-SA2 5 | 6 | <%= favicon_link_tag('art-sa2-logo.png') %> 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 10 | <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> 11 | <%= javascript_include_tag "sample", "data-turbo-track": "reload", type: "module" %> 12 | 13 | 14 | 15 | 16 | <% if @admin %> 17 | <%= render 'layouts/admin/navbar' %> 18 | <% elsif @customer %> 19 | <%= render 'layouts/customer/navbar' %> 20 | <% end %> 21 |
22 | <%= yield %> 23 |
24 | 25 | <% if @admin %> 26 | <%= render 'layouts/admin/footer' %> 27 | <% elsif @customer %> 28 | <%= render 'layouts/customer/footer' %> 29 | <% end %> 30 | 31 | -------------------------------------------------------------------------------- /src/app/views/layouts/customer/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
    5 |
  • 6 |

    Art-SA2について

    7 |
  • 8 |
  • 9 | <%= link_to 'Art-SA2', root_path, data: { turbo_method: :get }, class: 'text-sm font-bold hover:underline md:text-base' %> 10 |
  • 11 |
  • 12 | ご利用ガイド 13 |
  • 14 |
15 |
16 |
17 |
    18 |
  • 19 |

    コーポレート

    20 |
  • 21 |
  • 22 | <%= link_to 'お問い合わせ', new_contact_path, data: { turbo_method: :get }, class: 'text-sm font-bold hover:underline md:text-base' %> 23 |
  • 24 |
25 |
26 |
27 |
28 |

© 2024 Art-SA2

29 |
30 |
-------------------------------------------------------------------------------- /src/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /src/app/views/notification_mailer/order_complete_email.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 注文完了のお知らせ 6 | 7 | 8 |

ご注文番号 <%= @order.order_number %> の注文が完了しました。

9 |

ご注文いただきありがとうございました。

10 |

以下のリンクから領収書をご確認いただけます:

11 |

<%= link_to '領収書を見る', @order.receipt_url %>

12 | 13 | <% if @download_urls.any? %> 14 |

以下のリンクからダウンロード商品を取得できます:

15 |
    16 | <% @download_urls.each do |url| %> 17 |
  • <%= link_to url, url %>
  • 18 | <% end %> 19 |
20 | <% end %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/views/notification_mailer/order_complete_email.text.erb: -------------------------------------------------------------------------------- 1 | ご注文番号 <%= @order.order_number %> が完了しました。 2 | 3 | ご注文いただきありがとうございました。 4 | 5 | 以下のリンクから領収書をご確認いただけます: 6 | <%= @order.receipt_url %> 7 | 8 | <% if @download_urls.any? %> 9 | 以下のリンクからダウンロード商品を取得できます: 10 | <% @download_urls.each do |url| %> 11 | <%= url %> 12 | <% end %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /src/app/views/notification_mailer/shipping_email.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

ご注文番号 <%= @order.order_number %> の商品を発送いたしました。

8 |

お客様のご注文は配送中です。

9 |

追跡番号から配送状況をご確認いただけます。

10 |

<%= I18n.t("shipping.carriers.#{@shipping.carrier}") %> 追跡番号: <%= @shipping.tracking_number %>

11 |

配送中のアイテム

12 | <% @order.order_items.each do |order_item|%> 13 | <% if order_item.product.physics? %> 14 |

<%= order_item.product.name + ' × ' + order_item.quantity.to_s %>

15 | <% end %> 16 | <% end %> 17 |

ご質問やご不明な点がございましたら、下記アドレスまでお問い合わせ頂きますようお願い致します。

18 |

E-Mail: art-sa2.shop@gmail.com

19 | 20 | -------------------------------------------------------------------------------- /src/app/views/notification_mailer/shipping_email.text.erb: -------------------------------------------------------------------------------- 1 | ご注文番号 <%= @order.order_number %> の商品を発送いたしました。 2 | お客様のご注文は配送中です。 3 | 4 | 追跡番号から配送状況をご確認いただけます。 5 | <%= I18n.t("shipping.carriers.#{@shipping.carrier}") %> 追跡番号: <%= @shipping.tracking_number %> 6 | 7 | 配送中のアイテム 8 | <% @order.order_items.each do |order_item|%> 9 | <% if order_item.product.physics? %> 10 |

<%= order_item.product.name + ' × ' + order_item.quantity.to_s %>

11 | <% end %> 12 | <% end %> 13 | 14 | ご質問やご不明な点がございましたら、下記アドレスまでお問い合わせ頂きますようお願い致します。 15 | E-Mail: art-sa2.shop@gmail.com -------------------------------------------------------------------------------- /src/app/views/samples/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 11 |
12 |
13 | 14 | 18 |
-------------------------------------------------------------------------------- /src/app/views/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if model.errors.any? %> 2 | 10 | <% end %> -------------------------------------------------------------------------------- /src/app/views/shared/_flash.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <% flash.each do |type, message| %> 3 | <% unless message == true %> 4 | <% if type == 'notice' %> 5 |
6 | 通知: 7 | <%= message %> 8 |
9 | <% else %> 10 |
11 | 通知: 12 | <%= message %> 13 |
14 | <% end %> 15 | <% end %> 16 | <% end %> 17 | 18 | 19 | 20 | 21 | <% if resource.errors.any? %> 22 | 32 | <% end %> 33 | -------------------------------------------------------------------------------- /src/app/views/shared/_flash_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if @errors.present? %> 2 | 10 | <% end %> -------------------------------------------------------------------------------- /src/app/views/shared/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

© 2024 Art-SA2

3 |
-------------------------------------------------------------------------------- /src/bin/backup_to_s3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 環境ファイルの読み込み 4 | export $(grep -v '^#' "$(dirname "$0")/../.env" | xargs) 5 | 6 | # 設定 7 | export TZ=Asia/Tokyo 8 | DATE=$(date +"%Y%m%d%H%M") 9 | S3_BUCKET=${S3_BUCKET_NAME} 10 | DB_NAME=${PROD_RDS_DB_NAME} 11 | DB_USER=${PROD_RDS_USERNAME} 12 | DB_PASS=${PROD_RDS_PASSWORD} 13 | DB_HOST=${PROD_RDS_HOSTNAME} 14 | 15 | # ローカルバックアップファイル名 16 | BACKUP_FILE="/home/ubuntu/web/E-Commerce-Webapp-with-Stripe-Sync/src/tmp/db_backup_$DATE.sql" 17 | 18 | # データベースのバックアップを作成 19 | mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS --single-transaction --set-gtid-purged=OFF $DB_NAME > $BACKUP_FILE 20 | 21 | # S3にアップロード 22 | aws s3 cp $BACKUP_FILE s3://$S3_BUCKET/prod/db_backup_$DATE.sql 23 | 24 | # ローカルバックアップを削除 25 | rm $BACKUP_FILE 26 | 27 | # 古いバックアップを削除 (例: 7日以上前のものを削除) 28 | aws s3 ls s3://$S3_BUCKET/prod/ | grep "db_backup_" | while read -r line; do 29 | createDate=$(echo $line | awk '{print $1" "$2}') 30 | createDate=$(date -d"$createDate" +%s) 31 | olderThan=$(date -d"7 days ago" +%s) 32 | if [[ $createDate -lt $olderThan ]]; then 33 | fileName=$(echo $line | awk '{print $4}') 34 | if [[ $fileName != "" ]]; then 35 | aws s3 rm s3://$S3_BUCKET/prod/$fileName 36 | fi 37 | fi 38 | done 39 | -------------------------------------------------------------------------------- /src/bin/cleanup_volumes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # src_https-portal-data以外を削除する 4 | volumes_to_delete=$(docker volume ls -q | grep -v 'src_https-portal-data') 5 | 6 | for volume in $volumes_to_delete; do 7 | sudo docker volume rm $volume 8 | done 9 | -------------------------------------------------------------------------------- /src/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! gem list foreman -i --silent; then 4 | echo "Installing foreman..." 5 | gem install foreman 6 | fi 7 | 8 | exec foreman start -f Procfile.dev "$@" 9 | -------------------------------------------------------------------------------- /src/bin/docker-dev-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # データベースのセットアップ 5 | if [ "$RAILS_ENV" = "development" ]; then 6 | echo "Preparing database..." 7 | rails db:prepare 8 | fi 9 | 10 | exec "$@" -------------------------------------------------------------------------------- /src/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # If running the rails server then create or migrate existing database 4 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 5 | ./bin/rails db:prepare 6 | fi 7 | 8 | exec "${@}" 9 | -------------------------------------------------------------------------------- /src/bin/docker-prod-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Starting docker-prod-entrypoint.sh" 4 | 5 | mkdir -p /E-Commerce-Webapp-with-Stripe-Sync/tmp/pids /E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets 6 | 7 | # 必要なgemをインストール 8 | bundle install 9 | 10 | # データベースの準備 11 | bundle exec rails db:prepare 12 | 13 | # esbuildのインストール 14 | yarn add esbuild 15 | 16 | # JavaScriptのビルド 17 | yarn build 18 | 19 | # CSSのビルド 20 | yarn build:css 21 | 22 | # アセットプリコンパイルの実行(https://github.com/rails/rails/pull/46760) 23 | bundle exec rake assets:precompile SECRET_KEY_BASE_DUMMY=1 RAILS_ENV=production 24 | 25 | # Pumaサーバーの起動 26 | bundle exec puma -C config/puma.rb -e production 27 | -------------------------------------------------------------------------------- /src/bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /src/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /src/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /src/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /src/compose.prod.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: artsa2shop/art-sa2-shop-prod:app-latest 4 | volumes: 5 | - ./:/E-Commerce-Webapp-with-Stripe-Sync 6 | - /E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets:/E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets 7 | environment: 8 | RAILS_ENV: production 9 | TZ: Asia/Tokyo 10 | user: root 11 | env_file: 12 | - .env 13 | 14 | web: 15 | image: artsa2shop/art-sa2-shop-prod:web-latest 16 | depends_on: 17 | - app 18 | volumes: 19 | - ../infra/prod/web/default.conf:/etc/nginx/conf.d/default.conf 20 | - ./public:/E-Commerce-Webapp-with-Stripe-Sync/public 21 | - /E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets:/E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets 22 | 23 | https-portal: 24 | image: steveltn/https-portal:1 25 | ports: 26 | - "80:80" 27 | - "443:443" 28 | environment: 29 | DOMAINS: "art-sa2.com -> http://web:80" 30 | STAGE: "production" 31 | CLIENT_MAX_BODY_SIZE: 100M 32 | WEBSOCKET: "true" 33 | volumes: 34 | - https-portal-data:/var/lib/https-portal 35 | 36 | volumes: 37 | https-portal-data: # HTTPS-PORTALをアップグレードする際に再署名を避けるために推奨されます 38 | -------------------------------------------------------------------------------- /src/compose.stg.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: artsa2/art-sa2-shop:app-latest 4 | volumes: 5 | - ./:/E-Commerce-Webapp-with-Stripe-Sync 6 | - /E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets:/E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets 7 | environment: 8 | RAILS_ENV: production 9 | TZ: Asia/Tokyo 10 | user: root 11 | env_file: 12 | - .env 13 | 14 | web: 15 | image: artsa2/art-sa2-shop:web-latest 16 | depends_on: 17 | - app 18 | volumes: 19 | - ../infra/prod/web/default.conf:/etc/nginx/conf.d/default.conf 20 | - ./public:/E-Commerce-Webapp-with-Stripe-Sync/public 21 | - /E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets:/E-Commerce-Webapp-with-Stripe-Sync/tmp/sockets 22 | 23 | https-portal: 24 | image: steveltn/https-portal:1 25 | ports: 26 | - "80:80" 27 | - "443:443" 28 | environment: 29 | DOMAINS: "art-sa2-stg.com -> http://web:80" 30 | STAGE: "production" 31 | CLIENT_MAX_BODY_SIZE: 100M 32 | WEBSOCKET: "true" 33 | volumes: 34 | - https-portal-data:/var/lib/https-portal 35 | 36 | volumes: 37 | https-portal-data: # HTTPS-PORTALをアップグレードする際に再署名を避けるために推奨されます 38 | -------------------------------------------------------------------------------- /src/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /src/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module DockerRailsTest 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 7.1 13 | 14 | config.i18n.default_locale = :ja 15 | 16 | # Please, add to the `ignore` list any other `lib` subdirectories that do 17 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 18 | # Common ones are `templates`, `generators`, or `middleware`, for example. 19 | config.autoload_lib(ignore: %w(assets tasks)) 20 | 21 | # Configuration for the application, engines, and railties goes here. 22 | # 23 | # These settings can be overridden in specific environments using the files 24 | # in config/environments, which are processed later. 25 | # 26 | # config.time_zone = "Central Time (US & Canada)" 27 | # config.eager_load_paths << Rails.root.join("extras") 28 | 29 | # ステータスの日本語表示のために必要 30 | config.i18n.default_locale = :ja 31 | 32 | # Rails自体のアプリケーションの時刻の設定 33 | config.time_zone = 'Tokyo' 34 | 35 | # DBを読み書きする際に、DBに記録されている時間をどのタイムゾーンで読み込むかの設定 36 | config.active_record.default_timezone = :local 37 | 38 | # バリデーションエラー時のレイアウト崩れ防止 39 | config.action_view.field_error_proc = Proc.new do |html_tag, instance| 40 | %Q(#{html_tag}).html_safe 41 | end 42 | # Active Storage URLの有効期限を30日に設定 43 | config.active_storage.urls_expire_in = 30.days 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /src/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /src/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: async 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: art_sa2 11 | # 管理者側のチャットリストだけのためasyncに設定してます。 12 | # 大量のアクセスが来る場合はredisに変更します 13 | -------------------------------------------------------------------------------- /src/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | Q5P2ms01/OQxwrADU//2L+p4/g8SX0ZMycf6Z7nM1SAaSTn4YnzZjdbqBP+60/XhaV89elTXvO8CeksOk6IDnokxJCRWj5NlE08EYKXkyd3dr7e4em1qZocZnONjvgHR5lcY5l/2HoX9/6OHmTOofZrDLj8ekDMrFbJbqzHPw54vwmbW2BtRk4X8zN1JUZNtZrMm4gzj0JhLPpiIlTsD6nFqlGxaludhh90H7akTFDk1kHsg/1R/qX9OLp2Zz209bJJtnT8yI477zjhx98X+R3yLsJvOSSQ3TL0YobGlg1w7VgWs0Gd0IqYWly8waCaAxO6uSBEHZrX6D7xzskyx+CmQgq6T3RJnPimsP9LD96wE1rDE3lCcNcU+ucfx6NqKWpS0HSHQkygl0yAdcrboNVn2rTdx--hwHzVEgurn1Tvce/--fYBZuLL1Qkzr3eUY7IZBHA== -------------------------------------------------------------------------------- /src/config/database.yml.ci: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql2 3 | encoding: utf8mb4 4 | username: root 5 | password: password 6 | host: 127.0.0.1 7 | database: app_test -------------------------------------------------------------------------------- /src/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /src/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin 'application' 4 | pin '@hotwired/turbo-rails', to: 'turbo.min.js' 5 | pin '@hotwired/stimulus', to: 'stimulus.min.js' 6 | pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js' 7 | pin_all_from 'app/javascript/controllers', under: 'controllers' 8 | pin '@rails/actioncable', to: 'actioncable.esm.js' 9 | pin_all_from 'app/javascript/channels', under: 'channels' 10 | -------------------------------------------------------------------------------- /src/config/initializers/acts_as_taggable_on_ransack.rb: -------------------------------------------------------------------------------- 1 | # ActsAsTaggableOnライブラリのTagクラスの拡張(Ransack用の設定追加) 2 | ActsAsTaggableOn::Tag.class_eval do 3 | def self.ransackable_associations(_auth_object = nil) 4 | %w[taggings] 5 | end 6 | 7 | def self.ransackable_attributes(_auth_object = nil) 8 | %w[name] 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /src/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /src/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += %i[ 7 | passw secret token _key crypt salt certificate otp ssn 8 | ] 9 | -------------------------------------------------------------------------------- /src/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /src/config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Kaminari.configure do |config| 4 | config.default_per_page = 10 5 | # config.max_per_page = nil 6 | config.window = 1 7 | # config.outer_window = 0 8 | # config.left = 0 9 | # config.right = 0 10 | # config.page_method_name = :page 11 | # config.param_name = :page 12 | # config.max_pages = nil 13 | # config.params_on_first_page = false 14 | end 15 | -------------------------------------------------------------------------------- /src/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /src/config/initializers/stripe.rb: -------------------------------------------------------------------------------- 1 | Stripe.api_key = ENV.fetch('STRIPE_API_KEY') 2 | Stripe.api_version = '2024-04-10' 3 | -------------------------------------------------------------------------------- /src/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /src/config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | products: 3 | status: 4 | draft: 下書き 5 | published: 公開中 6 | archived: アーカイブ済み 7 | trashed: ゴミ箱 8 | product_type: 9 | physics: "物理" 10 | digital: "デジタル" 11 | shipping: 12 | carriers: 13 | sagawa: "佐川急便" 14 | yamato: "ヤマト運輸" 15 | nihon_yubin: "日本郵便" 16 | seinou: "西濃運輸" 17 | hukuyama: "福山運輸" 18 | views: 19 | pagination: 20 | previous: "<" 21 | next: ">" 22 | truncate: "..." -------------------------------------------------------------------------------- /src/config/locales/model.ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | activerecord: 3 | models: 4 | admin_account: "管理者アカウント" 5 | customer_account: "顧客アカウント" 6 | product_review: "レビュー" 7 | attributes: 8 | admin_account: 9 | email: "メールアドレス" 10 | password: "パスワード" 11 | password_confirmation: "パスワード(確認)" 12 | user_name: "ユーザー名" 13 | customer_account: 14 | email: "メールアドレス" 15 | password: "パスワード" 16 | password_confirmation: "パスワード(確認)" 17 | user_name: "ユーザー名" 18 | phone_number: "電話番号" 19 | shipping_name: "氏名" 20 | product: 21 | name: "名前" 22 | price: "価格" 23 | stock: "在庫数" 24 | creator: "作者" 25 | description: "説明" 26 | images: "画像" 27 | digital_file: "配信用" 28 | tag_list: "タグ" 29 | stripe_product_id: "Stripe Product ID" 30 | stripe_price_id: "Stripe Product ID" 31 | product_review: 32 | title: "タイトル" 33 | review: "レビュー" 34 | rating: "評価" 35 | address: 36 | zip_code: "郵便番号" 37 | street_address: "住所(1行目)" 38 | street_address_2: "住所(2行目)" 39 | contact: 40 | name: "氏名" 41 | email: "メールアドレス" 42 | message: "お問い合わせ内容" -------------------------------------------------------------------------------- /src/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | 5 | # Puma can serve each request in a thread from an internal thread pool. 6 | # The `threads` method setting takes two numbers: a minimum and maximum. 7 | # Any libraries that use thread pools should be configured to match 8 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 9 | # and maximum; this matches the default thread size of Active Record. 10 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 11 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 12 | threads min_threads_count, max_threads_count 13 | 14 | # Specifies that the worker count should equal the number of processors in production. 15 | if ENV["RAILS_ENV"] == "production" 16 | require "concurrent-ruby" 17 | worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) 18 | workers worker_count if worker_count > 1 19 | end 20 | 21 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 22 | # terminating a worker in development environments. 23 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 24 | 25 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 26 | port ENV.fetch("PORT") { 3000 } 27 | 28 | # Specifies the `environment` that Puma will run in. 29 | environment ENV.fetch("RAILS_ENV") { "development" } 30 | 31 | # Specifies the `pidfile` that Puma will use. 32 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 33 | 34 | # Allow puma to be restarted by `bin/rails restart` command. 35 | plugin :tmp_restart 36 | 37 | app_root = File.expand_path("../..", __FILE__) 38 | bind "unix://#{app_root}/tmp/sockets/puma.sock" 39 | 40 | # ログファイルの設定 41 | unless ENV.fetch("RAILS_ENV", "development") == "development" 42 | stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true 43 | end 44 | -------------------------------------------------------------------------------- /src/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | amazon: 11 | service: TenantS3 12 | access_key_id: <%= ENV['S3_ACCESS_KEY_ID'] %> 13 | secret_access_key: <%= ENV['S3_SECRET_ACCESS_KEY'] %> 14 | region: <%= ENV['S3_REGION'] %> 15 | bucket: <%= ENV['S3_BUCKET_NAME'] %> 16 | # Remember not to checkin your GCS keyfile to a repository 17 | # google: 18 | # service: GCS 19 | # project: your_project 20 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 21 | # bucket: your_own_bucket-<%= Rails.env %> 22 | 23 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 24 | # microsoft: 25 | # service: AzureStorage 26 | # storage_account_name: your_account_name 27 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 28 | # container: your_container_name-<%= Rails.env %> 29 | 30 | # mirror: 31 | # service: Mirror 32 | # primary: local 33 | # mirrors: [ amazon, google, microsoft ] 34 | -------------------------------------------------------------------------------- /src/db/migrate/20240503003220_devise_create_admin_accounts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateAdminAccounts < ActiveRecord::Migration[7.1] 4 | def change 5 | create_table :admin_accounts do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: '' 8 | t.string :encrypted_password, null: false, default: '' 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## 追加 18 | t.string :user_name 19 | t.string :status 20 | 21 | ## Trackable 22 | # t.integer :sign_in_count, default: 0, null: false 23 | # t.datetime :current_sign_in_at 24 | # t.datetime :last_sign_in_at 25 | # t.string :current_sign_in_ip 26 | # t.string :last_sign_in_ip 27 | 28 | ## Confirmable 29 | t.string :confirmation_token 30 | t.datetime :confirmed_at 31 | t.datetime :confirmation_sent_at 32 | t.string :unconfirmed_email # Only if using reconfirmable 33 | 34 | ## Lockable 35 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 36 | # t.string :unlock_token # Only if unlock strategy is :email or :both 37 | # t.datetime :locked_at 38 | 39 | t.timestamps null: false 40 | end 41 | 42 | add_index :admin_accounts, :email, unique: true 43 | add_index :admin_accounts, :reset_password_token, unique: true 44 | add_index :admin_accounts, :confirmation_token, unique: true 45 | # add_index :admin_accounts, :unlock_token, unique: true 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /src/db/migrate/20240503013829_create_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateAddresses < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :addresses do |t| 4 | t.integer :zip_code 5 | t.string :state 6 | t.string :city 7 | t.string :street_address 8 | t.string :street_address_2 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :addresses, :zip_code 14 | add_index :addresses, :city 15 | add_index :addresses, :state 16 | add_index :addresses, %i[city zip_code] 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/db/migrate/20240505123328_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :products do |t| 4 | t.string :name, null: false 5 | t.integer :price, null: false 6 | t.integer :stock, null: false, default: 0 7 | t.text :description, null: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/db/migrate/20240506064945_change_stock_in_products.rb: -------------------------------------------------------------------------------- 1 | class ChangeStockInProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | change_column_null :products, :stock, true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240506073146_add_status_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddStatusToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :status, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240506102646_add_stripe_product_id_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddStripeProductIdToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :stripe_product_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240511071352_add_creator_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddCreatorToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :creator, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240512011256_devise_create_customer_accounts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateCustomerAccounts < ActiveRecord::Migration[7.1] 4 | def change 5 | create_table :customer_accounts do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## 追加 18 | t.string :user_name 19 | t.string :status 20 | 21 | ## Trackable 22 | # t.integer :sign_in_count, default: 0, null: false 23 | # t.datetime :current_sign_in_at 24 | # t.datetime :last_sign_in_at 25 | # t.string :current_sign_in_ip 26 | # t.string :last_sign_in_ip 27 | 28 | ## Confirmable 29 | t.string :confirmation_token 30 | t.datetime :confirmed_at 31 | t.datetime :confirmation_sent_at 32 | t.string :unconfirmed_email # Only if using reconfirmable 33 | 34 | ## Lockable 35 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 36 | # t.string :unlock_token # Only if unlock strategy is :email or :both 37 | # t.datetime :locked_at 38 | 39 | 40 | t.timestamps null: false 41 | end 42 | 43 | add_index :customer_accounts, :email, unique: true 44 | add_index :customer_accounts, :reset_password_token, unique: true 45 | add_index :customer_accounts, :confirmation_token, unique: true 46 | # add_index :customer_accounts, :unlock_token, unique: true 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/db/migrate/20240512032905_create_product_categories.rb: -------------------------------------------------------------------------------- 1 | class CreateProductCategories < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :product_categories do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/db/migrate/20240512033205_add_product_category_ref_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddProductCategoryRefToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_belongs_to :products, :product_category, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240512101719_add_stripe_price_id_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddStripePriceIdToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :stripe_price_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240515113000_create_customers.rb: -------------------------------------------------------------------------------- 1 | class CreateCustomers < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :customers do |t| 4 | t.references :customer_account, null: false, foreign_key: true 5 | t.references :address, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :customers, [:customer_account_id, :address_id], unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/db/migrate/20240515120013_create_wish_products.rb: -------------------------------------------------------------------------------- 1 | class CreateWishProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :wish_products do |t| 4 | t.references :customer, null: false, foreign_key: true 5 | t.references :product, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :wish_products, [:customer_id, :product_id], unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/db/migrate/20240522090039_add_addressable_to_addresses.rb: -------------------------------------------------------------------------------- 1 | class AddAddressableToAddresses < ActiveRecord::Migration[7.1] 2 | def change 3 | add_reference :addresses, :addressable, polymorphic: true, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240523234426_create_favorite_products.rb: -------------------------------------------------------------------------------- 1 | class CreateFavoriteProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :favorite_products do |t| 4 | t.references :customer, null: false, foreign_key: true 5 | t.references :product, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/db/migrate/20240524133513_add_unique_index_to_favorite_products.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexToFavoriteProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_index :favorite_products, [:customer_id, :product_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240525023349_update_customer_and_customer_account_tables.rb: -------------------------------------------------------------------------------- 1 | class UpdateCustomerAndCustomerAccountTables < ActiveRecord::Migration[7.1] 2 | def change 3 | remove_column :customers, :address_id, :bigint 4 | remove_column :customers, :customer_account_id, :bigint 5 | add_reference :customer_accounts, :customer, foreign_key: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/db/migrate/20240525231108_create_product_reviews.rb: -------------------------------------------------------------------------------- 1 | class CreateProductReviews < ActiveRecord::Migration[7.1] 2 | 3 | def change 4 | create_table :product_reviews do |t| 5 | t.integer :rating, null: false 6 | t.string :title, null: false 7 | t.text :review, null: false 8 | t.references :customer, null: false, foreign_key: true 9 | t.references :product, null: false, foreign_key: true 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :product_reviews, [:customer_id, :product_id], unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/db/migrate/20240526084641_add_stripe_customer_id_to_customers.rb: -------------------------------------------------------------------------------- 1 | class AddStripeCustomerIdToCustomers < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :customers, :stripe_customer_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240527081952_create_orders.rb: -------------------------------------------------------------------------------- 1 | class CreateOrders < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :orders do |t| 4 | t.string :order_number, index: { unique: true} 5 | t.integer :total 6 | t.datetime :order_date 7 | t.string :guest_email 8 | t.string :receipt_url 9 | t.references :customer, null: true, foreign_key: true 10 | 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/db/migrate/20240527082654_create_order_items.rb: -------------------------------------------------------------------------------- 1 | class CreateOrderItems < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :order_items do |t| 4 | t.integer :quantity 5 | t.references :order, null: false, foreign_key: true 6 | t.references :product, null: false, foreign_key: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/db/migrate/20240529235025_create_download_products.rb: -------------------------------------------------------------------------------- 1 | class CreateDownloadProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :download_products do |t| 4 | t.references :customer, null: false, foreign_key: true 5 | t.references :product, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :download_products, [:customer_id, :product_id], unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/db/migrate/20240529235325_add_type_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddTypeToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :product_type, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240531110338_add_download_url_to_download_products.rb: -------------------------------------------------------------------------------- 1 | class AddDownloadUrlToDownloadProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :download_products, :download_url, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240601015501_create_shippings.rb: -------------------------------------------------------------------------------- 1 | class CreateShippings < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :shippings do |t| 4 | t.integer :carrier 5 | t.string :tracking_number 6 | t.integer :status 7 | t.datetime :shipping_at 8 | t.references :order, null: false, foreign_key: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/db/migrate/20240602005424_add_unique_index_to_shippings_tracking_number.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexToShippingsTrackingNumber < ActiveRecord::Migration[7.1] 2 | def change 3 | add_index :shippings, :tracking_number, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240603074543_add_shipping_name_to_customer_accounts.rb: -------------------------------------------------------------------------------- 1 | class AddShippingNameToCustomerAccounts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :customer_accounts, :shipping_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240603075400_add_phone_number_to_customer_accounts.rb: -------------------------------------------------------------------------------- 1 | class AddPhoneNumberToCustomerAccounts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :customer_accounts, :phone_number, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240603081220_modify_addresses.rb: -------------------------------------------------------------------------------- 1 | class ModifyAddresses < ActiveRecord::Migration[7.1] 2 | def change 3 | remove_column :addresses, :state, :string 4 | remove_column :addresses, :city, :string 5 | add_column :addresses, :prefecture_id, :integer 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/db/migrate/20240604084709_change_zip_code_type_in_addresses_table.rb: -------------------------------------------------------------------------------- 1 | class ChangeZipCodeTypeInAddressesTable < ActiveRecord::Migration[7.1] 2 | def change 3 | change_column :addresses, :zip_code, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035355_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 1) 4 | class ActsAsTaggableOnMigration < ActiveRecord::Migration[6.0] 5 | def self.up 6 | create_table ActsAsTaggableOn.tags_table do |t| 7 | t.string :name 8 | t.timestamps 9 | end 10 | 11 | create_table ActsAsTaggableOn.taggings_table do |t| 12 | t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table } 13 | 14 | # You should make sure that the column created is 15 | # long enough to store the required class names. 16 | t.references :taggable, polymorphic: true 17 | t.references :tagger, polymorphic: true 18 | 19 | # Limit is created to prevent MySQL error on index 20 | # length for MyISAM table type: http://bit.ly/vgW2Ql 21 | t.string :context, limit: 128 22 | 23 | t.datetime :created_at 24 | end 25 | 26 | add_index ActsAsTaggableOn.taggings_table, %i[taggable_id taggable_type context], 27 | name: 'taggings_taggable_context_idx' 28 | end 29 | 30 | def self.down 31 | drop_table ActsAsTaggableOn.taggings_table 32 | drop_table ActsAsTaggableOn.tags_table 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035356_add_missing_unique_indices.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 2) 4 | class AddMissingUniqueIndices < ActiveRecord::Migration[6.0] 5 | def self.up 6 | # add_index ActsAsTaggableOn.tags_table, :name, unique: true 7 | 8 | # remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) 9 | # remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx' 10 | # add_index ActsAsTaggableOn.taggings_table, 11 | # %i[tag_id taggable_id taggable_type context tagger_id tagger_type], 12 | # unique: true, name: 'taggings_idx' 13 | end 14 | 15 | def self.down 16 | # remove_index ActsAsTaggableOn.tags_table, :name 17 | 18 | # remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx' 19 | 20 | # add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) 21 | # add_index ActsAsTaggableOn.taggings_table, %i[taggable_id taggable_type context], 22 | # name: 'taggings_taggable_context_idx' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035357_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 3) 4 | class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[6.0] 5 | def self.up 6 | add_column ActsAsTaggableOn.tags_table, :taggings_count, :integer, default: 0 7 | 8 | ActsAsTaggableOn::Tag.reset_column_information 9 | ActsAsTaggableOn::Tag.find_each do |tag| 10 | ActsAsTaggableOn::Tag.reset_counters(tag.id, ActsAsTaggableOn.taggings_table) 11 | end 12 | end 13 | 14 | def self.down 15 | remove_column ActsAsTaggableOn.tags_table, :taggings_count 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035358_add_missing_taggable_index.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 4) 4 | class AddMissingTaggableIndex < ActiveRecord::Migration[6.0] 5 | def self.up 6 | # add_index ActsAsTaggableOn.taggings_table, %i[taggable_id taggable_type context], 7 | # name: 'taggings_taggable_context_idx' 8 | end 9 | 10 | def self.down 11 | # remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035359_change_collation_for_tag_names.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 5) 4 | # This migration is added to circumvent issue #623 and have special characters 5 | # work properly 6 | 7 | class ChangeCollationForTagNames < ActiveRecord::Migration[6.0] 8 | def up 9 | if ActsAsTaggableOn::Utils.using_mysql? 10 | execute("ALTER TABLE #{ActsAsTaggableOn.tags_table} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035360_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 6) 4 | class AddMissingIndexesOnTaggings < ActiveRecord::Migration[6.0] 5 | def change 6 | add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists? ActsAsTaggableOn.taggings_table, :tag_id 7 | add_index ActsAsTaggableOn.taggings_table, :taggable_id unless index_exists? ActsAsTaggableOn.taggings_table, 8 | :taggable_id 9 | add_index ActsAsTaggableOn.taggings_table, :taggable_type unless index_exists? ActsAsTaggableOn.taggings_table, 10 | :taggable_type 11 | add_index ActsAsTaggableOn.taggings_table, :tagger_id unless index_exists? ActsAsTaggableOn.taggings_table, 12 | :tagger_id 13 | add_index ActsAsTaggableOn.taggings_table, :context unless index_exists? ActsAsTaggableOn.taggings_table, :context 14 | 15 | unless index_exists? ActsAsTaggableOn.taggings_table, %i[tagger_id tagger_type] 16 | add_index ActsAsTaggableOn.taggings_table, %i[tagger_id tagger_type] 17 | end 18 | 19 | unless index_exists? ActsAsTaggableOn.taggings_table, %i[taggable_id taggable_type tagger_id context], 20 | name: 'taggings_idy' 21 | add_index ActsAsTaggableOn.taggings_table, %i[taggable_id taggable_type tagger_id context], 22 | name: 'taggings_idy' 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/db/migrate/20240606035361_add_tenant_to_taggings.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from acts_as_taggable_on_engine (originally 7) 4 | class AddTenantToTaggings < ActiveRecord::Migration[6.0] 5 | def self.up 6 | add_column ActsAsTaggableOn.taggings_table, :tenant, :string, limit: 128 7 | add_index ActsAsTaggableOn.taggings_table, :tenant unless index_exists? ActsAsTaggableOn.taggings_table, :tenant 8 | end 9 | 10 | def self.down 11 | remove_index ActsAsTaggableOn.taggings_table, :tenant 12 | remove_column ActsAsTaggableOn.taggings_table, :tenant 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/db/migrate/20240610002709_create_contacts.rb: -------------------------------------------------------------------------------- 1 | class CreateContacts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :contacts do |t| 4 | t.string :name, null: false 5 | t.string :email, null: false 6 | t.text :message, null: false 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/db/migrate/20240610062552_add_customer_id_to_contacts.rb: -------------------------------------------------------------------------------- 1 | class AddCustomerIdToContacts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_reference :contacts, :customer, null: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/db/migrate/20240612074748_create_chats.rb: -------------------------------------------------------------------------------- 1 | class CreateChats < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :chats do |t| 4 | t.integer :status, default: 0 5 | t.references :customer, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/db/migrate/20240612084818_create_admins.rb: -------------------------------------------------------------------------------- 1 | class CreateAdmins < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :admins do |t| 4 | 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/db/migrate/20240612094107_add_admin_ref_to_admin_accounts.rb: -------------------------------------------------------------------------------- 1 | class AddAdminRefToAdminAccounts < ActiveRecord::Migration[7.1] 2 | def change 3 | unless column_exists?(:admin_accounts, :admin_id) 4 | add_reference :admin_accounts, :admin, null: false, foreign_key: true 5 | else 6 | unless foreign_key_exists?(:admin_accounts, :admin_id) 7 | add_foreign_key :admin_accounts, :admins, column: :admin_id 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/db/migrate/20240621062234_change_price_and_description_in_products.rb: -------------------------------------------------------------------------------- 1 | class ChangePriceAndDescriptionInProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | change_column_null :products, :price, true 4 | change_column_default :products, :price, 0 5 | change_column_null :products, :description, true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/db/migrate/20240623082100_create_wish_product_tokens.rb: -------------------------------------------------------------------------------- 1 | class CreateWishProductTokens < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :wish_product_tokens do |t| 4 | t.string :token, index: { unique: true } 5 | t.references :customer, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/db/migrate/20240624054816_add_released_at_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddReleasedAtToProducts < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :products, :released_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/lib/active_storage/service/tenant_s3_service.rb: -------------------------------------------------------------------------------- 1 | require 'active_storage/service/s3_service' 2 | 3 | module ActiveStorage 4 | class Service::TenantS3Service < Service::S3Service 5 | private 6 | 7 | def object_for(key) 8 | if ENV['S3_ENV'] == 'stg' 9 | bucket.object File.join('stg', key) 10 | else 11 | bucket.object File.join('prod', key) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/lib/assets/.keep -------------------------------------------------------------------------------- /src/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/lib/tasks/.keep -------------------------------------------------------------------------------- /src/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/log/.keep -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "E-Commerce-Webapp-with-Stripe-Sync", 3 | "version": "1.0.0", 4 | "license": "UNLICENSED", 5 | "private": true, 6 | "dependencies": { 7 | "@hotwired/stimulus": "^3.2.2", 8 | "@hotwired/turbo-rails": "^8.0.4", 9 | "@yaireo/tagify": "^4.26.5", 10 | "flowbite": "^2.3.0" 11 | }, 12 | "devDependencies": { 13 | "@angular-eslint/template-parser": "^17.3.0", 14 | "@tailwindcss/aspect-ratio": "^0.4.2", 15 | "@tailwindcss/container-queries": "^0.1.1", 16 | "@tailwindcss/forms": "^0.5.7", 17 | "@tailwindcss/typography": "^0.5.13", 18 | "@typescript-eslint/parser": "^7.7.1", 19 | "autoprefixer": "^10.4.19", 20 | "esbuild": "^0.20.2", 21 | "eslint": "^8.57.0", 22 | "eslint-plugin-tailwindcss": "^3.15.1", 23 | "postcss": "^8.4.38", 24 | "tailwindcss": "^3.4.3", 25 | "typescript": "^5.4.5" 26 | }, 27 | "scripts": { 28 | "lint": "eslint ./app", 29 | "lint:debug": "eslint ./app --debug", 30 | "lint:fix": "eslint ./app --fix", 31 | "build": "esbuild ./app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets", 32 | "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/tailwind_output.css --minify" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /src/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /src/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /src/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/public/favicon.ico -------------------------------------------------------------------------------- /src/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /src/spec/channels/chat_channel_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ChatChannel, type: :channel do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /src/spec/factories/addresses.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :address do 3 | addressable { association :customer } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/spec/factories/admin_accounts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :admin_account do 3 | email { Faker::Internet.email } 4 | password { 'password123' } 5 | password_confirmation { 'password123' } 6 | admin 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/spec/factories/admins.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :admin do # rubocop:disable Lint/EmptyBlock 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/spec/factories/chats.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :chat do 3 | status { :waiting_for_admin } 4 | customer 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/spec/factories/contacts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :contact do 3 | name { '山田太郎' } 4 | email { 'test@example.com' } 5 | message { 'お問い合わせ内容' } 6 | customer 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/spec/factories/customer_accounts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :customer_account do 3 | sequence(:email) { |n| "customer#{n}@example.com" } 4 | password { 'password123' } 5 | password_confirmation { 'password123' } 6 | user_name { 'testuser' } 7 | 8 | customer factory: %i[customer] 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/spec/factories/customers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :customer do 3 | stripe_customer_id { 'sample_stripe_customer_id' } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/spec/factories/favorite_products.rb: -------------------------------------------------------------------------------- 1 | # spec/factories/favorite_products.rb 2 | FactoryBot.define do 3 | factory :favorite_product do 4 | customer 5 | product 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/spec/factories/order_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :order_item do 3 | quantity { 1 } 4 | order 5 | product 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/spec/factories/orders.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :order do 3 | order_number { '123-1234567-1234567' } 4 | total { 0 } 5 | order_date { Time.current } 6 | guest_email { 'test@gmail.com' } 7 | customer { nil } 8 | receipt_url { 'https://receipt.com' } 9 | 10 | trait :with_customer do 11 | guest_email { nil } 12 | customer 13 | end 14 | end 15 | 16 | factory :login_user_order, parent: :order do 17 | customer 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/spec/factories/product_categories.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :product_category do 3 | name { 'イラスト' } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/spec/factories/product_reviews.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :product_review do 3 | title { Faker::Lorem.characters(number: 50) } 4 | review { Faker::Lorem.paragraph_by_chars(number: 400, supplemental: false) } 5 | rating { Faker::Number.between(from: 1, to: 5) } 6 | customer 7 | product 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/spec/factories/products.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :product do 3 | sequence(:name) { |n| "product_#{n}" } 4 | sequence(:price) { |n| n * 100 } 5 | stock { 100 } 6 | description { 'Test description' } 7 | creator { 'Test creator' } 8 | product_type { 'physics' } 9 | product_category 10 | 11 | trait :digital do 12 | product_type { 'digital' } 13 | after(:build) do |product| 14 | product.digital_file.attach( 15 | io: Rails.root.join('spec/fixtures/files/valid_digital_file.zip').open, 16 | filename: 'valid_digital_file.zip', 17 | content_type: 'application/zip' 18 | ) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/spec/factories/shippings.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :shipping do 3 | carrier { :sagawa } 4 | tracking_number { '12345678AZ' } 5 | status { 0 } 6 | shipping_at { '2024-06-01 10:55:01' } 7 | order 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/spec/factories/wish_product_tokens.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :wish_product_token do 3 | token { WishProductToken.generate_token } 4 | customer 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/spec/factories/wish_products.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :wish_product do 3 | customer 4 | product 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/spec/fixtures/files/invalid_image.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/spec/fixtures/files/invalid_image.txt -------------------------------------------------------------------------------- /src/spec/fixtures/files/large_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/spec/fixtures/files/large_image.jpg -------------------------------------------------------------------------------- /src/spec/fixtures/files/valid_digital_file.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/spec/fixtures/files/valid_digital_file.zip -------------------------------------------------------------------------------- /src/spec/fixtures/files/valid_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/spec/fixtures/files/valid_image.jpg -------------------------------------------------------------------------------- /src/spec/mailers/previews/test_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/test_mailer 2 | class TestMailerPreview < ActionMailer::Preview 3 | end 4 | -------------------------------------------------------------------------------- /src/spec/mailers/test_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | # TODO: 時間がある際に細かいテストを記述 2 | 3 | # require 'rails_helper' 4 | 5 | # RSpec.describe TestMailer, type: :mailer do 6 | # pending "add some examples to (or delete) #{__FILE__}" 7 | # end 8 | -------------------------------------------------------------------------------- /src/spec/models/chat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Chat, type: :model do 4 | let(:customer) { create(:customer) } 5 | 6 | it 'valid with a status, customer' do 7 | chat = described_class.new(status: :waiting_for_admin, customer:) 8 | expect(chat).to be_valid 9 | end 10 | 11 | it 'is invalid without a status' do 12 | chat = described_class.new(status: nil) 13 | chat.valid? 14 | expect(chat).not_to be_valid 15 | end 16 | 17 | it 'is invalid without a customer' do 18 | chat = described_class.new(status: :waiting_for_admin, customer: nil) 19 | chat.valid? 20 | expect(chat).not_to be_valid 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/spec/models/contact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Contact, type: :model do 4 | let(:contact) { create(:contact) } 5 | 6 | describe 'Validations' do 7 | it 'has a valid factory' do 8 | expect(contact).to be_valid 9 | end 10 | 11 | it 'is valid with a customer_id' do 12 | contact.customer_id = nil 13 | expect(contact).to be_valid 14 | end 15 | 16 | it 'is invalid without a name' do 17 | contact.name = nil 18 | contact.valid? 19 | expect(contact.errors[:name]).to include('を入力してください') 20 | end 21 | 22 | it 'is invalid without a email' do 23 | contact.email = nil 24 | contact.valid? 25 | expect(contact.errors[:email]).to include('を入力してください') 26 | end 27 | 28 | it 'is invalid without a message' do 29 | contact.message = nil 30 | contact.valid? 31 | expect(contact.errors[:message]).to include('を入力してください') 32 | end 33 | 34 | it 'is invalid with a name longer than 50 characters' do 35 | contact.name = 'a' * 51 36 | contact.valid? 37 | expect(contact.errors[:name]).to include('は50文字以内で入力してください') 38 | end 39 | 40 | it 'is invalid email format' do 41 | contact.email = 'format.@test@example.com' 42 | contact.valid? 43 | expect(contact.errors[:email]).to include('の形式が不正です') 44 | end 45 | 46 | it 'is invalid with a message longer than 1000 characters' do 47 | contact.message = 'a' * 1001 48 | contact.valid? 49 | expect(contact.errors[:message]).to include('は1000文字以内で入力してください') 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/spec/models/customer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Customer, type: :model do 4 | let(:customer_account) { create(:customer_account) } 5 | let(:customer) { customer_account.customer } 6 | let(:address) { customer.address } 7 | 8 | describe 'Associations' do 9 | it 'belongs to a customer_account' do 10 | expect(customer.customer_account).to eq(customer_account) 11 | end 12 | 13 | it 'belongs to an address' do 14 | expect(customer.address).to eq(address) 15 | end 16 | end 17 | 18 | describe 'Validations' do 19 | it 'is valid with valid attributes' do 20 | expect(customer).to be_valid 21 | end 22 | 23 | it 'is valid without a customer_account' do 24 | customer.customer_account = nil 25 | expect(customer).to be_valid 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/spec/models/download_product_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe DownloadProduct, type: :model do 4 | let(:customer) { create(:customer) } 5 | let(:product) { create(:product) } 6 | 7 | it 'is valid with valid attributes' do 8 | download_product = described_class.new(customer:, product:) 9 | expect(download_product).to be_valid 10 | end 11 | 12 | context 'without a customer' do 13 | let(:download_product) { described_class.new(customer: nil, product:) } 14 | 15 | it 'is not valid' do 16 | expect(download_product).not_to be_valid 17 | end 18 | 19 | it 'has an error on customer' do 20 | download_product.valid? 21 | expect(download_product.errors[:customer]).to include('を入力してください') 22 | end 23 | end 24 | 25 | context 'without a product' do 26 | let(:download_product) { described_class.new(customer:, product: nil) } 27 | 28 | it 'is not valid' do 29 | expect(download_product).not_to be_valid 30 | end 31 | 32 | it 'has an error on product' do 33 | download_product.valid? 34 | expect(download_product.errors[:product]).to include('を入力してください') 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /src/spec/models/favorite_product_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe FavoriteProduct, type: :model do 4 | let(:customer_account) { create(:customer_account) } 5 | let(:customer) { customer_account.customer } 6 | let(:product) { create(:product) } 7 | 8 | describe 'Validations' do 9 | it 'is valid with valid attributes' do 10 | favorite_product = build(:favorite_product, customer:, product:) 11 | expect(favorite_product).to be_valid 12 | end 13 | 14 | it 'is not valid without a customer' do 15 | favorite_product = build(:favorite_product, customer: nil, product:) 16 | expect(favorite_product).not_to be_valid 17 | end 18 | 19 | it 'adds an error message when customer is missing' do 20 | favorite_product = build(:favorite_product, customer: nil, product:) 21 | favorite_product.valid? 22 | expect(favorite_product.errors[:customer]).to include('を入力してください') 23 | end 24 | 25 | it 'is not valid without a product' do 26 | favorite_product = build(:favorite_product, customer:, product: nil) 27 | expect(favorite_product).not_to be_valid 28 | end 29 | 30 | it 'adds an error message when product is missing' do 31 | favorite_product = build(:favorite_product, customer:, product: nil) 32 | favorite_product.valid? 33 | expect(favorite_product.errors[:product]).to include('を入力してください') 34 | end 35 | 36 | it 'is not valid with a duplicate customer and product combination' do 37 | create(:favorite_product, customer:, product:) 38 | duplicate_favorite_product = build(:favorite_product, customer:, product:) 39 | expect(duplicate_favorite_product).not_to be_valid 40 | end 41 | 42 | it 'adds an error message for duplicate customer and product combination' do 43 | create(:favorite_product, customer:, product:) 44 | duplicate_favorite_product = build(:favorite_product, customer:, product:) 45 | duplicate_favorite_product.valid? 46 | expect(duplicate_favorite_product.errors[:customer_id]).to include('この商品はすでにお気に入りリストに追加されています。') 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/spec/models/order_item_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe OrderItem, type: :model do 4 | let(:order_item) { create(:order_item) } 5 | 6 | it 'is valid with quantity, order, product' do 7 | expect(order_item).to be_valid 8 | end 9 | 10 | it 'is invalid without quantity' do 11 | order_item.quantity = nil 12 | order_item.valid? 13 | expect(order_item.errors[:quantity]).to include('を入力してください') 14 | end 15 | 16 | it 'is invalid without order' do 17 | order_item.order = nil 18 | order_item.valid? 19 | expect(order_item.errors[:order]).to include('を入力してください') 20 | end 21 | 22 | it 'is invalid without product' do 23 | order_item.product = nil 24 | order_item.valid? 25 | expect(order_item.errors[:product]).to include('を入力してください') 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/spec/models/product_category_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ProductCategory, type: :model do 4 | it 'is valid with a name' do 5 | expect(build(:product_category)).to be_valid 6 | end 7 | 8 | it 'is invalid without a name' do 9 | product_category = build(:product_category, name: nil) 10 | product_category.valid? 11 | expect(product_category.errors[:name]).to include('を入力してください') 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/spec/models/product_review_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ProductReview, type: :model do 4 | let(:customer_account) { create(:customer_account) } 5 | let(:customer) { customer_account.customer } 6 | let(:product) { create(:product) } 7 | let(:product_review) { build(:product_review, customer:, product:) } 8 | 9 | describe 'Validations' do 10 | it 'has a valid factory' do 11 | expect(product_review).to be_valid 12 | end 13 | 14 | it 'is invalid without a customer' do 15 | product_review.customer = nil 16 | expect(product_review).not_to be_valid 17 | end 18 | 19 | it 'is invalid without a product' do 20 | product_review.product = nil 21 | expect(product_review).not_to be_valid 22 | end 23 | 24 | it 'is invalid without a title' do 25 | product_review.title = nil 26 | product_review.valid? 27 | expect(product_review.errors[:title]).to include('を入力してください') 28 | end 29 | 30 | it 'is invalid without a review' do 31 | product_review.review = nil 32 | product_review.valid? 33 | expect(product_review.errors[:review]).to include('を入力してください') 34 | end 35 | 36 | it 'is invalid without a rating' do 37 | product_review.rating = nil 38 | product_review.valid? 39 | expect(product_review.errors[:rating]).to include('を入力してください') 40 | end 41 | 42 | it 'rating must be included in the list' do 43 | product_review.rating = nil 44 | product_review.valid? 45 | expect(product_review.errors[:rating]).to include('は一覧にありません') 46 | end 47 | 48 | it 'is invalid with a title longer than 50 characters' do 49 | product_review.title = 'a' * 51 50 | product_review.valid? 51 | expect(product_review.errors[:title]).to include('は50文字以内で入力してください') 52 | end 53 | 54 | it 'is invalid with a review longer than 400 characters' do 55 | product_review.review = 'a' * 401 56 | product_review.valid? 57 | expect(product_review.errors[:review]).to include('は400文字以内で入力してください') 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /src/spec/models/shipping_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Shipping, type: :model do 4 | let(:shipping) { create(:shipping) } 5 | 6 | it 'is valid with a carrier, tracking_number, order' do 7 | expect(shipping).to be_valid 8 | end 9 | 10 | it 'is invalid without a carrier' do 11 | shipping.carrier = nil 12 | shipping.valid? 13 | expect(shipping.errors[:carrier]).to include('は一覧にありません') 14 | end 15 | 16 | it 'is invalid without a tracking_number' do 17 | shipping.tracking_number = nil 18 | shipping.valid? 19 | expect(shipping.errors[:tracking_number]).to include('は不正な値です') 20 | end 21 | 22 | it 'is invalid without a status' do 23 | shipping.status = nil 24 | shipping.valid? 25 | expect(shipping.errors[:status]).to include('は一覧にありません') 26 | end 27 | 28 | it 'is invalid without a order' do 29 | shipping.order = nil 30 | shipping.valid? 31 | expect(shipping.errors[:order]).to include('を入力してください') 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/spec/models/wish_product_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe WishProduct, type: :model do 4 | let(:customer_account) { create(:customer_account) } 5 | let(:customer) { customer_account.customer } 6 | let(:product) { create(:product) } 7 | 8 | describe 'Validations' do 9 | it 'is valid with valid attributes' do 10 | wish_product = build(:wish_product, customer:, product:) 11 | expect(wish_product).to be_valid 12 | end 13 | 14 | it 'is not valid without a customer' do 15 | wish_product = build(:wish_product, customer: nil, product:) 16 | expect(wish_product).not_to be_valid 17 | end 18 | 19 | it 'adds an error message when customer is missing' do 20 | wish_product = build(:wish_product, customer: nil, product:) 21 | wish_product.valid? 22 | expect(wish_product.errors[:customer]).to include('を入力してください') 23 | end 24 | 25 | it 'is not valid without a product' do 26 | wish_product = build(:wish_product, customer:, product: nil) 27 | expect(wish_product).not_to be_valid 28 | end 29 | 30 | it 'adds an error message when product is missing' do 31 | wish_product = build(:wish_product, customer:, product: nil) 32 | wish_product.valid? 33 | expect(wish_product.errors[:product]).to include('を入力してください') 34 | end 35 | 36 | it 'is not valid with a duplicate customer and product combination' do 37 | create(:wish_product, customer:, product:) 38 | duplicate_wish_product = build(:wish_product, customer:, product:) 39 | expect(duplicate_wish_product).not_to be_valid 40 | end 41 | 42 | it 'adds an error message for duplicate customer and product combination' do 43 | create(:wish_product, customer:, product:) 44 | duplicate_wish_product = build(:wish_product, customer:, product:) 45 | duplicate_wish_product.valid? 46 | expect(duplicate_wish_product.errors[:customer_id]).to include('この商品はすでにウィッシュリストに追加されています。') 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/spec/models/wish_product_token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe WishProductToken, type: :model do 4 | let(:customer) { create(:customer) } 5 | 6 | it 'is valid with token with customer_id' do 7 | wish_product_token = described_class.new(token: described_class.generate_token, customer:) 8 | expect(wish_product_token).to be_valid 9 | end 10 | 11 | it 'is invalid without a token' do 12 | wish_product_token = described_class.new(token: nil) 13 | wish_product_token.valid? 14 | expect(wish_product_token.errors[:token]).to include('を入力してください') 15 | end 16 | 17 | it 'is invalid without a customer' do 18 | wish_product_token = described_class.new(customer_id: nil) 19 | wish_product_token.valid? 20 | expect(wish_product_token.errors[:customer]).to include('を入力してください') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/storage/.keep -------------------------------------------------------------------------------- /src/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | 3 | module.exports = { 4 | darkMode: "class", 5 | content: [ 6 | "./public/*.html", 7 | "./app/helpers/*.rb", 8 | "./app/javascript/**/*.js", 9 | "./app/views/**/*", 10 | "./node_modules/flowbite/**/*.js", 11 | ], 12 | safelist: [ 13 | "flex", 14 | "flex-row-reverse", 15 | "items-start", 16 | "gap-2.5", 17 | "w-full", 18 | "max-w-[320px]", 19 | "flex-col", 20 | "gap-1", 21 | "leading-1.5", 22 | "rounded-s-xl", 23 | "rounded-se-xl", 24 | "bg-sky-400", 25 | "p-4", 26 | "text-sm", 27 | "font-normal", 28 | "text-white", 29 | "rounded-e-xl", 30 | "rounded-ss-xl", 31 | "border-gray-200", 32 | "bg-gray-200", 33 | "text-gray-900", 34 | "text-gray-400", 35 | "inline-flex", 36 | "self-center", 37 | "rounded-full", 38 | "p-2", 39 | "text-center", 40 | "hover:bg-sky-100", 41 | "z-10", 42 | "hidden", 43 | "w-40", 44 | "divide-y", 45 | "divide-gray-100", 46 | "rounded-lg", 47 | "bg-white", 48 | "shadow-xl", 49 | "py-2", 50 | ], 51 | theme: { 52 | extend: { 53 | fontFamily: { 54 | sans: ["Inter var", ...defaultTheme.fontFamily.sans], 55 | }, 56 | }, 57 | }, 58 | plugins: [ 59 | require("@tailwindcss/forms"), 60 | require("@tailwindcss/aspect-ratio"), 61 | require("@tailwindcss/typography"), 62 | require("@tailwindcss/container-queries"), 63 | require("flowbite/plugin"), 64 | ], 65 | }; 66 | -------------------------------------------------------------------------------- /src/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /src/test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module ApplicationCable 4 | class ConnectionTest < ActionCable::Connection::TestCase 5 | # test "connects with cookies" do 6 | # cookies.signed[:user_id] = 42 7 | # 8 | # connect 9 | # 10 | # assert_equal connection.user_id, "42" 11 | # end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/controllers/.keep -------------------------------------------------------------------------------- /src/test/controllers/samples_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SamplesControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/test/fixtures/articles.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | title: MyString 5 | body: MyText 6 | 7 | two: 8 | title: MyString 9 | body: MyText 10 | -------------------------------------------------------------------------------- /src/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/fixtures/files/.keep -------------------------------------------------------------------------------- /src/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/helpers/.keep -------------------------------------------------------------------------------- /src/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/integration/.keep -------------------------------------------------------------------------------- /src/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/mailers/.keep -------------------------------------------------------------------------------- /src/test/mailers/previews/notification_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | class NotificationMailerPreview < ActionMailer::Preview 2 | def shipping_email 3 | NotificationMailer.with(email: 'test@gmail.com', order: Order.first, shipping: Shipping.first).shipping_email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/models/.keep -------------------------------------------------------------------------------- /src/test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/test/system/.keep -------------------------------------------------------------------------------- /src/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/tmp/.keep -------------------------------------------------------------------------------- /src/vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/vendor/.keep -------------------------------------------------------------------------------- /src/vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/recursion-backend-projects/E-Commerce-Webapp-with-Stripe-Sync/2c6842c531665702e5b88f4b9db4048ff1aa0a62/src/vendor/javascript/.keep --------------------------------------------------------------------------------