WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 61 additions & 61 deletions app/controllers/customers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,27 @@ class CustomersController < Sellers::BaseController
layout "inertia", only: [:index]

def index
product = Link.fetch(params[:link_id]) if params[:link_id].present?
sales = fetch_sales(products: [product].compact)
customers_presenter = CustomersPresenter.new(
pundit_user:,
product:,
customers: load_sales(sales),
pagination: { page: 1, pages: (sales.results.total / CUSTOMERS_PER_PAGE.to_f).ceil, next: nil },
count: sales.results.total
)
create_user_event("customers_view")
if !request.inertia_partial?
product = Link.fetch(params[:link_id]) if params[:link_id].present?
sales = fetch_sales(products: [product].compact)
customers_presenter = CustomersPresenter.new(
pundit_user:,
product:,
customers: load_sales(sales),
pagination: { page: 1, pages: (sales.results.total / CUSTOMERS_PER_PAGE.to_f).ceil, next: nil },
count: sales.results.total
)
create_user_event("customers_view")
end

purchase = current_seller.sales.find_by_external_id!(params[:purchase_id]) if params[:purchase_id].present?

render inertia: "Customers/Index",
props: { customers_presenter: customers_presenter.customers_props }
render inertia: "Customers/Index", props: {
customers_presenter: (-> { customers_presenter.customers_props } if !request.inertia_partial?),
customer_emails: InertiaRails.optional { fetch_customer_emails(purchase) },
missed_posts: InertiaRails.optional { CustomerPresenter.new(purchase:).missed_posts(workflow_id: params[:workflow_id]) },
workflows: InertiaRails.optional { WorkflowsPresenter.new(seller: current_seller).workflow_options_by_purchase_props(purchase:) },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved over here as per #2149 (comment)

}.compact
Comment on lines +14 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetches drawer data on partial visits

end

def paged
Expand Down Expand Up @@ -64,55 +72,6 @@ def customer_charges
render json: []
end

def customer_emails
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to #index to reduce network calls as per #2149 (comment)

original_purchase = current_seller.sales.find_by_external_id!(params[:purchase_id]) if params[:purchase_id].present?

all_purchases = if original_purchase.subscription.present?
original_purchase.subscription.purchases.all_success_states_except_preorder_auth_and_gift.preload(:receipt_email_info_from_purchase)
else
[original_purchase]
end

receipts = all_purchases.map do |purchase|
receipt_email_info = purchase.receipt_email_info
{
type: "receipt",
name: receipt_email_info&.email_name&.humanize || "Receipt",
id: purchase.external_id,
state: receipt_email_info&.state&.humanize || "Delivered",
state_at: receipt_email_info.present? ? receipt_email_info.most_recent_state_at.in_time_zone(current_seller.timezone) : purchase.created_at.in_time_zone(current_seller.timezone),
url: receipt_purchase_url(purchase.external_id, email: purchase.email),
date: purchase.created_at
}
end

posts = original_purchase.installments.alive.where(seller_id: original_purchase.seller_id).map do |post|
email_info = CreatorContactingCustomersEmailInfo.where(purchase: original_purchase, installment: post).last
{
type: "post",
name: post.name,
id: post.external_id,
state: email_info.state.humanize,
state_at: email_info.most_recent_state_at.in_time_zone(current_seller.timezone),
date: post.published_at
}
end

unpublished_posts = posts.select { |post| post[:date].nil? }
published_posts = posts - unpublished_posts
emails = published_posts
emails = emails.sort_by { |e| -e[:date].to_i } + unpublished_posts
emails = receipts + emails unless original_purchase.is_bundle_product_purchase?

render json: emails
end

def missed_posts
purchase = Purchase.where(email: params[:purchase_email].to_s).find_by_external_id!(params[:purchase_id])

render json: CustomerPresenter.new(purchase:).missed_posts
end

def product_purchases
purchase = current_seller.sales.find_by_external_id!(params[:purchase_id]) if params[:purchase_id].present?

Expand Down Expand Up @@ -191,4 +150,45 @@ def set_on_page_type
def authorize
super([:audience, Purchase], :index?)
end

def fetch_customer_emails(original_purchase)
all_purchases = if original_purchase.subscription.present?
original_purchase.subscription.purchases.all_success_states_except_preorder_auth_and_gift.preload(:receipt_email_info_from_purchase)
else
[original_purchase]
end

receipts = all_purchases.map do |purchase|
receipt_email_info = purchase.receipt_email_info
{
type: "receipt",
name: receipt_email_info&.email_name&.humanize || "Receipt",
id: purchase.external_id,
state: receipt_email_info&.state&.humanize || "Delivered",
state_at: receipt_email_info.present? ? receipt_email_info.most_recent_state_at.in_time_zone(current_seller.timezone) : purchase.created_at.in_time_zone(current_seller.timezone),
url: receipt_purchase_url(purchase.external_id, email: purchase.email),
date: purchase.created_at
}
end

posts = original_purchase.installments.alive.where(seller_id: original_purchase.seller_id).map do |post|
email_info = CreatorContactingCustomersEmailInfo.where(purchase: original_purchase, installment: post).last
{
type: "post",
name: post.name,
id: post.external_id,
state: email_info.state.humanize,
state_at: email_info.most_recent_state_at.in_time_zone(current_seller.timezone),
date: post.published_at
}
end

unpublished_posts = posts.select { |post| post[:date].nil? }
published_posts = posts - unpublished_posts
emails = published_posts
emails = emails.sort_by { |e| -e[:date].to_i } + unpublished_posts
emails = receipts + emails unless original_purchase.is_bundle_product_purchase?

emails
end
end
63 changes: 34 additions & 29 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
class PostsController < ApplicationController
include CustomDomainConfig

before_action :authenticate_user!, only: %i[send_for_purchase]
after_action :verify_authorized, only: %i[send_for_purchase]
before_action :authenticate_user!, only: %i[send_for_purchase send_missed_posts]
after_action :verify_authorized, only: %i[send_for_purchase send_missed_posts]
before_action :fetch_post, only: %i[send_for_purchase]
before_action :ensure_seller_is_eligible_to_send_emails, only: %i[send_for_purchase]
before_action :fetch_purchase, only: %i[send_for_purchase send_missed_posts]
before_action :ensure_seller_is_eligible_to_send_emails, only: %i[send_for_purchase send_missed_posts]
before_action :ensure_can_contact_for_purchase, only: %i[send_for_purchase send_missed_posts]
before_action :set_user_and_custom_domain_config, only: %i[show]
before_action :check_if_needs_redirect, only: %i[show]

Expand Down Expand Up @@ -54,28 +56,19 @@ def redirect_from_purchase_id
def send_for_purchase
authorize @post

purchase = current_seller.sales.find_by_external_id!(params[:purchase_id])

# Limit the number of emails sent per post to avoid abuse.
Rails.cache.fetch("post_email:#{@post.id}:#{purchase.id}", expires_in: 8.hours) do
CreatorContactingCustomersEmailInfo.where(purchase:, installment: @post).destroy_all

PostEmailApi.process(
post: @post,
recipients: [
{
email: purchase.email,
purchase:,
url_redirect: purchase.url_redirect,
subscription: purchase.subscription,
}.compact_blank
])
true
end
SendPostsForPurchaseService.send_post(post: @post, purchase: @purchase)

head :no_content
end

def send_missed_posts
authorize [:audience, @purchase], :send_missed_posts?

SendPostsForPurchaseService.send_missed_posts_for(purchase: @purchase, workflow_id: params[:workflow_id])

render json: { message: "Missed emails are queued for delivery" }, status: :ok
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the purchase opted out, we could return a 422 status with an error toast straight away instead of enqueue-ing the job. This is more coherent from the user's standpoint. We would still need to check that at the job level (in the event of retries for instance).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should already be handled as there is a before_action :ensure_can_contact_for_purchase, only: %i[send_for_purchase send_missed_posts] callback in the controller which does it.

I'll handle it differently though using validation at service level and change the error code as well

end

def increment_post_views
fetch_post(false)

Expand All @@ -99,14 +92,14 @@ def fetch_post(viewed_by_seller = true)
end
e404 if @post.blank?

if viewed_by_seller
e404 if @post.seller != current_seller
if @post.seller_id?
@seller = @post.seller
else
@seller = @post.link.seller
Comment on lines +95 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why you need to set @seller here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for reasons highlighted over #2149 (comment) and #2149 (comment)

end

if @post.seller_id?
e404 if @post.seller.suspended?
elsif @post.link_id?
e404 if @post.link.seller&.suspended?
if viewed_by_seller
e404 if @seller != current_seller
end
end

Expand All @@ -120,10 +113,22 @@ def check_if_needs_redirect
end
end

def fetch_purchase
@purchase = current_seller.sales.find_by_external_id(params[:purchase_id])
return e404_json if @purchase.blank?

@seller = @purchase.seller
end

def ensure_seller_is_eligible_to_send_emails
seller = @post.seller || @post.link.seller
unless seller&.eligible_to_send_emails?
unless @seller&.eligible_to_send_emails?
render json: { message: "You are not eligible to resend this email." }, status: :unauthorized
end
end

def ensure_can_contact_for_purchase
unless @purchase.can_contact?
render json: { message: "This customer has opted out of receiving emails." }, status: :forbidden
end
end
end
13 changes: 0 additions & 13 deletions app/javascript/components/Admin/EmptyState.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/javascript/components/Admin/Users/UserList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { usePage } from "@inertiajs/react";
import React from "react";

import EmptyState from "$app/components/Admin/EmptyState";
import PaginatedLoader, { type Pagination } from "$app/components/Admin/PaginatedLoader";
import UserCard, { type User } from "$app/components/Admin/Users/User";
import EmptyState from "$app/components/ui/EmptyState";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-factored as per #2149 (comment)


type PageProps = {
users: User[];
Expand Down
Loading