This commit is contained in:
Hongbo Wu
2023-10-10 10:10:45 +08:00
parent ac0d295cf3
commit bab343c41f
16 changed files with 159 additions and 160 deletions

View File

@ -13,10 +13,10 @@ import * as httpContext from 'express-http-context2'
import * as jwt from 'jsonwebtoken'
import { EntityManager } from 'typeorm'
import { promisify } from 'util'
import { appDataSource } from './data_source'
import { sanitizeDirectiveTransformer } from './directives'
import { env } from './env'
import { createPubSubClient } from './pubsub'
import { entityManager } from './repository'
import { functionResolvers } from './resolvers/function_resolvers'
import { ClaimsToSet, ResolverContext } from './resolvers/types'
import ScalarResolvers from './scalars'
@ -79,7 +79,7 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
cb: (em: EntityManager) => TResult,
userRole?: string
): Promise<TResult> =>
entityManager.transaction(async (tx) => {
appDataSource.transaction(async (tx) => {
await setClaims(tx, undefined, userRole)
return cb(tx)
}),

View File

@ -0,0 +1,34 @@
import {
EntitySubscriberInterface,
EventSubscriber,
InsertEvent,
} from 'typeorm'
import { Profile } from '../../entity/profile'
import { createDefaultFiltersForUser } from '../../services/create_user'
import { addPopularReadsForNewUser } from '../../services/popular_reads'
@EventSubscriber()
export class AddPopularReadsToNewUser
implements EntitySubscriberInterface<Profile>
{
listenTo() {
return Profile
}
async afterInsert(event: InsertEvent<Profile>): Promise<void> {
await addPopularReadsForNewUser(event.entity.user.id, event.manager)
}
}
@EventSubscriber()
export class AddDefaultFiltersToNewUser
implements EntitySubscriberInterface<Profile>
{
listenTo() {
return Profile
}
async afterInsert(event: InsertEvent<Profile>): Promise<void> {
await createDefaultFiltersForUser(event.manager)(event.entity.user.id)
}
}

View File

@ -1,64 +0,0 @@
// import {
// EntitySubscriberInterface,
// EventSubscriber,
// InsertEvent,
// } from 'typeorm'
// import { Profile } from '../../entity/profile'
// import { createPubSubClient } from '../../pubsub'
// import { addPopularReadsForNewUser } from '../../services/popular_reads'
// import { IntercomClient } from '../../utils/intercom'
// @EventSubscriber()
// export class CreateIntercomAccount
// implements EntitySubscriberInterface<Profile>
// {
// listenTo() {
// return Profile
// }
// async afterInsert(event: InsertEvent<Profile>): Promise<void> {
// const profile = event.entity
// const customAttributes: { source_user_id: string } = {
// source_user_id: profile.user.sourceUserId,
// }
// await IntercomClient?.contacts.createUser({
// email: profile.user.email,
// externalId: profile.user.id,
// name: profile.user.name,
// avatar: profile.pictureUrl || undefined,
// customAttributes: customAttributes,
// signedUpAt: Math.floor(Date.now() / 1000),
// })
// }
// }
// @EventSubscriber()
// export class PublishNewUserEvent implements EntitySubscriberInterface<Profile> {
// listenTo() {
// return Profile
// }
// async afterInsert(event: InsertEvent<Profile>): Promise<void> {
// const client = createPubSubClient()
// await client.userCreated(
// event.entity.user.id,
// event.entity.user.email,
// event.entity.user.name,
// event.entity.username
// )
// }
// }
// @EventSubscriber()
// export class AddPopularReadsToNewUser
// implements EntitySubscriberInterface<Profile>
// {
// listenTo() {
// return Profile
// }
// async afterInsert(event: InsertEvent<Profile>): Promise<void> {
// await addPopularReadsForNewUser(event.entity.user.id, event.manager)
// }
// }

View File

@ -1,6 +1,6 @@
import { DeepPartial } from 'typeorm'
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
import { entityManager } from '.'
import { appDataSource } from '../data_source'
import { Highlight } from '../entity/highlight'
import { unescapeHtml } from '../utils/helpers'
@ -16,7 +16,7 @@ const unescapeHighlight = (highlight: DeepPartial<Highlight>) => {
return highlight
}
export const highlightRepository = entityManager
export const highlightRepository = appDataSource
.getRepository(Highlight)
.extend({
findById(id: string) {

View File

@ -22,7 +22,7 @@ export const setClaims = async (
export const authTrx = async <T>(
fn: (manager: EntityManager) => Promise<T>,
em = entityManager,
em = appDataSource.manager,
uid?: string,
userRole?: string
): Promise<T> => {
@ -40,7 +40,5 @@ export const authTrx = async <T>(
}
export const getRepository = <T>(entity: EntityTarget<T>) => {
return entityManager.getRepository(entity)
return appDataSource.getRepository(entity)
}
export const entityManager = appDataSource.manager

View File

@ -1,6 +1,6 @@
import { In } from 'typeorm'
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
import { entityManager } from '.'
import { appDataSource } from '../data_source'
import { Label } from '../entity/label'
import { generateRandomColor } from '../utils/helpers'
@ -38,7 +38,7 @@ const convertToLabel = (label: CreateLabelInput, userId: string) => {
}
}
export const labelRepository = entityManager.getRepository(Label).extend({
export const labelRepository = appDataSource.getRepository(Label).extend({
findById(id: string) {
return this.findOneBy({ id })
},

View File

@ -1,7 +1,7 @@
import { entityManager } from '.'
import { appDataSource } from '../data_source'
import { LibraryItem } from '../entity/library_item'
export const libraryItemRepository = entityManager
export const libraryItemRepository = appDataSource
.getRepository(LibraryItem)
.extend({
findById(id: string) {

View File

@ -1,5 +1,5 @@
import { In } from 'typeorm'
import { entityManager } from '.'
import { appDataSource } from '../data_source'
import { User } from './../entity/user'
const TOP_USERS = [
@ -14,7 +14,7 @@ const TOP_USERS = [
]
export const MAX_RECORDS_LIMIT = 1000
export const userRepository = entityManager.getRepository(User).extend({
export const userRepository = appDataSource.getRepository(User).extend({
findById(id: string) {
return this.findOneBy({ id })
},

View File

@ -1,4 +1,5 @@
import { EntityManager } from 'typeorm'
import { appDataSource } from '../data_source'
import { Filter } from '../entity/filter'
import { GroupMembership } from '../entity/groups/group_membership'
import { Invite } from '../entity/groups/invite'
@ -7,14 +8,13 @@ import { StatusType, User } from '../entity/user'
import { env } from '../env'
import { SignupErrorCode } from '../generated/graphql'
import { createPubSubClient } from '../pubsub'
import { authTrx, entityManager, getRepository } from '../repository'
import { authTrx, getRepository } from '../repository'
import { userRepository } from '../repository/user'
import { AuthProvider } from '../routers/auth/auth_types'
import { analytics } from '../utils/analytics'
import { IntercomClient } from '../utils/intercom'
import { logger } from '../utils/logger'
import { validateUsername } from '../utils/usernamePolicy'
import { addPopularReadsForNewUser } from './popular_reads'
import { sendConfirmationEmail } from './send_emails'
export const MAX_RECORDS_LIMIT = 1000
@ -64,7 +64,7 @@ export const createUser = async (input: {
return Promise.reject({ errorCode: SignupErrorCode.InvalidUsername })
}
const [user, profile] = await entityManager.transaction<[User, Profile]>(
const [user, profile] = await appDataSource.transaction<[User, Profile]>(
async (t) => {
let hasInvite = false
let invite: Invite | null = null
@ -103,19 +103,10 @@ export const createUser = async (input: {
})
}
await createDefaultFiltersForUser(t)(user.id)
await addPopularReadsForNewUser(user.id, t)
return [user, profile]
}
)
if (input.pendingConfirmation) {
if (!(await sendConfirmationEmail(user))) {
return Promise.reject({ errorCode: SignupErrorCode.InvalidEmail })
}
}
const customAttributes: { source_user_id: string } = {
source_user_id: user.sourceUserId,
}
@ -146,10 +137,16 @@ export const createUser = async (input: {
},
})
if (input.pendingConfirmation) {
if (!(await sendConfirmationEmail(user))) {
return Promise.reject({ errorCode: SignupErrorCode.InvalidEmail })
}
}
return [user, profile]
}
const createDefaultFiltersForUser =
export const createDefaultFiltersForUser =
(t: EntityManager) =>
async (userId: string): Promise<Filter[]> => {
const defaultFilters = [

View File

@ -1,8 +1,9 @@
import * as jwt from 'jsonwebtoken'
import { DeepPartial, FindOptionsWhere, IsNull, Not } from 'typeorm'
import { appDataSource } from '../data_source'
import { Feature } from '../entity/feature'
import { env } from '../env'
import { entityManager, getRepository } from '../repository'
import { getRepository } from '../repository'
import { logger } from '../utils/logger'
export enum FeatureName {
@ -41,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 entityManager.query(
const optedInFeatures = (await appDataSource.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

View File

@ -1,4 +1,5 @@
import { nanoid } from 'nanoid'
import { appDataSource } from '../data_source'
import { Group } from '../entity/groups/group'
import { GroupMembership } from '../entity/groups/group_membership'
import { Invite } from '../entity/groups/invite'
@ -6,7 +7,7 @@ import { RuleActionType } from '../entity/rule'
import { User } from '../entity/user'
import { homePageURL } from '../env'
import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql'
import { entityManager, getRepository } from '../repository'
import { getRepository } from '../repository'
import { userDataToUser } from '../utils/helpers'
import { findOrCreateLabels } from './labels'
import { createRule } from './rules'
@ -21,7 +22,7 @@ export const createGroup = async (input: {
onlyAdminCanPost?: boolean | null
onlyAdminCanSeeMembers?: boolean | null
}): Promise<[Group, Invite]> => {
const [group, invite] = await entityManager.transaction<[Group, Invite]>(
const [group, invite] = await appDataSource.transaction<[Group, Invite]>(
async (t) => {
// Max number of groups a user can create
const maxGroups = 3
@ -113,7 +114,7 @@ export const joinGroup = async (
user: User,
inviteCode: string
): Promise<RecommendationGroup> => {
const invite = await entityManager.transaction<Invite>(async (t) => {
const invite = await appDataSource.transaction<Invite>(async (t) => {
// Check if the invite exists
const invite = await t
.getRepository(Invite)
@ -173,7 +174,7 @@ export const leaveGroup = async (
user: User,
groupId: string
): Promise<boolean> => {
return entityManager.transaction(async (t) => {
return appDataSource.transaction(async (t) => {
const group = await t
.getRepository(Group)
.createQueryBuilder('group')

View File

@ -2,9 +2,10 @@ import * as httpContext from 'express-http-context2'
import { readFileSync } from 'fs'
import path from 'path'
import { DeepPartial, EntityManager } from 'typeorm'
import { appDataSource } from '../data_source'
import { LibraryItem } from '../entity/library_item'
import { PageType } from '../generated/graphql'
import { authTrx, entityManager } from '../repository'
import { authTrx } from '../repository'
import { libraryItemRepository } from '../repository/library_item'
import { generateSlug, stringToHash, wordsCount } from '../utils/helpers'
import { logger } from '../utils/logger'
@ -107,7 +108,7 @@ const addPopularReads = async (
export const addPopularReadsForNewUser = async (
userId: string,
em = entityManager
em = appDataSource.manager
): Promise<void> => {
const defaultReads = ['omnivore_organize', 'power_read_it_later']

View File

@ -1,8 +1,10 @@
import { AbuseReport } from '../entity/reports/abuse_report'
import { ContentDisplayReport } from '../entity/reports/content_display_report'
import { env } from '../env'
import { ReportItemInput, ReportType } from '../generated/graphql'
import { authTrx, getRepository } from '../repository'
import { logger } from '../utils/logger'
import { sendEmail } from '../utils/sendEmail'
import { findLibraryItemById } from './library_item'
export const saveContentDisplayReport = async (
@ -18,7 +20,7 @@ export const saveContentDisplayReport = async (
// We capture the article content and original html now, in case it
// reparsed or updated later, this gives us a view of exactly
// what the user saw.
const result = await getRepository(ContentDisplayReport).save({
const report = await getRepository(ContentDisplayReport).save({
user: { id: uid },
content: item.readableContent,
originalHtml: item.originalContent || undefined,
@ -27,7 +29,23 @@ export const saveContentDisplayReport = async (
libraryItemId: item.id,
})
return !!result
const message = `A new content display report was created by:
${report.user.id} for URL: ${report.originalUrl}
${report.reportComment}`
logger.info(message)
if (!env.dev.isLocal) {
// If we are in the local environment, just log a message, otherwise email the report
await sendEmail({
to: env.sender.feedback,
subject: 'New content display report',
text: message,
from: env.sender.message,
})
}
return !!report
}
export const saveAbuseReport = async (

View File

@ -1,8 +1,9 @@
import axios from 'axios'
import { appDataSource } from '../data_source'
import { NewsletterEmail } from '../entity/newsletter_email'
import { Subscription } from '../entity/subscription'
import { SubscriptionStatus, SubscriptionType } from '../generated/graphql'
import { authTrx, entityManager, getRepository } from '../repository'
import { authTrx, getRepository } from '../repository'
import { logger } from '../utils/logger'
import { sendEmail } from '../utils/sendEmail'
@ -105,7 +106,7 @@ export const saveSubscription = async ({
}
const existingSubscription = await getSubscriptionByName(name, userId)
const result = await entityManager.transaction(async (tx) => {
const result = await appDataSource.transaction(async (tx) => {
if (existingSubscription) {
// update subscription if already exists
await tx

View File

@ -260,15 +260,17 @@ export const enqueueParseRequest = async ({
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios.post(env.queue.contentFetchUrl, payload).catch((error) => {
logError(error)
logger.error(
`Error occurred while requesting local puppeteer-parse function\nPlease, ensure your function is set up properly and running using "yarn start" from the "/pkg/gcf/puppeteer-parse" folder`
)
})
}, 0)
if (env.queue.contentFetchUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios.post(env.queue.contentFetchUrl, payload).catch((error) => {
logError(error)
logger.error(
`Error occurred while requesting local puppeteer-parse function\nPlease, ensure your function is set up properly and running using "yarn start" from the "/pkg/gcf/puppeteer-parse" folder`
)
})
}, 0)
}
return ''
}
@ -414,12 +416,14 @@ export const enqueueTextToSpeech = async ({
const taskHandlerUrl = `${env.queue.textToSpeechTaskHandlerUrl}?token=${token}`
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios.post(taskHandlerUrl, payload).catch((error) => {
logError(error)
})
}, 0)
if (env.queue.textToSpeechTaskHandlerUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios.post(taskHandlerUrl, payload).catch((error) => {
logError(error)
})
}, 0)
}
return ''
}
const createdTasks = await createHttpTaskWithToken({
@ -461,16 +465,18 @@ export const enqueueRecommendation = async (
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.recommendationTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
if (env.queue.recommendationTaskHandlerUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.recommendationTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
}
return ''
}
@ -505,16 +511,18 @@ export const enqueueImportFromIntegration = async (
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(`${env.queue.integrationTaskHandlerUrl}/import`, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
if (env.queue.integrationTaskHandlerUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(`${env.queue.integrationTaskHandlerUrl}/import`, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
}
return nanoid()
}
@ -552,16 +560,18 @@ export const enqueueThumbnailTask = async (
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.thumbnailTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
if (env.queue.thumbnailTaskHandlerUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.thumbnailTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
}
return ''
}
@ -599,16 +609,18 @@ export const enqueueRssFeedFetch = async (
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.rssFeedTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
if (env.queue.rssFeedTaskHandlerUrl) {
// Calling the handler function directly.
setTimeout(() => {
axios
.post(env.queue.rssFeedTaskHandlerUrl, payload, {
headers,
})
.catch((error) => {
logError(error)
})
}, 0)
}
return nanoid()
}

View File

@ -6,7 +6,7 @@ import { LibraryItem } from '../src/entity/library_item'
import { Reminder } from '../src/entity/reminder'
import { User } from '../src/entity/user'
import { UserDeviceToken } from '../src/entity/user_device_tokens'
import { entityManager, getRepository, setClaims } from '../src/repository'
import { getRepository, setClaims } from '../src/repository'
import { userRepository } from '../src/repository/user'
import { createUser } from '../src/services/create_user'
import { saveLabelsInLibraryItem } from '../src/services/labels'
@ -37,7 +37,7 @@ export const createTestConnection = async (): Promise<void> => {
}
export const deleteFiltersFromUser = async (userId: string) => {
await entityManager.transaction(async (t) => {
await appDataSource.transaction(async (t) => {
await setClaims(t, userId)
const filterRepo = t.getRepository(Filter)