save claim in httpContext
This commit is contained in:
@ -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,
|
||||
|
||||
@ -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[]
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
import { entityManager } from '.'
|
||||
import { ApiKey } from '../entity/api_key'
|
||||
|
||||
export const apiKeyRepository = entityManager.getRepository(ApiKey)
|
||||
4
packages/api/src/repository/group.ts
Normal file
4
packages/api/src/repository/group.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { entityManager } from '.'
|
||||
import { Group } from '../entity/groups/group'
|
||||
|
||||
export const groupRepository = entityManager.getRepository(Group)
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 })
|
||||
},
|
||||
})
|
||||
@ -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],
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
// }
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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),
|
||||
})
|
||||
|
||||
@ -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}%` })
|
||||
|
||||
@ -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 }))
|
||||
}
|
||||
|
||||
@ -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
|
||||
// }
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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=¬ify=true&optIn=false`,
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: '{"Money Stuff":true}',
|
||||
}
|
||||
)
|
||||
|
||||
return ['Money Stuff']
|
||||
}
|
||||
}
|
||||
|
||||
@ -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' })
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user