save claim in httpContext

This commit is contained in:
Hongbo Wu
2023-09-04 13:06:51 +08:00
parent b1899e340d
commit f8f3ec0c56
32 changed files with 515 additions and 729 deletions

View File

@ -9,6 +9,7 @@ import * as Sentry from '@sentry/node'
import { ContextFunction } from 'apollo-server-core'
import { ApolloServer } from 'apollo-server-express'
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer'
import * as httpContext from 'express-http-context2'
import * as jwt from 'jsonwebtoken'
import { EntityManager } from 'typeorm'
import { promisify } from 'util'
@ -45,6 +46,8 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
const token = req?.cookies?.auth || req?.headers?.authorization
const claims = await getClaimsByToken(token)
httpContext.set('claims', claims)
async function setClaims(
em: EntityManager,
uuid?: string,

View File

@ -1,72 +0,0 @@
// Table "omnivore.links"
// Column | Type | Collation | Nullable | Default
// ---------------------------------------+--------------------------+-----------+----------+----------------------
// article_url | text | | not null |
// article_hash | text | | not null |
// created_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP
// shared_comment | text | | |
// article_reading_progress | real | | not null | 0
// article_reading_progress_anchor_index | integer | | not null | 0
// shared_with_highlights | boolean | | | false
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
JoinTable,
ManyToMany,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
import { User } from './user'
import { Page } from './page'
import { Label } from './label'
@Entity({ name: 'links' })
export class Link {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column('text')
slug!: string
@OneToOne(() => User)
@JoinColumn({ name: 'user_id' })
user!: User
@OneToOne(() => Page)
@JoinColumn({ name: 'article_id' })
page!: Page
@Column('timestamp')
savedAt!: Date
@Column('timestamp')
sharedAt!: Date | null
@Column('timestamp')
archivedAt?: Date | null
@Column('text')
articleUrl!: string
@Column('text')
articleHash!: string
@CreateDateColumn()
createdAt?: Date
@UpdateDateColumn()
updatedAt?: Date
@ManyToMany(() => Label)
@JoinTable({
name: 'link_labels',
joinColumn: { name: 'link_id' },
inverseJoinColumn: { name: 'label_id' },
})
labels?: Label[]
}

View File

@ -1,52 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
@Entity({ name: 'pages' })
export class Page {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column('text')
url!: string
@Column('text')
hash!: string
@Column('text')
title!: string
@Column('text', { nullable: true })
uploadFileId!: string
@Column('text', { nullable: true })
author!: string
@Column('text', { nullable: true })
description!: string
@Column('text', { nullable: true })
image!: string
@Column('text')
content!: string
@Column('text', { name: 'page_type' })
type!: string
@Column('text', { nullable: true })
originalHtml!: string
@Column('timestamp')
publishedAt?: Date
@CreateDateColumn()
createdAt?: Date
@UpdateDateColumn()
updatedAt?: Date
}

View File

@ -13,7 +13,7 @@ export class AbuseReport {
id?: string
@Column('text')
pageId?: string
libraryItemId?: string
@Column('text')
sharedBy!: string
@ -35,7 +35,4 @@ export class AbuseReport {
@UpdateDateColumn()
updatedAt?: Date
@Column('text')
elasticPageId?: string
}

View File

@ -19,7 +19,7 @@ export class ContentDisplayReport {
user!: User
@Column('text')
pageId?: string
libraryItemId?: string
@Column('text')
content!: string
@ -38,7 +38,4 @@ export class ContentDisplayReport {
@UpdateDateColumn()
updatedAt?: Date
@Column('text')
elasticPageId?: string
}

View File

@ -1,4 +0,0 @@
import { entityManager } from '.'
import { ApiKey } from '../entity/api_key'
export const apiKeyRepository = entityManager.getRepository(ApiKey)

View File

@ -0,0 +1,4 @@
import { entityManager } from '.'
import { Group } from '../entity/groups/group'
export const groupRepository = entityManager.getRepository(Group)

View File

@ -1,5 +1,7 @@
import * as httpContext from 'express-http-context2'
import { EntityManager } from 'typeorm'
import { appDataSource } from '../data_source'
import { Claims } from '../resolvers/types'
export const setClaims = async (
manager: EntityManager,
@ -14,11 +16,19 @@ export const setClaims = async (
export const authTrx = async <T>(
fn: (manager: EntityManager) => Promise<T>,
uid = '00000000-0000-0000-0000-000000000000',
dbRole = 'omnivore_user'
em = entityManager,
uid?: string,
userRole?: string
): Promise<T> => {
return entityManager.transaction(async (tx) => {
await setClaims(tx, uid, dbRole)
// if uid and dbRole are not passed in, then get them from the claims
if (!uid && !userRole) {
const claims: Claims | undefined = httpContext.get('claims')
uid = claims?.uid
userRole = claims?.userRole
}
return em.transaction(async (tx) => {
await setClaims(tx, uid, userRole)
return fn(tx)
})
}

View File

@ -1,10 +0,0 @@
import { entityManager } from '.'
import { UploadFile } from '../entity/upload_file'
export const uploadFileRepository = entityManager
.getRepository(UploadFile)
.extend({
findById(id: string) {
return this.findOneBy({ id })
},
})

View File

@ -23,7 +23,6 @@ import {
UpdateFilterSuccess,
UpdateFilterErrorCode,
} from '../../generated/graphql'
import { entityManager, getRepository, setClaims } from '../../repository'
import { analytics } from '../../utils/analytics'
import { env } from '../../env'
import { isNil, mergeWith } from 'lodash'
@ -44,36 +43,24 @@ export const saveFilterResolver = authorized<
})
try {
const user = await getRepository(User).findOneBy({ id: uid })
if (!user) {
return {
errorCodes: [SaveFilterErrorCode.Unauthorized],
}
}
const filter = await getRepository(Filter).save({
user: { id: uid },
name: input.name,
category: 'Search',
description: '',
position: input.position ?? 0,
filter: input.filter,
defaultFilter: false,
visible: true,
const filter = await authTrx(async (t) => {
return t.withRepository(filterRepository).save({
user: { id: uid },
name: input.name,
category: 'Search',
description: '',
position: input.position ?? 0,
filter: input.filter,
defaultFilter: false,
visible: true,
})
})
return {
filter,
}
} catch (error) {
log.error('Error saving filters', {
error,
labels: {
source: 'resolver',
resolver: 'saveFilterResolver',
uid,
},
})
log.error('Error saving filters', error)
return {
errorCodes: [SaveFilterErrorCode.BadRequest],
@ -85,7 +72,7 @@ export const deleteFilterResolver = authorized<
DeleteFilterSuccess,
DeleteFilterError,
MutationDeleteFilterArgs
>(async (_, { id }, { claims, log }) => {
>(async (_, { id }, { authTrx, uid, log }) => {
log.info('Deleting filters', {
id,
labels: {
@ -96,37 +83,16 @@ export const deleteFilterResolver = authorized<
})
try {
const user = await getRepository(User).findOneBy({ id: claims.uid })
if (!user) {
return {
errorCodes: [DeleteFilterErrorCode.Unauthorized],
}
}
const filter = await getRepository(Filter).findOneBy({
id,
user: { id: claims.uid },
})
if (!filter) {
return {
errorCodes: [DeleteFilterErrorCode.NotFound],
}
}
await getRepository(Filter).delete({ id })
const filter = await authTrx(async (t) => {
const filter = await t.withRepository(filterRepository).findOne({
return {
filter,
}
} catch (error) {
log.error('Error deleting filters', {
error,
labels: {
source: 'resolver',
resolver: 'deleteFilterResolver',
uid: claims.uid,
},
})
log.error('Error deleting filters',
error
)
return {
errorCodes: [DeleteFilterErrorCode.BadRequest],

View File

@ -11,7 +11,7 @@ import {
saveNewsletter,
} from '../../services/save_newsletter_email'
import { saveUrlFromEmail } from '../../services/save_url'
import { getSubscriptionByNameAndUserId } from '../../services/subscriptions'
import { getSubscriptionByName } from '../../services/subscriptions'
import { isUrl } from '../../utils/helpers'
import { logger } from '../../utils/logger'
@ -127,7 +127,7 @@ export function newsletterServiceRouter() {
}
} else {
// do not subscribe if subscription already exists and is unsubscribed
const existingSubscription = await getSubscriptionByNameAndUserId(
const existingSubscription = await getSubscriptionByName(
data.author,
newsletterEmail.user.id
)

View File

@ -1,11 +1,11 @@
import { EntityManager } from 'typeorm'
import { appDataSource } from '../data_source'
import { GroupMembership } from '../entity/groups/group_membership'
import { Invite } from '../entity/groups/invite'
import { Profile } from '../entity/profile'
import { StatusType, User } from '../entity/user'
import { SignupErrorCode } from '../generated/graphql'
import { getRepository } from '../repository'
import { authTrx, entityManager } from '../repository'
import { profileRepository } from '../repository/profile'
import { userRepository } from '../repository/user'
import { AuthProvider } from '../routers/auth/auth_types'
import { logger } from '../utils/logger'
@ -48,7 +48,7 @@ export const createUser = async (input: {
}
// create profile if user exists but profile does not exist
const profile = await getRepository(Profile).save({
const profile = await profileRepository.save({
username: input.username,
pictureUrl: input.pictureUrl,
bio: input.bio,
@ -72,7 +72,7 @@ export const createUser = async (input: {
return Promise.reject({ errorCode: SignupErrorCode.InvalidUsername })
}
const [user, profile] = await appDataSource.transaction<[User, Profile]>(
const [user, profile] = await entityManager.transaction<[User, Profile]>(
async (t) => {
let hasInvite = false
let invite: Invite | null = null
@ -168,8 +168,11 @@ const validateInvite = async (
logger.info('rejecting invite, expired', invite)
return false
}
const membershipRepo = entityManager.getRepository(GroupMembership)
const numMembers = await membershipRepo.countBy({ invite: { id: invite.id } })
const numMembers = await authTrx(
(t) =>
t.getRepository(GroupMembership).countBy({ invite: { id: invite.id } }),
entityManager
)
if (numMembers >= invite.maxMembers) {
logger.info('rejecting invite, too many users', invite, numMembers)
return false

View File

@ -1,9 +1,8 @@
import * as jwt from 'jsonwebtoken'
import { IsNull, Not } from 'typeorm'
import { appDataSource } from '../data_source'
import { Feature } from '../entity/feature'
import { env } from '../env'
import { getRepository } from '../repository'
import { authTrx, entityManager } from '../repository'
import { logger } from '../utils/logger'
export enum FeatureName {
@ -26,14 +25,15 @@ export const optInFeature = async (
}
const optInUltraRealisticVoice = async (uid: string): Promise<Feature> => {
const feature = await getRepository(Feature).findOne({
where: {
user: { id: uid },
name: FeatureName.UltraRealisticVoice,
grantedAt: Not(IsNull()),
},
relations: ['user'],
})
const feature = await authTrx((t) =>
t.getRepository(Feature).findOne({
where: {
name: FeatureName.UltraRealisticVoice,
grantedAt: Not(IsNull()),
},
relations: ['user'],
})
)
if (feature) {
// already opted in
logger.info('already opted in')
@ -42,7 +42,7 @@ const optInUltraRealisticVoice = async (uid: string): Promise<Feature> => {
const MAX_USERS = 1500
// opt in to feature for the first 1500 users
const optedInFeatures = (await appDataSource.query(
const optedInFeatures = (await entityManager.query(
`insert into omnivore.features (user_id, name, granted_at)
select $1, $2, $3 from omnivore.features
where name = $2 and granted_at is not null
@ -63,10 +63,9 @@ const optInUltraRealisticVoice = async (uid: string): Promise<Feature> => {
name: FeatureName.UltraRealisticVoice,
grantedAt: null,
}
const result = await getRepository(Feature).upsert(optInRecord, [
'user',
'name',
])
const result = await authTrx((t) =>
t.getRepository(Feature).upsert(optInRecord, ['user', 'name'])
)
if (result.generatedMaps.length === 0) {
throw new Error('failed to update opt-in record')
}
@ -100,25 +99,23 @@ export const signFeatureToken = (
)
}
export const isOptedIn = async (
name: FeatureName,
uid: string
): Promise<boolean> => {
const feature = await getRepository(Feature).findOneBy({
user: { id: uid },
name,
grantedAt: Not(IsNull()),
})
export const isOptedIn = async (name: FeatureName): Promise<boolean> => {
const feature = await authTrx((t) =>
t.getRepository(Feature).findOneBy({
name,
grantedAt: Not(IsNull()),
})
)
return !!feature
}
export const getFeature = async (
name: FeatureName,
uid: string
name: FeatureName
): Promise<Feature | null> => {
return getRepository(Feature).findOneBy({
user: { id: uid },
name,
})
return authTrx((t) =>
t.getRepository(Feature).findOneBy({
name,
})
)
}

View File

@ -1,33 +1,29 @@
import { Follower } from '../entity/follower'
import { User } from '../entity/user'
import { getRepository } from '../repository'
// export const getUserFollowers = async (
// user: User,
// offset?: number,
// count?: number
// ): Promise<User[]> => {
// return (
// await getRepository(Follower).find({
// where: { user: { id: user.id } },
// relations: ['user', 'followee'],
// skip: offset,
// take: count,
// })
// ).map((f: Follower) => f.followee)
// }
export const getUserFollowers = async (
user: User,
offset?: number,
count?: number
): Promise<User[]> => {
return (
await getRepository(Follower).find({
where: { user: { id: user.id } },
relations: ['user', 'followee'],
skip: offset,
take: count,
})
).map((f: Follower) => f.followee)
}
export const getUserFollowing = async (
user: User,
offset?: number,
count?: number
): Promise<User[]> => {
return (
await getRepository(Follower).find({
where: { followee: { id: user.id } },
relations: ['user', 'followee'],
skip: offset,
take: count,
})
).map((f: Follower) => f.user)
}
// export const getUserFollowing = async (
// user: User,
// offset?: number,
// count?: number
// ): Promise<User[]> => {
// return (
// await getRepository(Follower).find({
// where: { followee: { id: user.id } },
// relations: ['user', 'followee'],
// skip: offset,
// take: count,
// })
// ).map((f: Follower) => f.user)
// }

View File

@ -7,7 +7,8 @@ import { RuleActionType } from '../entity/rule'
import { User } from '../entity/user'
import { homePageURL } from '../env'
import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql'
import { getRepository } from '../repository'
import { authTrx } from '../repository'
import { groupRepository } from '../repository/group'
import { userDataToUser } from '../utils/helpers'
import { createLabel, getLabelByName } from './labels'
import { createRule } from './rules'
@ -26,7 +27,7 @@ export const createGroup = async (input: {
async (t) => {
// Max number of groups a user can create
const maxGroups = 3
const groupCount = await getRepository(Group).countBy({
const groupCount = await t.getRepository(Group).countBy({
createdBy: { id: input.admin.id },
})
if (groupCount >= maxGroups) {
@ -71,10 +72,12 @@ export const createGroup = async (input: {
export const getRecommendationGroups = async (
user: User
): Promise<RecommendationGroup[]> => {
const groupMembers = await getRepository(GroupMembership).find({
where: { user: { id: user.id } },
relations: ['invite', 'group.members.user.profile'],
})
const groupMembers = await authTrx((t) =>
t.getRepository(GroupMembership).find({
where: { user: { id: user.id } },
relations: ['invite', 'group.members.user.profile'],
})
)
return groupMembers.map((gm) => {
const admins: GraphqlUser[] = []
@ -144,7 +147,7 @@ having count(*) < $4`,
return invite
})
const group = await getRepository(Group).findOneOrFail({
const group = await groupRepository.findOneOrFail({
where: { id: invite.group.id },
relations: ['members', 'members.user.profile'],
})
@ -175,7 +178,7 @@ export const leaveGroup = async (
user: User,
groupId: string
): Promise<boolean> => {
return appDataSource.transaction(async (t) => {
return authTrx(async (t) => {
const group = await t
.getRepository(Group)
.createQueryBuilder('group')
@ -275,7 +278,7 @@ export const getGroupsWhereUserCanPost = async (
userId: string,
groupIds: string[]
): Promise<Group[]> => {
return getRepository(Group)
return groupRepository
.createQueryBuilder('group')
.innerJoin('group.members', 'members1')
.whereInIds(groupIds)

View File

@ -3,7 +3,7 @@ import { DeepPartial } from 'typeorm'
import { Highlight } from '../entity/highlight'
import { homePageURL } from '../env'
import { createPubSubClient, EntityType } from '../pubsub'
import { entityManager, setClaims } from '../repository'
import { authTrx, setClaims } from '../repository'
import { highlightRepository } from '../repository/highlight'
type HighlightEvent = Highlight & { pageId: string }
@ -20,10 +20,9 @@ export const getHighlightUrl = (slug: string, highlightId: string): string =>
export const saveHighlight = async (
highlight: DeepPartial<Highlight>,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
) => {
const newHighlight = await em.transaction(async (tx) => {
const newHighlight = await authTrx(async (tx) => {
await setClaims(tx, userId)
return tx
@ -44,12 +43,9 @@ export const mergeHighlights = async (
highlightsToRemove: string[],
highlightToAdd: DeepPartial<Highlight>,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
) => {
const newHighlight = await em.transaction(async (tx) => {
await setClaims(tx, userId)
const newHighlight = await authTrx(async (tx) => {
const highlightRepo = tx.withRepository(highlightRepository)
await highlightRepo.delete(highlightsToRemove)
@ -66,13 +62,8 @@ export const mergeHighlights = async (
return newHighlight
}
export const deleteHighlightById = async (
highlightId: string,
userId: string
) => {
return entityManager.transaction(async (tx) => {
await setClaims(tx, userId)
export const deleteHighlightById = async (highlightId: string) => {
return authTrx(async (tx) => {
const highlightRepo = tx.withRepository(highlightRepository)
const highlight = await highlightRepo.findById(highlightId)
if (!highlight) {

View File

@ -2,7 +2,7 @@ import { In } from 'typeorm'
import { Label } from '../entity/label'
import { LibraryItem } from '../entity/library_item'
import { createPubSubClient, EntityType } from '../pubsub'
import { entityManager, setClaims } from '../repository'
import { authTrx } from '../repository'
import { highlightRepository } from '../repository/highlight'
import { CreateLabelInput, labelRepository } from '../repository/label'
import { libraryItemRepository } from '../repository/library_item'
@ -24,12 +24,9 @@ import { libraryItemRepository } from '../repository/library_item'
export const getLabelsAndCreateIfNotExist = async (
labels: CreateLabelInput[],
userId: string,
em = entityManager
userId: string
): Promise<Label[]> => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return authTrx(async (tx) => {
const labelRepo = tx.withRepository(labelRepository)
// find existing labels
const labelEntities = await labelRepo.findByNames(labels.map((l) => l.name))
@ -55,11 +52,9 @@ export const saveLabelsInLibraryItem = async (
labels: Label[],
libraryItemId: string,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
) => {
await em.transaction(async (tx) => {
await setClaims(tx, userId)
await authTrx(async (tx) => {
await tx
.withRepository(libraryItemRepository)
.update(libraryItemId, { labels })
@ -77,11 +72,9 @@ export const addLabelsToLibraryItem = async (
labels: Label[],
libraryItemId: string,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
) => {
await em.transaction(async (tx) => {
await setClaims(tx, userId)
await authTrx(async (tx) => {
await tx
.withRepository(libraryItemRepository)
.createQueryBuilder()
@ -102,12 +95,9 @@ export const saveLabelsInHighlight = async (
labels: Label[],
highlightId: string,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
) => {
await em.transaction(async (tx) => {
await setClaims(tx, userId)
await authTrx(async (tx) => {
await tx.withRepository(highlightRepository).update(highlightId, { labels })
})
@ -119,14 +109,8 @@ export const saveLabelsInHighlight = async (
)
}
export const findLabelsByIds = async (
ids: string[],
userId: string,
em = entityManager
): Promise<Label[]> => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
export const findLabelsByIds = async (ids: string[]): Promise<Label[]> => {
return authTrx(async (tx) => {
return tx.withRepository(labelRepository).findBy({
id: In(ids),
})

View File

@ -7,7 +7,7 @@ import {
LibraryItemType,
} from '../entity/library_item'
import { createPubSubClient, EntityType } from '../pubsub'
import { entityManager, setClaims } from '../repository'
import { authTrx, setClaims } from '../repository'
import { libraryItemRepository } from '../repository/library_item'
import {
DateFilter,
@ -18,9 +18,9 @@ import {
LabelFilterType,
NoFilter,
ReadFilter,
Sort,
SortBy,
SortOrder,
Sort,
} from '../utils/search'
export interface SearchArgs {
@ -225,8 +225,7 @@ const buildWhereClause = (
export const searchLibraryItems = async (
args: SearchArgs,
userId: string,
em = entityManager
userId: string
): Promise<{ libraryItems: LibraryItem[]; count: number }> => {
const { from = 0, size = 10, sort } = args
@ -236,9 +235,7 @@ export const searchLibraryItems = async (
const sortField = sort?.by || SortBy.SAVED
// add pagination and sorting
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return authTrx(async (tx) => {
const queryBuilder = tx
.createQueryBuilder(LibraryItem, 'library_item')
.leftJoinAndSelect('library_item.labels', 'labels')
@ -262,12 +259,9 @@ export const searchLibraryItems = async (
export const findLibraryItemById = async (
id: string,
userId: string,
em = entityManager
userId: string
): Promise<LibraryItem | null> => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return authTrx(async (tx) => {
return tx
.createQueryBuilder(LibraryItem, 'library_item')
.leftJoinAndSelect('library_item.labels', 'labels')
@ -280,12 +274,9 @@ export const findLibraryItemById = async (
export const findLibraryItemByUrl = async (
url: string,
userId: string,
em = entityManager
userId: string
): Promise<LibraryItem | null> => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return authTrx(async (tx) => {
return tx
.createQueryBuilder(LibraryItem, 'library_item')
.leftJoinAndSelect('library_item.labels', 'labels')
@ -300,12 +291,9 @@ export const updateLibraryItem = async (
id: string,
libraryItem: DeepPartial<LibraryItem>,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
): Promise<LibraryItem> => {
const updatedLibraryItem = await em.transaction(async (tx) => {
await setClaims(tx, userId)
const updatedLibraryItem = await authTrx(async (tx) => {
return tx.withRepository(libraryItemRepository).save({ id, ...libraryItem })
})
@ -321,10 +309,9 @@ export const updateLibraryItem = async (
export const createLibraryItem = async (
libraryItem: DeepPartial<LibraryItem>,
userId: string,
pubsub = createPubSubClient(),
em = entityManager
pubsub = createPubSubClient()
): Promise<LibraryItem> => {
const newLibraryItem = await em.transaction(async (tx) => {
const newLibraryItem = await authTrx(async (tx) => {
await setClaims(tx, userId)
return tx.withRepository(libraryItemRepository).save(libraryItem)
@ -341,13 +328,9 @@ export const createLibraryItem = async (
export const findLibraryItemsByPrefix = async (
prefix: string,
userId: string,
limit = 5,
em = entityManager
limit = 5
): Promise<LibraryItem[]> => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return authTrx(async (tx) => {
return tx
.createQueryBuilder(LibraryItem, 'library_item')
.where('library_item.title ILIKE :prefix', { prefix: `${prefix}%` })

View File

@ -1,5 +1,5 @@
import { ReceivedEmail } from '../entity/received_email'
import { entityManager, getRepository } from '../repository'
import { authTrx } from '../repository'
export const saveReceivedEmail = async (
from: string,
@ -10,21 +10,22 @@ export const saveReceivedEmail = async (
userId: string,
type: 'article' | 'non-article' = 'non-article'
): Promise<ReceivedEmail> => {
return getRepository(ReceivedEmail).save({
from,
to,
subject,
text,
html,
type,
user: { id: userId },
})
return authTrx((t) =>
t.getRepository(ReceivedEmail).save({
from,
to,
subject,
text,
html,
type,
user: { id: userId },
})
)
}
export const updateReceivedEmail = async (
id: string,
type: 'article' | 'non-article',
em = entityManager
type: 'article' | 'non-article'
) => {
await em.getRepository(ReceivedEmail).update(id, { type })
return authTrx((t) => t.getRepository(ReceivedEmail).update(id, { type }))
}

View File

@ -1,74 +1,74 @@
import { EntityManager, IsNull, Not } from 'typeorm'
import { getPageById } from '../elastic/pages'
import { Reminder } from '../entity/reminder'
import { getRepository } from '../repository'
import { logger } from '../utils/logger'
// import { EntityManager, IsNull, Not } from 'typeorm'
// import { getPageById } from '../elastic/pages'
// import { Reminder } from '../entity/reminder'
// import { getRepository } from '../repository'
// import { logger } from '../utils/logger'
export interface PageReminder {
pageId: string
reminderId: string
url: string
slug: string
title: string
description?: string
author?: string
image?: string
sendNotification?: boolean
}
// export interface PageReminder {
// pageId: string
// reminderId: string
// url: string
// slug: string
// title: string
// description?: string
// author?: string
// image?: string
// sendNotification?: boolean
// }
export const getPagesWithReminder = async (
userId: string,
remindAt: Date
): Promise<PageReminder[]> => {
const reminders = await getRepository(Reminder).findBy({
user: { id: userId },
status: 'CREATED',
remindAt,
elasticPageId: Not(IsNull()),
})
// export const getPagesWithReminder = async (
// userId: string,
// remindAt: Date
// ): Promise<PageReminder[]> => {
// const reminders = await getRepository(Reminder).findBy({
// user: { id: userId },
// status: 'CREATED',
// remindAt,
// elasticPageId: Not(IsNull()),
// })
const results: PageReminder[] = []
for (const reminder of reminders) {
if (reminder.elasticPageId) {
const page = await getPageById(reminder.elasticPageId)
if (!page) {
logger.info(`Reminder ${reminder.id} has invalid elasticPageId`)
continue
}
// const results: PageReminder[] = []
// for (const reminder of reminders) {
// if (reminder.elasticPageId) {
// const page = await getPageById(reminder.elasticPageId)
// if (!page) {
// logger.info(`Reminder ${reminder.id} has invalid elasticPageId`)
// continue
// }
results.push({
pageId: page.id,
reminderId: reminder.id,
url: page.url,
slug: page.slug,
title: page.title,
description: page.description,
author: page.author,
image: page.image,
sendNotification: reminder.sendNotification,
})
}
}
// results.push({
// pageId: page.id,
// reminderId: reminder.id,
// url: page.url,
// slug: page.slug,
// title: page.title,
// description: page.description,
// author: page.author,
// image: page.image,
// sendNotification: reminder.sendNotification,
// })
// }
// }
return results
}
// return results
// }
export const setRemindersComplete = async (
tx: EntityManager,
userId: string,
remindAt: Date
): Promise<Reminder | null> => {
const updateResult = await tx
.createQueryBuilder()
.where({ userId, remindAt })
.update(Reminder)
.set({ status: 'COMPLETED' })
.returning('*')
.execute()
// export const setRemindersComplete = async (
// tx: EntityManager,
// userId: string,
// remindAt: Date
// ): Promise<Reminder | null> => {
// const updateResult = await tx
// .createQueryBuilder()
// .where({ userId, remindAt })
// .update(Reminder)
// .set({ status: 'COMPLETED' })
// .returning('*')
// .execute()
if (updateResult.generatedMaps.length === 0) {
return null
}
// if (updateResult.generatedMaps.length === 0) {
// return null
// }
return updateResult.generatedMaps[0] as Reminder
}
// return updateResult.generatedMaps[0] as Reminder
// }

View File

@ -4,14 +4,13 @@ import { ContentDisplayReport } from '../entity/reports/content_display_report'
import { ReportItemInput, ReportType } from '../generated/graphql'
import { getRepository } from '../repository'
import { logger } from '../utils/logger'
import { findLibraryItemById } from './library_item'
export const saveContentDisplayReport = async (
uid: string,
input: ReportItemInput
): Promise<boolean> => {
const repo = getRepository(ContentDisplayReport)
const page = await getPageById(input.pageId)
const page = await findLibraryItemById(input.pageId)
if (!page) {
logger.info('unable to submit report, page not found', input)
@ -23,7 +22,6 @@ export const saveContentDisplayReport = async (
// what the user saw.
const result = await repo.save({
user: { id: uid },
elasticPageId: input.pageId,
content: page.content,
originalHtml: page.originalHtml || undefined,
originalUrl: page.url,

View File

@ -1,6 +1,6 @@
import { ILike } from 'typeorm'
import { Rule, RuleAction } from '../entity/rule'
import { getRepository } from '../repository'
import { authTrx } from '../repository'
export const createRule = async (
userId: string,
@ -11,17 +11,20 @@ export const createRule = async (
filter: string
}
): Promise<Rule> => {
const existingRule = await getRepository(Rule).findOneBy({
user: { id: userId },
name: ILike(rule.name),
})
const existingRule = await authTrx((t) =>
t.getRepository(Rule).findOneBy({
name: ILike(rule.name),
})
)
if (existingRule) {
return existingRule
}
return getRepository(Rule).save({
...rule,
user: { id: userId },
})
return authTrx((t) =>
t.getRepository(Rule).save({
...rule,
user: { id: userId },
})
)
}

View File

@ -12,6 +12,7 @@ import { WithDataSourcesContext } from '../resolvers/types'
import { logger } from '../utils/logger'
import { getStorageFileDetails } from '../utils/uploads'
import { getLabelsAndCreateIfNotExist } from './labels'
import { setFileUploadComplete } from './upload_file'
export const saveFile = async (
ctx: WithDataSourcesContext,
@ -32,9 +33,7 @@ export const saveFile = async (
await getStorageFileDetails(input.uploadFileId, uploadFile.fileName)
const uploadFileData = await ctx.authTrx(async (tx) => {
return setFileUploadComplete(input.uploadFileId, tx)
})
const uploadFileData = await setFileUploadComplete(input.uploadFileId)
if (!uploadFileData) {
return {

View File

@ -1,43 +1,46 @@
import { SearchHistory } from '../entity/search_history'
import { getRepository } from '../repository'
import { authTrx } from '../repository'
export const getRecentSearches = async (
userId: string
): Promise<SearchHistory[]> => {
export const getRecentSearches = async (): Promise<SearchHistory[]> => {
// get top 10 recent searches
return getRepository(SearchHistory).find({
where: { user: { id: userId } },
order: { createdAt: 'DESC' },
take: 10,
})
return authTrx((t) =>
t.getRepository(SearchHistory).find({
order: { createdAt: 'DESC' },
take: 10,
})
)
}
export const saveSearchHistory = async (
userId: string,
term: string
): Promise<void> => {
await getRepository(SearchHistory).upsert(
{
user: { id: userId },
term,
createdAt: new Date(),
},
{
conflictPaths: ['user', 'term'],
}
await authTrx((t) =>
t.getRepository(SearchHistory).upsert(
{
user: { id: userId },
term,
createdAt: new Date(),
},
{
conflictPaths: ['user', 'term'],
}
)
)
}
export const deleteSearchHistory = async (userId: string): Promise<void> => {
await getRepository(SearchHistory).delete({ user: { id: userId } })
await authTrx((t) =>
t.getRepository(SearchHistory).delete({ user: { id: userId } })
)
}
export const deleteSearchHistoryById = async (
userId: string,
searchHistoryId: string
): Promise<void> => {
await getRepository(SearchHistory).delete({
user: { id: userId },
id: searchHistoryId,
})
await authTrx((t) =>
t.getRepository(SearchHistory).delete({
id: searchHistoryId,
})
)
}

View File

@ -1,23 +0,0 @@
import { Page } from '../elastic/types'
import { ContentReader } from '../generated/graphql'
import { contentReaderForPage } from '../utils/uploads'
import { FeatureName, isOptedIn } from './features'
/*
* We should synthesize the page when user is opted in to the feature
*/
export const shouldSynthesize = async (
userId: string,
page: Page
): Promise<boolean> => {
if (
contentReaderForPage(page.pageType, page.uploadFileId) !==
ContentReader.Web ||
!page.content
) {
// we don't synthesize files for now
return false
}
return isOptedIn(FeatureName.UltraRealisticVoice, userId)
}

View File

@ -2,10 +2,9 @@ import axios from 'axios'
import { NewsletterEmail } from '../entity/newsletter_email'
import { Subscription } from '../entity/subscription'
import { SubscriptionStatus, SubscriptionType } from '../generated/graphql'
import { getRepository } from '../repository'
import { authTrx } from '../repository'
import { logger } from '../utils/logger'
import { sendEmail } from '../utils/sendEmail'
import { createNewsletterEmail } from './newsletters'
interface SaveSubscriptionInput {
userId: string
@ -80,15 +79,15 @@ const sendUnsubscribeHttpRequest = async (url: string): Promise<boolean> => {
}
}
export const getSubscriptionByNameAndUserId = async (
name: string,
userId: string
export const getSubscriptionByName = async (
name: string
): Promise<Subscription | null> => {
return getRepository(Subscription).findOneBy({
name,
user: { id: userId },
type: SubscriptionType.Newsletter,
})
return authTrx((tx) =>
tx.getRepository(Subscription).findOneBy({
name,
type: SubscriptionType.Newsletter,
})
)
}
export const saveSubscription = async ({
@ -106,26 +105,24 @@ export const saveSubscription = async ({
lastFetchedAt: new Date(),
}
const existingSubscription = await getSubscriptionByNameAndUserId(
name,
userId
)
if (existingSubscription) {
// update subscription if already exists
await getRepository(Subscription).update(
existingSubscription.id,
subscriptionData
)
const existingSubscription = await getSubscriptionByName(name)
const result = await authTrx(async (tx) => {
if (existingSubscription) {
// update subscription if already exists
await tx
.getRepository(Subscription)
.update(existingSubscription.id, subscriptionData)
return existingSubscription.id
}
return existingSubscription
}
const result = await getRepository(Subscription).save({
...subscriptionData,
name,
newsletterEmail: { id: newsletterEmail.id },
user: { id: userId },
type: SubscriptionType.Newsletter,
return tx.getRepository(Subscription).save({
...subscriptionData,
name,
newsletterEmail: { id: newsletterEmail.id },
user: { id: userId },
type: SubscriptionType.Newsletter,
})
})
return result.id
@ -150,22 +147,26 @@ export const unsubscribe = async (subscription: Subscription) => {
// because it often requires clicking a button on the page to unsubscribe
}
return getRepository(Subscription).update(subscription.id, {
status: SubscriptionStatus.Unsubscribed,
})
return authTrx((tx) =>
tx.getRepository(Subscription).update(subscription.id, {
status: SubscriptionStatus.Unsubscribed,
})
)
}
export const unsubscribeAll = async (
newsletterEmail: NewsletterEmail
): Promise<void> => {
try {
const subscriptions = await getRepository(Subscription).find({
where: {
user: { id: newsletterEmail.user.id },
newsletterEmail: { id: newsletterEmail.id },
},
relations: ['newsletterEmail'],
})
const subscriptions = await authTrx((t) =>
t.getRepository(Subscription).find({
where: {
user: { id: newsletterEmail.user.id },
newsletterEmail: { id: newsletterEmail.id },
},
relations: ['newsletterEmail'],
})
)
for await (const subscription of subscriptions) {
try {
@ -178,117 +179,3 @@ export const unsubscribeAll = async (
logger.info('Failed to unsubscribe all', error)
}
}
export const getSubscribeHandler = (name: string): SubscribeHandler | null => {
switch (name.toLowerCase()) {
case 'axios_essentials':
return new AxiosEssentialsHandler()
case 'morning_brew':
return new MorningBrewHandler()
case 'milk_road':
return new MilkRoadHandler()
case 'money_stuff':
return new MoneyStuffHandler()
default:
return null
}
}
export class SubscribeHandler {
async handleSubscribe(
userId: string,
name: string
): Promise<Subscription[] | null> {
try {
const newsletterEmail =
(await getRepository(NewsletterEmail).findOneBy({
user: { id: userId },
})) || (await createNewsletterEmail(userId))
// subscribe to newsletter service
const subscribedNames = await this._subscribe(newsletterEmail.address)
if (subscribedNames.length === 0) {
logger.info('Failed to get subscribe response', name)
return null
}
// create new subscriptions in db
const newSubscriptions = subscribedNames.map(
(name: string): Promise<Subscription> => {
return getRepository(Subscription).save({
name,
newsletterEmail: { id: newsletterEmail.id },
user: { id: userId },
status: SubscriptionStatus.Active,
})
}
)
return Promise.all(newSubscriptions)
} catch (error) {
logger.info('Failed to handleSubscribe', error)
return null
}
}
async _subscribe(email: string): Promise<string[]> {
return Promise.all([])
}
}
class AxiosEssentialsHandler extends SubscribeHandler {
async _subscribe(email: string): Promise<string[]> {
await axios.post('https://api.axios.com/api/render/readers/unauth-sub/', {
headers: {
'content-type': 'application/json',
},
body: `{"lists":["newsletter_axiosam","newsletter_axiospm","newsletter_axiosfinishline"],"user_vars":{"source":"axios","medium":null,"campaign":null,"term":null,"content":null,"page":"webflow-newsletters-all"},"email":"${email}"`,
})
return ['Axios AM', 'Axios PM', 'Axios Finish Line']
}
}
class MorningBrewHandler extends SubscribeHandler {
async _subscribe(email: string): Promise<string[]> {
await axios.post('https://singularity.morningbrew.com/graphql', {
headers: {
'content-type': 'application/json',
},
body: `{"operationName":"CreateUserSubscription","variables":{"signupCreateInput":{"email":"${email}","kid":null,"gclid":null,"utmCampaign":"mb","utmMedium":"website","utmSource":"hero-module","utmContent":null,"utmTerm":null,"requestPath":"https://www.morningbrew.com/daily","uiModule":"hero-module"},"signupCreateVerticalSlug":"daily"},"query":"mutation CreateUserSubscription($signupCreateInput: SignupCreateInput!, $signupCreateVerticalSlug: String!) {\\n signupCreate(input: $signupCreateInput, verticalSlug: $signupCreateVerticalSlug) {\\n user {\\n accessToken\\n email\\n hasSeenOnboarding\\n referralCode\\n verticalSubscriptions {\\n isActive\\n vertical {\\n slug\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n isNewSubscription\\n fromAffiliate\\n subscriptionId\\n __typename\\n }\\n}\\n"}`,
})
return ['Morning Brew']
}
}
class MilkRoadHandler extends SubscribeHandler {
async _subscribe(email: string): Promise<string[]> {
await axios.post('https://www.milkroad.com/subscriptions', {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
body: `email=${encodeURIComponent(email)}&commit=Subscribe`,
})
return ['Milk Road']
}
}
class MoneyStuffHandler extends SubscribeHandler {
async _subscribe(email: string): Promise<string[]> {
await axios.put(
`https://login.bloomberg.com/api/newsletters/update?email=${encodeURIComponent(
email
)}&source=&notify=true&optIn=false`,
{
headers: {
'content-type': 'application/json',
},
body: '{"Money Stuff":true}',
}
)
return ['Money Stuff']
}
}

View File

@ -1,30 +1,12 @@
import { entityManager, setClaims } from '../repository'
import { uploadFileRepository } from '../repository/upload_file'
import { UploadFile } from '../entity/upload_file'
import { authTrx } from '../repository'
export const findUploadFileById = async (
id: string,
userId: string,
em = entityManager
) => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
const uploadFile = await tx
.withRepository(uploadFileRepository)
.findById(id)
return uploadFile
})
export const findUploadFileById = async (id: string) => {
return authTrx(async (tx) => tx.getRepository(UploadFile).findBy({ id }))
}
export const setFileUploadComplete = async (
id: string,
userId: string,
em = entityManager
) => {
return em.transaction(async (tx) => {
await setClaims(tx, userId)
return tx
.withRepository(uploadFileRepository)
.save({ id, status: 'COMPLETED' })
})
export const setFileUploadComplete = async (id: string) => {
return authTrx(async (tx) =>
tx.getRepository(UploadFile).save({ id, status: 'COMPLETED' })
)
}

View File

@ -1,42 +1,34 @@
import { appDataSource } from '../data_source'
import { User } from '../entity/user'
import { UserDeviceToken } from '../entity/user_device_tokens'
import { env } from '../env'
import { SetDeviceTokenErrorCode } from '../generated/graphql'
import { getRepository, setClaims } from '../repository'
import { authTrx } from '../repository'
import { analytics } from '../utils/analytics'
export const getDeviceToken = async (
id: string
): Promise<UserDeviceToken | null> => {
return getRepository(UserDeviceToken).findOneBy({ id })
return authTrx((t) => t.getRepository(UserDeviceToken).findOneBy({ id }))
}
export const getDeviceTokenByToken = async (
token: string
): Promise<UserDeviceToken | null> => {
return getRepository(UserDeviceToken).findOneBy({ token })
return authTrx((t) => t.getRepository(UserDeviceToken).findOneBy({ token }))
}
export const getDeviceTokensByUserId = async (
userId: string
): Promise<UserDeviceToken[]> => {
return getRepository(UserDeviceToken).find({
where: { user: { id: userId } },
})
return authTrx((t) =>
t.getRepository(UserDeviceToken).findBy({
user: { id: userId },
})
)
}
export const createDeviceToken = async (
userId: string,
token: string
): Promise<UserDeviceToken> => {
const user = await getRepository(User).findOneBy({ id: userId })
if (!user) {
return Promise.reject({
errorCode: SetDeviceTokenErrorCode.Unauthorized,
})
}
analytics.track({
userId: userId,
event: 'device_token_created',
@ -45,23 +37,18 @@ export const createDeviceToken = async (
},
})
return getRepository(UserDeviceToken).save({
token: token,
user: user,
})
return authTrx((t) =>
t.getRepository(UserDeviceToken).save({
token,
user: { id: userId },
})
)
}
export const deleteDeviceToken = async (
id: string,
userId: string
): Promise<boolean> => {
const user = await getRepository(User).findOneBy({ id: userId })
if (!user) {
return Promise.reject({
errorCode: SetDeviceTokenErrorCode.Unauthorized,
})
}
analytics.track({
userId: userId,
event: 'device_token_deleted',
@ -70,8 +57,7 @@ export const deleteDeviceToken = async (
},
})
return appDataSource.transaction(async (t) => {
await setClaims(t, userId)
return authTrx(async (t) => {
const result = await t.getRepository(UserDeviceToken).delete(id)
return !!result.affected

View File

@ -4,9 +4,9 @@ import express from 'express'
import * as jwt from 'jsonwebtoken'
import { promisify } from 'util'
import { v4 as uuidv4 } from 'uuid'
import { ApiKey } from '../entity/api_key'
import { env } from '../env'
import { authTrx } from '../repository'
import { apiKeyRepository } from '../repository/api_key'
import { Claims, ClaimsToSet } from '../resolvers/types'
import { logger } from './logger'
@ -33,34 +33,39 @@ export const hashApiKey = (apiKey: string) => {
export const claimsFromApiKey = async (key: string): Promise<Claims> => {
const hashedKey = hashApiKey(key)
return authTrx(async (tx) => {
const apiKeyRepo = tx.withRepository(apiKeyRepository)
return authTrx(
async (tx) => {
const apiKeyRepo = tx.getRepository(ApiKey)
const apiKey = await apiKeyRepo.findOne({
where: {
key: hashedKey,
},
relations: ['user'],
})
if (!apiKey) {
throw new Error('api key not found')
}
const apiKey = await apiKeyRepo.findOne({
where: {
key: hashedKey,
},
relations: ['user'],
})
if (!apiKey) {
throw new Error('api key not found')
}
const iat = Math.floor(Date.now() / 1000)
const exp = Math.floor(new Date(apiKey.expiresAt).getTime() / 1000)
if (exp < iat) {
throw new Error('api key expired')
}
const iat = Math.floor(Date.now() / 1000)
const exp = Math.floor(new Date(apiKey.expiresAt).getTime() / 1000)
if (exp < iat) {
throw new Error('api key expired')
}
// update last used
await apiKeyRepo.update(apiKey.id, { usedAt: new Date() })
// update last used
await apiKeyRepo.update(apiKey.id, { usedAt: new Date() })
return {
uid: apiKey.user.id,
iat,
exp,
}
})
return {
uid: apiKey.user.id,
iat,
exp,
}
},
undefined,
undefined,
'omnivore_admin'
)
}
// verify jwt token first

View File

@ -7,6 +7,11 @@ BEGIN;
DROP TRIGGER IF EXISTS library_item_highlight_annotations_update ON omnivore.highlight;
DROP FUNCTION IF EXISTS update_library_item_highlight_annotations();
ALTER POLICY read_highlight on omnivore.highlight USING (true);
ALTER POLICY create_highlight on omnivore.highlight WITH CHECK (true);
DROP POLICY delete_highlight on omnivore.highlight;
REVOKE DELETE ON omnivore.highlight FROM omnivore_user;
ALTER TABLE omnivore.highlight
ADD COLUMN article_id uuid,
ADD COLUMN elastic_page_id uuid,

View File

@ -206,4 +206,55 @@ CREATE POLICY delete_webhooks on omnivore.webhooks
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.webhooks TO omnivore_user;
ALTER POLICY read_user_device_tokens on omnivore.user_device_tokens
FOR SELECT TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
ALTER POLICY create_user_device_tokens on omnivore.user_device_tokens
FOR INSERT TO omnivore_user
WITH CHECK (user_id = omnivore.get_current_user_id());
ALTER TABLE omnivore.search_history ENABLE ROW LEVEL SECURITY;
CREATE POLICY read_search_history on omnivore.search_history
FOR SELECT TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
CREATE POLICY create_search_history on omnivore.search_history
FOR INSERT TO omnivore_user
WITH CHECK (user_id = omnivore.get_current_user_id());
CREATE POLICY update_search_history on omnivore.search_history
FOR UPDATE TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
CREATE POLICY delete_search_history on omnivore.search_history
FOR DELETE TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
ALTER TABLE omnivore.group_membership ENABLE ROW LEVEL SECURITY;
CREATE POLICY read_group_membership on omnivore.group_membership
FOR SELECT TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
CREATE POLICY create_group_membership on omnivore.group_membership
FOR INSERT TO omnivore_user
WITH CHECK (user_id = omnivore.get_current_user_id());
CREATE POLICY update_group_membership on omnivore.group_membership
FOR UPDATE TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
CREATE POLICY delete_group_membership on omnivore.group_membership
FOR DELETE TO omnivore_user
USING (user_id = omnivore.get_current_user_id());
ALTER TABLE omnivore.abuse_report
ALTER COLUMN elastic_page_id RENAME TO library_item_id,
DROP COLUMN page_id;
ALTER TABLE omnivore.content_display_report
ALTER COLUMN elastic_page_id RENAME TO library_item_id,
DROP COLUMN page_id;
COMMIT;

View File

@ -4,4 +4,97 @@
BEGIN;
ALTER TABLE omnivore.api_key DISABLE ROW LEVEL SECURITY;
DROP POLICY read_api_key on omnivore.api_key;
DROP POLICY create_api_key on omnivore.api_key;
DROP POLICY update_api_key on omnivore.api_key;
DROP POLICY delete_api_key on omnivore.api_key;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.api_key FROM omnivore_user;
ALTER TABLE omnivore.features DISABLE ROW LEVEL SECURITY;
DROP POLICY read_features on omnivore.features;
DROP POLICY create_features on omnivore.features;
DROP POLICY update_features on omnivore.features;
DROP POLICY delete_features on omnivore.features;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.features FROM omnivore_user;
ALTER TABLE omnivore.filters DISABLE ROW LEVEL SECURITY;
DROP POLICY read_filters on omnivore.filters;
DROP POLICY create_filters on omnivore.filters;
DROP POLICY update_filters on omnivore.filters;
DROP POLICY delete_filters on omnivore.filters;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.filters FROM omnivore_user;
ALTER TABLE omnivore.integrations DISABLE ROW LEVEL SECURITY;
DROP POLICY read_integrations on omnivore.integrations;
DROP POLICY create_integrations on omnivore.integrations;
DROP POLICY update_integrations on omnivore.integrations;
DROP POLICY delete_integrations on omnivore.integrations;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.integrations FROM omnivore_user;
ALTER TABLE omnivore.newsletter_emails DISABLE ROW LEVEL SECURITY;
DROP POLICY read_newsletter_emails on omnivore.newsletter_emails;
DROP POLICY create_newsletter_emails on omnivore.newsletter_emails;
DROP POLICY delete_newsletter_emails on omnivore.newsletter_emails;
REVOKE SELECT, INSERT, DELETE ON omnivore.newsletter_emails FROM omnivore_user;
ALTER POLICY read_labels on omnivore.labels USING (true);
ALTER POLICY create_labels on omnivore.labels WITH CHECK (true);
ALTER TABLE omnivore.received_emails DISABLE ROW LEVEL SECURITY;
DROP POLICY read_received_emails on omnivore.received_emails;
DROP POLICY create_received_emails on omnivore.received_emails;
DROP POLICY update_received_emails on omnivore.received_emails;
DROP POLICY delete_received_emails on omnivore.received_emails;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.received_emails FROM omnivore_user;
ALTER TABLE omnivore.rules DISABLE ROW LEVEL SECURITY;
DROP POLICY read_rules on omnivore.rules;
DROP POLICY create_rules on omnivore.rules;
DROP POLICY update_rules on omnivore.rules;
DROP POLICY delete_rules on omnivore.rules;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.rules FROM omnivore_user;
ALTER TABLE omnivore.subscriptions DISABLE ROW LEVEL SECURITY;
DROP POLICY read_subscriptions on omnivore.subscriptions;
DROP POLICY create_subscriptions on omnivore.subscriptions;
DROP POLICY update_subscriptions on omnivore.subscriptions;
DROP POLICY delete_subscriptions on omnivore.subscriptions;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.subscriptions FROM omnivore_user;
ALTER TABLE omnivore.upload_files DISABLE ROW LEVEL SECURITY;
DROP POLICY read_upload_files on omnivore.upload_files;
DROP POLICY create_upload_files on omnivore.upload_files;
DROP POLICY update_upload_files on omnivore.upload_files;
DROP POLICY delete_upload_files on omnivore.upload_files;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.upload_files FROM omnivore_user;
ALTER TABLE omnivore.webhooks DISABLE ROW LEVEL SECURITY;
DROP POLICY read_webhooks on omnivore.webhooks;
DROP POLICY create_webhooks on omnivore.webhooks;
DROP POLICY update_webhooks on omnivore.webhooks;
DROP POLICY delete_webhooks on omnivore.webhooks;
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.webhooks FROM omnivore_user;
ALTER POLICY read_user_device_tokens on omnivore.user_device_tokens
FOR SELECT FROM omnivore_user
USING (true);
ALTER POLICY create_user_device_tokens on omnivore.user_device_tokens
FOR INSERT FROM omnivore_user
WITH CHECK (true);
ALTER TABLE omnivore.search_history DISABLE ROW LEVEL SECURITY;
DROP POLICY read_search_history on omnivore.search_history;
DROP POLICY create_search_history on omnivore.search_history;
DROP POLICY update_search_history on omnivore.search_history;
DROP POLICY delete_search_history on omnivore.search_history;
ALTER TABLE omnivore.abuse_report
ALTER COLUMN library_item_id RENAME TO elastic_page_id,
ADD COLUMN page_id text;
ALTER TABLE omnivore.content_display_report
ALTER COLUMN library_item_id RENAME TO elastic_page_id,
ADD COLUMN page_id text;
COMMIT;