Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
320e7ea246 |
+1
-1
@@ -561,7 +561,7 @@ GEM
|
|||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.12)
|
rack (2.2.11)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-contrib (2.5.0)
|
rack-contrib (2.5.0)
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
module FacebookConcern
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
class InvalidDigestError < StandardError; end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def deletion_processed?(code)
|
|
||||||
request = DeleteRequest.find_by!(confirmation_code: code)
|
|
||||||
request.complete?
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_fb_signed_request(signed_request)
|
|
||||||
encoded_signature, payload = signed_request.split('.', 2)
|
|
||||||
|
|
||||||
decoded_signature = Base64.urlsafe_decode64(encoded_signature)
|
|
||||||
decoded_payload = JSON.parse(Base64.urlsafe_decode64(payload))
|
|
||||||
|
|
||||||
expected_signature = OpenSSL::HMAC.digest('sha256', app_secret, payload)
|
|
||||||
|
|
||||||
raise InvalidDigestError if decoded_signature != expected_signature
|
|
||||||
|
|
||||||
decoded_payload
|
|
||||||
end
|
|
||||||
|
|
||||||
def app_url_base
|
|
||||||
ENV.fetch('FRONTEND_URL', nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def app_secret
|
|
||||||
GlobalConfigService.load('FB_APP_SECRET', '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
class Facebook::ConfirmController < ApplicationController
|
|
||||||
include FacebookConcern
|
|
||||||
|
|
||||||
def show
|
|
||||||
if deletion_processed?(params[:id])
|
|
||||||
render plain: 'Data Deleted Successfully', status: :ok
|
|
||||||
else
|
|
||||||
render plain: 'Processing. If there is an issue, please contact support', status: :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
class Facebook::DeleteController < ApplicationController
|
|
||||||
include FacebookConcern
|
|
||||||
|
|
||||||
def create
|
|
||||||
signed_request = params['signed_request']
|
|
||||||
payload = parse_fb_signed_request(signed_request)
|
|
||||||
id_to_process = payload['user_id']
|
|
||||||
|
|
||||||
delete_request = DeleteRequest.create(fb_id: id_to_process)
|
|
||||||
status_url = "#{app_url_base}/facebook/confirm/#{delete_request.confirmation_code}"
|
|
||||||
|
|
||||||
# IMPORTANT: Do not change the response format below.
|
|
||||||
# Facebook's Data Deletion Request system specifically expects responses in this format
|
|
||||||
# with a 'url' for status confirmation and a 'confirmation_code' field.
|
|
||||||
# See: https://developers.facebook.com/docs/development/create-an-app/app-dashboard/data-deletion-callback/#implementing
|
|
||||||
render json: { url: status_url, confirmation_code: delete_request.confirmation_code }, status: :ok
|
|
||||||
rescue InvalidDigestError
|
|
||||||
render json: { error: 'Invalid signature' }, status: :unprocessable_entity
|
|
||||||
rescue StandardError => e
|
|
||||||
ChatwootExceptionTracker.new(e).capture_exception
|
|
||||||
render json: { error: e.message }, status: :malformed_request
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -4,7 +4,6 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
|
|||||||
import LoadingState from './components/widgets/LoadingState.vue';
|
import LoadingState from './components/widgets/LoadingState.vue';
|
||||||
import NetworkNotification from './components/NetworkNotification.vue';
|
import NetworkNotification from './components/NetworkNotification.vue';
|
||||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||||
import UpgradeBanner from './components/app/UpgradeBanner.vue';
|
|
||||||
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||||
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
|
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
|
||||||
import vueActionCable from './helper/actionCable';
|
import vueActionCable from './helper/actionCable';
|
||||||
@@ -31,7 +30,6 @@ export default {
|
|||||||
UpdateBanner,
|
UpdateBanner,
|
||||||
PaymentPendingBanner,
|
PaymentPendingBanner,
|
||||||
WootSnackbarBox,
|
WootSnackbarBox,
|
||||||
UpgradeBanner,
|
|
||||||
PendingEmailVerificationBanner,
|
PendingEmailVerificationBanner,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
@@ -146,7 +144,6 @@ export default {
|
|||||||
<template v-if="currentAccountId">
|
<template v-if="currentAccountId">
|
||||||
<PendingEmailVerificationBanner v-if="hideOnOnboardingView" />
|
<PendingEmailVerificationBanner v-if="hideOnOnboardingView" />
|
||||||
<PaymentPendingBanner v-if="hideOnOnboardingView" />
|
<PaymentPendingBanner v-if="hideOnOnboardingView" />
|
||||||
<UpgradeBanner />
|
|
||||||
</template>
|
</template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
|
import { differenceInDays } from 'date-fns';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const ALWAYS_ON_ROUTES = [
|
||||||
|
'agent_list',
|
||||||
|
'settings_inbox_list',
|
||||||
|
'billing_settings_index',
|
||||||
|
];
|
||||||
|
|
||||||
|
const { accountId } = useAccount();
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const routeName = computed(() => {
|
||||||
|
return route.name || '';
|
||||||
|
});
|
||||||
|
const isOnChatwootCloud = computed(
|
||||||
|
() => store.getters['globalConfig/isOnChatwootCloud']
|
||||||
|
);
|
||||||
|
const account = computed(() =>
|
||||||
|
store.getters['accounts/getAccount'](accountId.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const isTrialAccount = computed(() => {
|
||||||
|
if (!account.value) return false;
|
||||||
|
|
||||||
|
const createdAt = new Date(account.value.created_at);
|
||||||
|
const diffDays = differenceInDays(new Date(), createdAt);
|
||||||
|
|
||||||
|
return diffDays <= 15;
|
||||||
|
});
|
||||||
|
|
||||||
|
const testLimit = ({ allowed, consumed }) => {
|
||||||
|
return consumed > allowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLimitExceeded = computed(() => {
|
||||||
|
if (ALWAYS_ON_ROUTES.includes(routeName.value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.value) return false;
|
||||||
|
|
||||||
|
const { limits } = account.value;
|
||||||
|
if (!limits) return false;
|
||||||
|
|
||||||
|
const { conversation, non_web_inboxes: nonWebInboxes } = limits;
|
||||||
|
return testLimit(conversation) || testLimit(nonWebInboxes);
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldShowBanner = computed(() => {
|
||||||
|
if (!isOnChatwootCloud.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTrialAccount.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLimitExceeded.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchLimits = () => store.dispatch('accounts/limits');
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (isOnChatwootCloud.value) {
|
||||||
|
fetchLimits();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="shouldShowBanner">Limits exceeded</div>
|
||||||
|
|
||||||
|
<slot v-else />
|
||||||
|
</template>
|
||||||
@@ -9,7 +9,7 @@ import AddAccountModal from 'dashboard/components/layout/sidebarComponents/AddAc
|
|||||||
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
|
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
|
||||||
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
||||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
||||||
|
import PaymentPaywall from 'dashboard/components/app/PaymentPaywall.vue';
|
||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ export default {
|
|||||||
CommandBar,
|
CommandBar,
|
||||||
WootKeyShortcutModal,
|
WootKeyShortcutModal,
|
||||||
AddAccountModal,
|
AddAccountModal,
|
||||||
|
PaymentPaywall,
|
||||||
AccountSelector,
|
AccountSelector,
|
||||||
AddLabelModal,
|
AddLabelModal,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
@@ -194,7 +195,9 @@ export default {
|
|||||||
@show-add-label-popup="showAddLabelPopup"
|
@show-add-label-popup="showAddLabelPopup"
|
||||||
/>
|
/>
|
||||||
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
||||||
<router-view />
|
<PaymentPaywall>
|
||||||
|
<router-view />
|
||||||
|
</PaymentPaywall>
|
||||||
<CommandBar />
|
<CommandBar />
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
:show-account-modal="showAccountModal"
|
:show-account-modal="showAccountModal"
|
||||||
|
|||||||
@@ -39,14 +39,13 @@ export default {
|
|||||||
},
|
},
|
||||||
mixins: [globalConfigMixin],
|
mixins: [globalConfigMixin],
|
||||||
setup() {
|
setup() {
|
||||||
const { isEditorHotKeyEnabled, updateUISettings } = useUISettings();
|
const { isEditorHotKeyEnabled } = useUISettings();
|
||||||
const { currentFontSize, updateFontSize } = useFontSize();
|
const { currentFontSize, updateFontSize } = useFontSize();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentFontSize,
|
currentFontSize,
|
||||||
updateFontSize,
|
updateFontSize,
|
||||||
isEditorHotKeyEnabled,
|
isEditorHotKeyEnabled,
|
||||||
updateUISettings,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const state = {
|
|||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getAccount: $state => id => {
|
getAccount: $state => id => {
|
||||||
|
console.log('account', id);
|
||||||
return findRecordById($state, id);
|
return findRecordById($state, id);
|
||||||
},
|
},
|
||||||
getUIFlags($state) {
|
getUIFlags($state) {
|
||||||
|
|||||||
@@ -2,15 +2,8 @@ class Inboxes::FetchImapEmailInboxesJob < ApplicationJob
|
|||||||
queue_as :scheduled_jobs
|
queue_as :scheduled_jobs
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
email_inboxes = Inbox.where(channel_type: 'Channel::Email')
|
Inbox.where(channel_type: 'Channel::Email').all.find_each(batch_size: 100) do |inbox|
|
||||||
email_inboxes.find_each(batch_size: 100) do |inbox|
|
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if inbox.channel.imap_enabled
|
||||||
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if should_fetch_emails?(inbox)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def should_fetch_emails?(inbox)
|
|
||||||
inbox.channel.imap_enabled && !inbox.account.suspended?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# == Schema Information
|
|
||||||
#
|
|
||||||
# Table name: delete_requests
|
|
||||||
#
|
|
||||||
# id :bigint not null, primary key
|
|
||||||
# completed_at :datetime
|
|
||||||
# confirmation_code :string not null
|
|
||||||
# deleted_type :string
|
|
||||||
# status :string not null
|
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
# account_id :bigint
|
|
||||||
# deleted_id :integer
|
|
||||||
# fb_id :string not null
|
|
||||||
#
|
|
||||||
# Indexes
|
|
||||||
#
|
|
||||||
# index_delete_requests_on_account_id (account_id)
|
|
||||||
# index_delete_requests_on_confirmation_code (confirmation_code) UNIQUE
|
|
||||||
#
|
|
||||||
class DeleteRequest < ApplicationRecord
|
|
||||||
belongs_to :account, optional: true
|
|
||||||
enum :status, %w[pending complete].index_by(&:itself), default: :pending
|
|
||||||
|
|
||||||
before_create :ensure_unqiue_confirmation_code
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ensure_unqiue_confirmation_code
|
|
||||||
max_retries = 3
|
|
||||||
retry_count = 0
|
|
||||||
|
|
||||||
begin
|
|
||||||
self.confirmation_code = generate_confirmation_code
|
|
||||||
raise ActiveRecord::RecordNotUnique if self.class.exists?(confirmation_code: confirmation_code)
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
|
||||||
retry_count += 1
|
|
||||||
if retry_count > max_retries
|
|
||||||
# it's really really unlikely that we'll ever ever hit this case, but just in case
|
|
||||||
self.confirmation_code = generate_confirmation_code + SecureRandom.alphanumeric(3)
|
|
||||||
else
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_confirmation_code
|
|
||||||
SecureRandom.uuid.delete('-')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -453,11 +453,6 @@ Rails.application.routes.draw do
|
|||||||
resource :callback, only: [:show]
|
resource :callback, only: [:show]
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :facebook do
|
|
||||||
resources :delete, only: [:create]
|
|
||||||
resources :confirm, only: [:show]
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :linear do
|
namespace :linear do
|
||||||
resource :callback, only: [:show]
|
resource :callback, only: [:show]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
class CreateDeleteRequests < ActiveRecord::Migration[7.0]
|
|
||||||
def change
|
|
||||||
create_table :delete_requests do |t|
|
|
||||||
t.string :fb_id, null: false
|
|
||||||
t.string :status, null: false
|
|
||||||
t.string :confirmation_code, null: false
|
|
||||||
t.datetime :completed_at
|
|
||||||
t.string :deleted_type
|
|
||||||
t.integer :deleted_id
|
|
||||||
t.references :account, index: true, null: true
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
|
|
||||||
add_index :delete_requests, :confirmation_code, unique: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
+1
-15
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2025_03_06_081627) do
|
ActiveRecord::Schema[7.0].define(version: 2025_02_28_185548) do
|
||||||
# These extensions should be enabled to support this database
|
# These extensions should be enabled to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@@ -639,20 +639,6 @@ ActiveRecord::Schema[7.0].define(version: 2025_03_06_081627) do
|
|||||||
t.index ["account_id"], name: "index_data_imports_on_account_id"
|
t.index ["account_id"], name: "index_data_imports_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "delete_requests", force: :cascade do |t|
|
|
||||||
t.string "fb_id", null: false
|
|
||||||
t.string "status", null: false
|
|
||||||
t.string "confirmation_code", null: false
|
|
||||||
t.datetime "completed_at"
|
|
||||||
t.string "deleted_type"
|
|
||||||
t.integer "deleted_id"
|
|
||||||
t.bigint "account_id"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["account_id"], name: "index_delete_requests_on_account_id"
|
|
||||||
t.index ["confirmation_code"], name: "index_delete_requests_on_confirmation_code", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "email_templates", force: :cascade do |t|
|
create_table "email_templates", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.text "body", null: false
|
t.text "body", null: false
|
||||||
|
|||||||
@@ -41,7 +41,4 @@ module Redis::RedisKeys
|
|||||||
IG_MESSAGE_MUTEX = 'IG_MESSAGE_CREATE_LOCK::%<sender_id>s::%<ig_account_id>s'.freeze
|
IG_MESSAGE_MUTEX = 'IG_MESSAGE_CREATE_LOCK::%<sender_id>s::%<ig_account_id>s'.freeze
|
||||||
SLACK_MESSAGE_MUTEX = 'SLACK_MESSAGE_LOCK::%<conversation_id>s::%<reference_id>s'.freeze
|
SLACK_MESSAGE_MUTEX = 'SLACK_MESSAGE_LOCK::%<conversation_id>s::%<reference_id>s'.freeze
|
||||||
EMAIL_MESSAGE_MUTEX = 'EMAIL_CHANNEL_LOCK::%<inbox_id>s'.freeze
|
EMAIL_MESSAGE_MUTEX = 'EMAIL_CHANNEL_LOCK::%<inbox_id>s'.freeze
|
||||||
|
|
||||||
## Meta deletion flags
|
|
||||||
META_DELETE_PROCESSING = 'META_DELETE_PROCESSING::%<id>s'.freeze
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Facebook::ConfirmController do
|
|
||||||
describe 'GET #show' do
|
|
||||||
let(:user_id) { '12345' }
|
|
||||||
|
|
||||||
context 'when deletion is in progress' do
|
|
||||||
before do
|
|
||||||
redis_key = format(Redis::Alfred::META_DELETE_PROCESSING, id: user_id)
|
|
||||||
allow(Redis::Alfred).to receive(:get).with(redis_key).and_return('true')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns processing status' do
|
|
||||||
get :show, params: { id: user_id }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok)
|
|
||||||
expect(response.body).to eq('Processing')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when deletion is completed' do
|
|
||||||
before do
|
|
||||||
redis_key = format(Redis::Alfred::META_DELETE_PROCESSING, id: user_id)
|
|
||||||
allow(Redis::Alfred).to receive(:get).with(redis_key).and_return(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns success message' do
|
|
||||||
get :show, params: { id: user_id }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok)
|
|
||||||
expect(response.body).to eq('Data Deleted Successfully')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Facebook::DeleteController do
|
|
||||||
describe 'POST #create' do
|
|
||||||
let(:user_id) { '12345' }
|
|
||||||
let(:app_secret) { 'test_app_secret' }
|
|
||||||
let(:frontend_url) { ENV.fetch('FRONTEND_URL', nil) }
|
|
||||||
let(:redis_key) { format(Redis::Alfred::META_DELETE_PROCESSING, id: user_id) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(GlobalConfigService).to receive(:load).with('FB_APP_SECRET', '').and_return(app_secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with valid signed request' do
|
|
||||||
let(:payload) { { 'user_id' => user_id }.to_json }
|
|
||||||
let(:encoded_payload) { Base64.urlsafe_encode64(payload) }
|
|
||||||
let(:signature) { OpenSSL::HMAC.digest('sha256', app_secret, encoded_payload) }
|
|
||||||
let(:encoded_signature) { Base64.urlsafe_encode64(signature) }
|
|
||||||
let(:signed_request) { "#{encoded_signature}.#{encoded_payload}" }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(Redis::Alfred).to receive(:set)
|
|
||||||
allow(Channels::Facebook::RedactContactDataJob).to receive(:perform_later)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'processes the delete request and returns status URL' do
|
|
||||||
post :create, params: { signed_request: signed_request }
|
|
||||||
|
|
||||||
expect(Redis::Alfred).to have_received(:set).with(redis_key, true)
|
|
||||||
expect(Channels::Facebook::RedactContactDataJob).to have_received(:perform_later).with(user_id)
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok)
|
|
||||||
|
|
||||||
response_json = response.parsed_body
|
|
||||||
# NOTE: this is an important condition since this is exactly the format facebook expects
|
|
||||||
expect(response_json['url']).to eq("#{frontend_url}/facebook/confirm/#{user_id}")
|
|
||||||
expect(response_json['confirmation_code']).to eq(user_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with invalid signed request' do
|
|
||||||
let(:invalid_signed_request) { 'invalid_signature.invalid_payload' }
|
|
||||||
|
|
||||||
before do
|
|
||||||
# Mock the Base64.urlsafe_decode64 to return valid values for testing
|
|
||||||
allow(Base64).to receive(:urlsafe_decode64).with('invalid_signature').and_return('decoded_signature')
|
|
||||||
allow(Base64).to receive(:urlsafe_decode64).with('invalid_payload').and_return('{"user_id":"12345"}')
|
|
||||||
allow(JSON).to receive(:parse).with('{"user_id":"12345"}').and_return({ 'user_id' => user_id })
|
|
||||||
allow(OpenSSL::HMAC).to receive(:digest).and_return('different_signature')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns an error for invalid signature' do
|
|
||||||
post :create, params: { signed_request: invalid_signed_request }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:unprocessable_entity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -2,19 +2,11 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe Inboxes::FetchImapEmailInboxesJob do
|
RSpec.describe Inboxes::FetchImapEmailInboxesJob do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:suspended_account) { create(:account, status: 'suspended') }
|
|
||||||
|
|
||||||
let(:imap_email_channel) do
|
let(:imap_email_channel) do
|
||||||
create(:channel_email, imap_enabled: true, account: account)
|
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com', imap_port: 993, imap_login: 'imap@gmail.com',
|
||||||
end
|
imap_password: 'password', account: account)
|
||||||
|
|
||||||
let(:imap_email_channel_suspended) do
|
|
||||||
create(:channel_email, imap_enabled: true, account: suspended_account)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:disabled_imap_channel) do
|
|
||||||
create(:channel_email, imap_enabled: false, account: account)
|
|
||||||
end
|
end
|
||||||
|
let(:email_inbox) { create(:inbox, channel: imap_email_channel, account: account) }
|
||||||
|
|
||||||
it 'enqueues the job' do
|
it 'enqueues the job' do
|
||||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||||
@@ -22,26 +14,9 @@ RSpec.describe Inboxes::FetchImapEmailInboxesJob do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when called' do
|
context 'when called' do
|
||||||
it 'fetches emails only for active accounts with imap enabled' do
|
it 'fetch all the email channels' do
|
||||||
# Should call perform_later only once for the active, imap-enabled inbox
|
|
||||||
expect(Inboxes::FetchImapEmailsJob).to receive(:perform_later).with(imap_email_channel).once
|
expect(Inboxes::FetchImapEmailsJob).to receive(:perform_later).with(imap_email_channel).once
|
||||||
|
|
||||||
# Should not call for suspended account or disabled IMAP channels
|
|
||||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(imap_email_channel_suspended)
|
|
||||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(disabled_imap_channel)
|
|
||||||
|
|
||||||
described_class.perform_now
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'skips suspended accounts' do
|
|
||||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(imap_email_channel_suspended)
|
|
||||||
|
|
||||||
described_class.perform_now
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'skips disabled imap channels' do
|
|
||||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(disabled_imap_channel)
|
|
||||||
|
|
||||||
described_class.perform_now
|
described_class.perform_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user