Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/archive account #4350

Merged
merged 8 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .github/workflows/lint-migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ jobs:
run: |
modified_migrations=$(git diff --diff-filter=d --name-only main 'packages/db/migrations/*.do.*.sql')
echo "$modified_migrations"
echo "text<<EOF" >> $GITHUB_OUTPUT
echo "file_names=$modified_migrations" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
id: modified-migrations
- uses: sbdchd/squawk-action@v1
with:
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/entity/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum StatusType {
Active = 'ACTIVE',
Pending = 'PENDING',
Deleted = 'DELETED',
Archived = 'ARCHIVED',
}

@Entity()
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/jobs/email/inbound_emails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import { enqueueSendEmail } from '../../utils/createTask'
import { generateSlug, isUrl } from '../../utils/helpers'
import { logger } from '../../utils/logger'
import {
parseEmailAddress,
isProbablyArticle,
getTitleFromEmailSubject,
generateUniqueUrl,
getTitleFromEmailSubject,
isProbablyArticle,
parseEmailAddress,
} from '../../utils/parser'
import {
generateUploadFilePathName,
Expand Down
3 changes: 1 addition & 2 deletions packages/api/src/jobs/rss/refreshAllFeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@ export const refreshAllFeeds = async (db: DataSource): Promise<boolean> => {
FROM
omnivore.subscriptions s
INNER JOIN
omnivore.user u ON u.id = s.user_id
omnivore.user u ON u.id = s.user_id AND u.status = $4
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Optimized the query a little bit here

WHERE
s.type = $1
AND s.status = $2
AND (s.scheduled_at <= NOW() OR s.scheduled_at IS NULL)
AND u.status = $4
GROUP BY
url
`,
Expand Down
10 changes: 6 additions & 4 deletions packages/api/src/routers/auth/auth_router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
} from '../../utils/auth'
import { corsConfig } from '../../utils/corsConfig'
import { logger } from '../../utils/logger'
import { DEFAULT_HOME_PATH } from '../../utils/navigation'
import { ARCHIVE_ACCOUNT_PATH, DEFAULT_HOME_PATH } from '../../utils/navigation'
import { hourlyLimiter } from '../../utils/rate_limit'
import { verifyChallengeRecaptcha } from '../../utils/recaptcha'
import { createSsoToken, ssoRedirectURL } from '../../utils/sso'
Expand Down Expand Up @@ -378,9 +378,11 @@ export function authRouter() {
}
}

redirectUri = redirectUri
? redirectUri
: `${env.client.url}${DEFAULT_HOME_PATH}`
if (user.status === StatusType.Archived) {
redirectUri = `${env.client.url}${ARCHIVE_ACCOUNT_PATH}`
}

redirectUri = redirectUri ?? `${env.client.url}${DEFAULT_HOME_PATH}`

const message = res.get('Message')
if (message) {
Expand Down
20 changes: 11 additions & 9 deletions packages/api/src/routers/auth/google_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { env, homePageURL } from '../../env'
import { LoginErrorCode } from '../../generated/graphql'
import { userRepository } from '../../repository/user'
import { logger } from '../../utils/logger'
import { ARCHIVE_ACCOUNT_PATH, DEFAULT_HOME_PATH } from '../../utils/navigation'
import { createSsoToken, ssoRedirectURL } from '../../utils/sso'
import { DecodeTokenResult } from './auth_types'
import { createPendingUserToken, createWebAuthToken } from './jwt_helpers'
import { DEFAULT_HOME_PATH } from '../../utils/navigation'

export const googleAuthMobile = (): OAuth2Client =>
new google.auth.OAuth2(env.google.auth.clientId, env.google.auth.secret)
Expand Down Expand Up @@ -132,7 +132,6 @@ export async function handleGoogleWebAuth(
const user = await userRepository.findOneBy({
email,
source: 'GOOGLE',
status: StatusType.Active,
})
const userId = user?.id

Expand All @@ -158,15 +157,18 @@ export async function handleGoogleWebAuth(
}
}

let redirectURL = `${baseURL()}${
user.status === StatusType.Archived
? ARCHIVE_ACCOUNT_PATH
: DEFAULT_HOME_PATH
}`

const authToken = await createWebAuthToken(userId)
if (authToken) {
const ssoToken = createSsoToken(
authToken,
`${baseURL()}${DEFAULT_HOME_PATH}`
)
const redirectURL = isVercel
? ssoRedirectURL(ssoToken)
: `${baseURL()}${DEFAULT_HOME_PATH}`
if (isVercel) {
const ssoToken = createSsoToken(authToken, redirectURL)
redirectURL = ssoRedirectURL(ssoToken)
}

return {
authToken,
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/routers/export_router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import express, { Router } from 'express'
import { TaskState } from '../generated/graphql'
import { jobStateToTaskState } from '../queue-processor'
import { countExportsWithin24Hours, saveExport } from '../services/export'
import { sendExportJobEmail } from '../services/send_emails'
import { getClaimsByToken, getTokenByRequest } from '../utils/auth'
import { corsConfig } from '../utils/corsConfig'
import { queueExportJob } from '../utils/createTask'
Expand Down
6 changes: 5 additions & 1 deletion packages/api/src/services/library_item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,11 @@ export const updateLibraryItemReadingProgress = async (
}

const updatedItem = result[0][0]
await pubsub.entityUpdated<ItemEvent>(EntityType.ITEM, updatedItem, userId)
const readingProgress = updatedItem.readingProgressBottomPercent
if (readingProgress === 0 || readingProgress === 100) {
// only send PAGE_UPDATED event if users mark item as read or unread
await pubsub.entityUpdated<ItemEvent>(EntityType.ITEM, updatedItem, userId)
}

return updatedItem
}
Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/services/newsletters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { nanoid } from 'nanoid'
import { NewsletterEmail } from '../entity/newsletter_email'
import { StatusType } from '../entity/user'
import { env } from '../env'
import {
CreateNewsletterEmailErrorCode,
Expand Down Expand Up @@ -91,7 +92,12 @@ export const findNewsletterEmailByAddress = async (
const address = parsedAddress(emailAddress)
return getRepository(NewsletterEmail)
.createQueryBuilder('newsletter_email')
.innerJoinAndSelect('newsletter_email.user', 'user')
.innerJoinAndSelect(
'newsletter_email.user',
'user',
'user.status = :status',
{ status: StatusType.Active }
)
.where('LOWER(address) = :address', { address: address.toLowerCase() })
.getOne()
}
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/services/rules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArrayContains, ILike, IsNull, Not } from 'typeorm'
import { Rule, RuleAction, RuleEventType } from '../entity/rule'
import { StatusType } from '../entity/user'
import { authTrx, getRepository } from '../repository'

export const createRule = async (
Expand Down Expand Up @@ -62,7 +63,7 @@ export const findEnabledRules = async (
eventType: RuleEventType
) => {
return getRepository(Rule).findBy({
user: { id: userId },
user: { id: userId, status: StatusType.Active },
enabled: true,
eventTypes: ArrayContains([eventType]),
failedAt: IsNull(), // only rules that have not failed
Expand Down
38 changes: 10 additions & 28 deletions packages/api/src/services/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,38 +81,20 @@ export const createUsers = async (users: DeepPartial<User>[]) => {

export const batchDelete = async (criteria: FindOptionsWhere<User>) => {
const userQb = getRepository(User).createQueryBuilder().where(criteria)
const userCountSql = queryBuilderToRawSql(userQb.select('COUNT(1)'))
const userSubQuery = queryBuilderToRawSql(
userQb.select('array_agg(id::UUID) into user_ids')
)
const batchSize = 100
const userSubQuery = queryBuilderToRawSql(userQb.select('id').take(batchSize))

const batchSize = 1000
const sql = `
-- Set batch size
DO $$
DECLARE
batch_size INT := ${batchSize};
user_ids UUID[];
BEGIN
-- Loop through batches of users
FOR i IN 0..CEIL((${userCountSql}) * 1.0 / batch_size) - 1 LOOP
-- GET batch of user ids
${userSubQuery} LIMIT batch_size;

-- Loop through batches of items
FOR j IN 0..CEIL((SELECT COUNT(1) FROM omnivore.library_item WHERE user_id = ANY(user_ids)) * 1.0 / batch_size) - 1 LOOP
-- Delete batch of items
DELETE FROM omnivore.library_item
WHERE id = ANY(
SELECT id
FROM omnivore.library_item
WHERE user_id = ANY(user_ids)
LIMIT batch_size
);
END LOOP;

-- Delete the batch of users
DELETE FROM omnivore.user WHERE id = ANY(user_ids);
LOOP
DELETE FROM omnivore.user
WHERE id IN (${userSubQuery});

EXIT WHEN NOT FOUND;

-- Avoid overwhelming the server
PERFORM pg_sleep(0.1);
END LOOP;
END $$
`
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/utils/navigation.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const DEFAULT_HOME_PATH = '/home'
export const ARCHIVE_ACCOUNT_PATH = '/account-archived'
28 changes: 9 additions & 19 deletions packages/api/test/resolvers/newsletters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Newsletters API', () => {
.post('/local/debug/fake-user-login')
.send({ fakeEmail: user.email })

authToken = res.body.authToken
authToken = res.body.authToken as string
})

after(async () => {
Expand Down Expand Up @@ -65,14 +65,8 @@ describe('Newsletters API', () => {

before(async () => {
// create test newsletter emails
const newsletterEmail1 = await createNewsletterEmail(
user.id,
'[email protected]'
)
const newsletterEmail2 = await createNewsletterEmail(
user.id,
'[email protected]'
)
const newsletterEmail1 = await createNewsletterEmail(user.id)
const newsletterEmail2 = await createNewsletterEmail(user.id)
newsletterEmails = [newsletterEmail1, newsletterEmail2]

// create testing subscriptions
Expand All @@ -89,7 +83,9 @@ describe('Newsletters API', () => {
it('responds with newsletter emails sort by created_at desc', async () => {
const response = await graphqlRequest(query, authToken).expect(200)
expect(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
response.body.data.newsletterEmails.newsletterEmails.map((e: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
...e,
createdAt:
Expand Down Expand Up @@ -124,10 +120,7 @@ describe('Newsletters API', () => {

before(async () => {
// create test newsletter emails
newsletterEmail = await createNewsletterEmail(
user.id,
'[email protected]'
)
newsletterEmail = await createNewsletterEmail(user.id)

// create unsubscribed subscriptions
await createSubscription(
Expand Down Expand Up @@ -190,7 +183,7 @@ describe('Newsletters API', () => {
const response = await graphqlRequest(query, authToken, {
input: {
folder,
}
},
}).expect(200)
const newsletterEmail = await findNewsletterEmailById(
response.body.data.createNewsletterEmail.newsletterEmail.id
Expand Down Expand Up @@ -239,10 +232,7 @@ describe('Newsletters API', () => {
context('when newsletter email exists', () => {
before(async () => {
// create test newsletter emails
const newsletterEmail = await createNewsletterEmail(
user.id,
'[email protected]'
)
const newsletterEmail = await createNewsletterEmail(user.id)
newsletterEmailId = newsletterEmail.id
})

Expand All @@ -254,7 +244,7 @@ describe('Newsletters API', () => {
it('responds with status code 200', async () => {
const response = await graphqlRequest(query, authToken).expect(200)
const newsletterEmail = await findNewsletterEmailByAddress(
response.body.data.deleteNewsletterEmail.newsletterEmail.id
response.body.data.deleteNewsletterEmail.newsletterEmail.address
)
expect(newsletterEmail).to.be.null
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Type: DO
-- Name: allow_admin_to_delete_filters
-- Description: Add permissions to delete data from filters table to the omnivore_admin role

BEGIN;

GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.filters TO omnivore_admin;

CREATE POLICY filters_admin_policy on omnivore.filters
FOR ALL
TO omnivore_admin
USING (true);

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Type: UNDO
-- Name: allow_admin_to_delete_filters
-- Description: Add permissions to delete data from filters table to the omnivore_admin role

BEGIN;

DROP POLICY filters_admin_policy on omnivore.filters;

REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.filters FROM omnivore_admin;

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Type: DO
-- Name: add_archived_status_to_user
-- Description: Add ARCHIVED status to the user table

BEGIN;

ALTER TYPE user_status_type ADD VALUE IF NOT EXISTS 'ARCHIVED';

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Type: UNDO
-- Name: add_archived_status_to_user
-- Description: Add ARCHIVED status to the user table

BEGIN;

ALTER TYPE user_status_type DROP VALUE IF EXISTS 'ARCHIVED';

COMMIT;
Loading