2019-04-30 08:27:42 +08:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2022-07-28 10:27:38 +08:00
|
|
|
|
RSpec.describe EmailController do
|
2019-05-07 11:12:20 +08:00
|
|
|
|
fab!(:user) { Fabricate(:user) }
|
2018-04-16 12:44:43 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
describe "#perform_unsubscribe" do
|
2018-05-22 07:06:46 +08:00
|
|
|
|
it "raises not found on invalid key" do
|
|
|
|
|
post "/email/unsubscribe/123.json"
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
end
|
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
describe "unsubscribe from all emails" do
|
|
|
|
|
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::ALL_TYPE) }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can fully unsubscribe" do
|
|
|
|
|
user.user_option.update_columns(
|
|
|
|
|
email_digests: true,
|
|
|
|
|
email_level: UserOption.email_level_types[:never],
|
|
|
|
|
email_messages_level: UserOption.email_level_types[:never],
|
|
|
|
|
mailing_list_mode: true,
|
|
|
|
|
)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { unsubscribe_all: "1" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
get response.redirect_url
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
# cause it worked ... yay
|
|
|
|
|
expect(body).to include(user.email)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
user.user_option.reload
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(user.user_option.email_digests).to eq(false)
|
|
|
|
|
expect(user.user_option.email_level).to eq(UserOption.email_level_types[:never])
|
|
|
|
|
expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:never])
|
|
|
|
|
expect(user.user_option.mailing_list_mode).to eq(false)
|
|
|
|
|
end
|
2019-04-17 23:14:40 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can disable mailing list" do
|
|
|
|
|
user.user_option.update_columns(mailing_list_mode: true)
|
2019-04-17 23:14:40 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { disable_mailing_list: "1" }
|
2019-04-17 23:14:40 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(user.user_option.reload.mailing_list_mode).to eq(false)
|
|
|
|
|
end
|
2019-04-17 23:14:40 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
describe "unsubscribe from digest" do
|
|
|
|
|
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::DIGEST_TYPE) }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "Can change digest frequency" do
|
|
|
|
|
weekly_interval_minutes = 10_080
|
|
|
|
|
user.user_option.update_columns(email_digests: true, digest_after_minutes: 0)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json",
|
|
|
|
|
params: {
|
|
|
|
|
digest_after_minutes: weekly_interval_minutes.to_s,
|
|
|
|
|
}
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(user.user_option.reload.digest_after_minutes).to eq(weekly_interval_minutes)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "Can disable email digests setting frequency to zero" do
|
|
|
|
|
user.user_option.update_columns(email_digests: true, digest_after_minutes: 10_080)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { digest_after_minutes: "0" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
user.user_option.reload
|
|
|
|
|
expect(user.user_option.digest_after_minutes).to be_zero
|
|
|
|
|
expect(user.user_option.email_digests).to eq(false)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
describe "unsubscribe from a topic" do
|
|
|
|
|
fab!(:a_post) { Fabricate(:post) }
|
|
|
|
|
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::TOPIC_TYPE, post: a_post) }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can unwatch topic" do
|
|
|
|
|
TopicUser.change(
|
|
|
|
|
user.id,
|
|
|
|
|
a_post.topic_id,
|
|
|
|
|
notification_level: TopicUser.notification_levels[:watching],
|
|
|
|
|
)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { unwatch_topic: "1" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(TopicUser.get(a_post.topic, user).notification_level).to eq(
|
|
|
|
|
TopicUser.notification_levels[:tracking],
|
|
|
|
|
)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can mute topic" do
|
|
|
|
|
TopicUser.change(
|
|
|
|
|
user.id,
|
|
|
|
|
a_post.topic_id,
|
|
|
|
|
notification_level: TopicUser.notification_levels[:watching],
|
|
|
|
|
)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { mute_topic: "1" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(TopicUser.get(a_post.topic, user).notification_level).to eq(
|
|
|
|
|
TopicUser.notification_levels[:muted],
|
|
|
|
|
)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can unwatch category" do
|
|
|
|
|
cu =
|
|
|
|
|
CategoryUser.create!(
|
|
|
|
|
user_id: user.id,
|
|
|
|
|
category_id: a_post.topic.category_id,
|
|
|
|
|
notification_level: CategoryUser.notification_levels[:watching],
|
|
|
|
|
)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { unwatch_category: "1" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
it "can unwatch first post from category" do
|
|
|
|
|
cu =
|
|
|
|
|
CategoryUser.create!(
|
|
|
|
|
user_id: user.id,
|
|
|
|
|
category_id: a_post.topic.category_id,
|
|
|
|
|
notification_level: CategoryUser.notification_levels[:watching_first_post],
|
|
|
|
|
)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
post "/email/unsubscribe/#{key}.json", params: { unwatch_category: "1" }
|
2018-05-22 07:06:46 +08:00
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
|
|
|
|
|
end
|
2018-05-22 07:06:46 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-10-09 09:04:46 +08:00
|
|
|
|
describe "#unsubscribed" do
|
|
|
|
|
describe "when email is invalid" do
|
|
|
|
|
it "should return the right response" do
|
|
|
|
|
get "/email/unsubscribed", params: { email: "somerandomstring" }
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-04-16 12:44:43 +08:00
|
|
|
|
|
|
|
|
|
describe "when topic is public" do
|
2022-06-22 02:49:47 +08:00
|
|
|
|
fab!(:topic) { Fabricate(:topic) }
|
|
|
|
|
|
2018-04-16 12:44:43 +08:00
|
|
|
|
it "should return the right response" do
|
2018-05-22 07:06:46 +08:00
|
|
|
|
key = SecureRandom.hex
|
2019-11-27 13:11:49 +08:00
|
|
|
|
Discourse.cache.write(key, user.email)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
get "/email/unsubscribed", params: { key: key, topic_id: topic.id }
|
2018-06-07 16:11:09 +08:00
|
|
|
|
expect(response.status).to eq(200)
|
2018-04-16 12:44:43 +08:00
|
|
|
|
expect(response.body).to include(topic.title)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "when topic is private" do
|
2022-06-22 02:49:47 +08:00
|
|
|
|
fab!(:private_topic) { Fabricate(:private_message_topic) }
|
|
|
|
|
|
2018-04-16 12:44:43 +08:00
|
|
|
|
it "should return the right response" do
|
2018-05-22 07:06:46 +08:00
|
|
|
|
key = SecureRandom.hex
|
2019-11-27 13:11:49 +08:00
|
|
|
|
Discourse.cache.write(key, user.email)
|
2018-05-22 07:06:46 +08:00
|
|
|
|
get "/email/unsubscribed", params: { key: key, topic_id: private_topic.id }
|
2018-06-07 16:11:09 +08:00
|
|
|
|
expect(response.status).to eq(200)
|
2018-04-16 12:44:43 +08:00
|
|
|
|
expect(response.body).to_not include(private_topic.title)
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-10-09 09:04:46 +08:00
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#unsubscribe" do
|
2018-06-05 10:57:11 +08:00
|
|
|
|
it "displays not found if key is not found" do
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe(SecureRandom.hex)
|
|
|
|
|
|
2018-06-05 10:57:11 +08:00
|
|
|
|
expect(response.body).to include(CGI.escapeHTML(I18n.t("unsubscribe.not_found_description")))
|
|
|
|
|
end
|
|
|
|
|
|
2019-05-07 11:12:20 +08:00
|
|
|
|
fab!(:user) { Fabricate(:user) }
|
2022-12-01 01:29:07 +08:00
|
|
|
|
|
|
|
|
|
it "displays an error when the key has no associated user" do
|
|
|
|
|
key_without_owner = UnsubscribeKey.create_key_for(user, UnsubscribeKey::DIGEST_TYPE)
|
|
|
|
|
user.destroy!
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe(key_without_owner)
|
|
|
|
|
|
|
|
|
|
expect(response.body).to include(
|
|
|
|
|
CGI.escapeHTML(I18n.t("unsubscribe.user_not_found_description")),
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
2022-06-22 02:49:47 +08:00
|
|
|
|
let(:unsubscribe_key) { UnsubscribeKey.create_key_for(user, key_type, post: post) }
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when unsubscribing from digest" do
|
2022-06-22 02:49:47 +08:00
|
|
|
|
let(:key_type) { UnsubscribeKey::DIGEST_TYPE }
|
|
|
|
|
let(:post) { nil }
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "displays log out button if wrong user logged in" do
|
|
|
|
|
sign_in(Fabricate(:admin))
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
expect(response.body).to include(I18n.t("unsubscribe.log_out"))
|
|
|
|
|
expect(response.body).to include(I18n.t("unsubscribe.different_user_description"))
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2020-07-23 14:20:10 +08:00
|
|
|
|
it "displays correct label when email_digests is set to false" do
|
|
|
|
|
user.user_option.update!(email_digests: false, digest_after_minutes: 10_080)
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
|
|
|
|
|
expect(body).to include("You are not receiving summary emails")
|
2021-01-05 10:32:02 +08:00
|
|
|
|
expect(body).to include("Don’t send me any mail from Discourse")
|
2020-07-23 14:20:10 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "hides unsubscribe from all checkbox when user already unsubscribed" do
|
|
|
|
|
user.user_option.update!(
|
|
|
|
|
email_digests: false,
|
|
|
|
|
mailing_list_mode: false,
|
|
|
|
|
email_level: 2,
|
|
|
|
|
email_messages_level: 2,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
|
|
|
|
|
expect(body).to include("You are not receiving summary emails")
|
|
|
|
|
expect(body).not_to include("Don't send me any mail from Discourse")
|
|
|
|
|
end
|
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "correctly handles mailing list mode" do
|
2021-03-05 04:24:37 +08:00
|
|
|
|
SiteSetting.disable_mailing_list_mode = false
|
2019-04-17 23:14:40 +08:00
|
|
|
|
user.user_option.update_columns(mailing_list_mode: true)
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).to include(I18n.t("unsubscribe.mailing_list_mode"))
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
SiteSetting.disable_mailing_list_mode = true
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).not_to include(I18n.t("unsubscribe.mailing_list_mode"))
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
user.user_option.update_columns(mailing_list_mode: false)
|
|
|
|
|
SiteSetting.disable_mailing_list_mode = false
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).not_to include(I18n.t("unsubscribe.mailing_list_mode"))
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "Lets you select the digest frequency ranging from never to half a year" do
|
|
|
|
|
selected_digest_frequency = 0
|
|
|
|
|
slow_digest_frequencies = ["weekly", "every month", "every six months", "never"]
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
source = Nokogiri::HTML5.fragment(response.body)
|
2019-04-17 23:14:40 +08:00
|
|
|
|
expect(source.css(".combobox option").map(&:inner_text)).to eq(slow_digest_frequencies)
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "Selects the next slowest frequency by default" do
|
|
|
|
|
every_month_freq = 43_200
|
|
|
|
|
six_months_freq = 259_200
|
|
|
|
|
user.user_option.update_columns(digest_after_minutes: every_month_freq)
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
source = Nokogiri::HTML5.fragment(response.body)
|
2019-04-17 23:14:40 +08:00
|
|
|
|
expect(source.css(".combobox option[selected='selected']")[0]["value"]).to eq(
|
|
|
|
|
six_months_freq.to_s,
|
|
|
|
|
)
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "Uses never as the selected frequency if current one is six months" do
|
|
|
|
|
never_frequency = 0
|
|
|
|
|
six_months_freq = 259_200
|
|
|
|
|
user.user_option.update_columns(digest_after_minutes: six_months_freq)
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
source = Nokogiri::HTML5.fragment(response.body)
|
2019-04-17 23:14:40 +08:00
|
|
|
|
expect(source.css(".combobox option[selected='selected']")[0]["value"]).to eq(
|
|
|
|
|
never_frequency.to_s,
|
|
|
|
|
)
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when unsubscribing from a post" do
|
2019-05-07 11:12:20 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post) }
|
2019-04-17 23:14:40 +08:00
|
|
|
|
let(:user) { post.user }
|
2022-06-22 02:49:47 +08:00
|
|
|
|
let(:key_type) { UnsubscribeKey::TOPIC_TYPE }
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
it "correctly handles watched categories" do
|
|
|
|
|
cu = create_category_user(:watching)
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).to include("unwatch_category")
|
2021-07-26 10:19:30 +08:00
|
|
|
|
doc = Nokogiri::HTML5.fragment(response.body)
|
|
|
|
|
expect(doc.css('a.badge-wrapper[href="/c/uncategorized/1"]').size).to eq(1)
|
2019-04-17 23:14:40 +08:00
|
|
|
|
|
|
|
|
|
cu.destroy!
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).not_to include("unwatch_category")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "correctly handles watched first post categories" do
|
|
|
|
|
cu = create_category_user(:watching_first_post)
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).to include("unwatch_category")
|
|
|
|
|
|
|
|
|
|
cu.destroy!
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
expect(response.body).not_to include("unwatch_category")
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-16 18:47:01 +08:00
|
|
|
|
it "displays form even if topic is deleted" do
|
|
|
|
|
post.topic.trash!
|
|
|
|
|
|
|
|
|
|
navigate_to_unsubscribe
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.body).to include(I18n.t("unsubscribe.all", sitename: SiteSetting.title))
|
|
|
|
|
end
|
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
def create_category_user(notification_level)
|
|
|
|
|
CategoryUser.create!(
|
|
|
|
|
user_id: user.id,
|
|
|
|
|
category_id: post.topic.category_id,
|
|
|
|
|
notification_level: CategoryUser.notification_levels[notification_level],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-06-05 10:57:11 +08:00
|
|
|
|
|
2019-04-17 23:14:40 +08:00
|
|
|
|
def navigate_to_unsubscribe(key = unsubscribe_key)
|
2018-06-05 10:57:11 +08:00
|
|
|
|
get "/email/unsubscribe/#{key}"
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-10-09 09:04:46 +08:00
|
|
|
|
end
|