diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 787385516a..3c735a7776 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -27,21 +27,6 @@ jobs: --health-retries 5 ports: - 5432 - elastic: - image: docker.elastic.co/elasticsearch/elasticsearch:7.17.1 - env: - discovery.type: single-node - http.cors.allow-origin: '*' - http.cors.enabled: true - http.cors.allow-headers: 'X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization' - http.cors.allow-credentials: true - options: >- - --health-cmd "curl http://0.0.0.0:9200/_cluster/health" - --health-interval 10s - --health-timeout 5s - --health-retries 10 - ports: - - 9200 redis: image: redis options: >- @@ -82,22 +67,12 @@ jobs: PG_USER: postgres PG_PASSWORD: postgres PG_DB: omnivore_test - ELASTIC_URL: http://localhost:${{ job.services.elastic.ports[9200] }}/ PGPASSWORD: postgres # This is required for the psql command to work without a password prompt - name: TypeScript Build and Lint run: | source ~/.nvm/nvm.sh yarn build yarn lint - env: - PG_HOST: localhost - PG_PORT: ${{ job.services.postgres.ports[5432] }} - PG_USER: app_user - PG_PASSWORD: app_pass - PG_DB: omnivore_test - PG_POOL_MAX: 10 - ELASTIC_URL: http://localhost:${{ job.services.elastic.ports[9200] }}/ - REDIS_URL: redis://localhost:${{ job.services.redis.ports[6379] }} - name: Tests run: | source ~/.nvm/nvm.sh @@ -108,8 +83,7 @@ jobs: PG_USER: app_user PG_PASSWORD: app_pass PG_DB: omnivore_test - PG_POOL_MAX: 10 - ELASTIC_URL: http://localhost:${{ job.services.elastic.ports[9200] }}/ + PG_LOGGER: debug REDIS_URL: redis://localhost:${{ job.services.redis.ports[6379] }} build-docker-images: name: Build docker images diff --git a/packages/api/src/apollo.ts b/packages/api/src/apollo.ts index 67b9bf79ad..7d4c8ea02f 100644 --- a/packages/api/src/apollo.ts +++ b/packages/api/src/apollo.ts @@ -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 = async ({ cb: (em: EntityManager) => TResult, userRole?: string ): Promise => - entityManager.transaction(async (tx) => { + appDataSource.transaction(async (tx) => { await setClaims(tx, undefined, userRole) return cb(tx) }), diff --git a/packages/api/src/events/entity_created.ts b/packages/api/src/events/entity_created.ts deleted file mode 100644 index 74b8c7abed..0000000000 --- a/packages/api/src/events/entity_created.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { PubSub } from '@google-cloud/pubsub' -import { - BaseEntity, - EntitySubscriberInterface, - EventSubscriber, - InsertEvent, -} from 'typeorm' -import { env } from '../env' -import { logger } from '../utils/logger' - -const TOPIC_NAME = 'EntityCreated' - -@EventSubscriber() -export class PublishEntitySubscriber implements EntitySubscriberInterface { - async afterInsert(event: InsertEvent): Promise { - const client = new PubSub() - - const msg = JSON.stringify({ - type: 'EntityCreated', - entity: event.entity, - entityClass: event.entity?.constructor?.name, - }) - - if (env.dev.isLocal) { - logger.info('PublishEntitySubscriber', msg) - return - } - - await client - .topic(TOPIC_NAME) - .publishMessage({ data: Buffer.from(msg) }) - .catch((err) => { - logger.error('PublishEntitySubscriber error publishing event', err) - }) - } -} diff --git a/packages/api/src/events/user/profile_created.ts b/packages/api/src/events/user/profile_created.ts new file mode 100644 index 0000000000..0af9326e05 --- /dev/null +++ b/packages/api/src/events/user/profile_created.ts @@ -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 +{ + listenTo() { + return Profile + } + + async afterInsert(event: InsertEvent): Promise { + await addPopularReadsForNewUser(event.entity.user.id, event.manager) + } +} + +@EventSubscriber() +export class AddDefaultFiltersToNewUser + implements EntitySubscriberInterface +{ + listenTo() { + return Profile + } + + async afterInsert(event: InsertEvent): Promise { + await createDefaultFiltersForUser(event.manager)(event.entity.user.id) + } +} diff --git a/packages/api/src/events/user/user_created.ts b/packages/api/src/events/user/user_created.ts deleted file mode 100644 index 25c5e15cc5..0000000000 --- a/packages/api/src/events/user/user_created.ts +++ /dev/null @@ -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 -{ - listenTo() { - return Profile - } - - async afterInsert(event: InsertEvent): Promise { - 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 { - listenTo() { - return Profile - } - - async afterInsert(event: InsertEvent): Promise { - 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 -{ - listenTo() { - return Profile - } - - async afterInsert(event: InsertEvent): Promise { - await addPopularReadsForNewUser(event.entity.user.id, event.manager) - } -} diff --git a/packages/api/src/repository/highlight.ts b/packages/api/src/repository/highlight.ts index 543669170e..488a802815 100644 --- a/packages/api/src/repository/highlight.ts +++ b/packages/api/src/repository/highlight.ts @@ -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) => { return highlight } -export const highlightRepository = entityManager +export const highlightRepository = appDataSource .getRepository(Highlight) .extend({ findById(id: string) { diff --git a/packages/api/src/repository/index.ts b/packages/api/src/repository/index.ts index fd2d9ed5af..7b02d12a22 100644 --- a/packages/api/src/repository/index.ts +++ b/packages/api/src/repository/index.ts @@ -22,7 +22,7 @@ export const setClaims = async ( export const authTrx = async ( fn: (manager: EntityManager) => Promise, - em = entityManager, + em = appDataSource.manager, uid?: string, userRole?: string ): Promise => { @@ -40,7 +40,5 @@ export const authTrx = async ( } export const getRepository = (entity: EntityTarget) => { - return entityManager.getRepository(entity) + return appDataSource.getRepository(entity) } - -export const entityManager = appDataSource.manager diff --git a/packages/api/src/repository/label.ts b/packages/api/src/repository/label.ts index adb90457af..d217089617 100644 --- a/packages/api/src/repository/label.ts +++ b/packages/api/src/repository/label.ts @@ -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 }) }, diff --git a/packages/api/src/repository/library_item.ts b/packages/api/src/repository/library_item.ts index b6a4ca4cf9..1a428d65bf 100644 --- a/packages/api/src/repository/library_item.ts +++ b/packages/api/src/repository/library_item.ts @@ -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) { diff --git a/packages/api/src/repository/user.ts b/packages/api/src/repository/user.ts index a900746eb3..408543c951 100644 --- a/packages/api/src/repository/user.ts +++ b/packages/api/src/repository/user.ts @@ -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 }) }, diff --git a/packages/api/src/services/create_user.ts b/packages/api/src/services/create_user.ts index 24be963d9f..4648658c87 100644 --- a/packages/api/src/services/create_user.ts +++ b/packages/api/src/services/create_user.ts @@ -1,29 +1,22 @@ 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' import { Profile } from '../entity/profile' import { StatusType, User } from '../entity/user' +import { env } from '../env' import { SignupErrorCode } from '../generated/graphql' -import { authTrx, entityManager, getRepository } from '../repository' +import { createPubSubClient } from '../pubsub' +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 { sendConfirmationEmail } from './send_emails' -import { Filter } from '../entity/filter' -import { analytics } from '../utils/analytics' -import { env } from '../env' -const TOP_USERS = [ - 'jacksonh', - 'nat', - 'luis', - 'satindar', - 'malandrina', - 'patrick', - 'alexgutjahr', - 'hongbowu', -] export const MAX_RECORDS_LIMIT = 1000 export const createUser = async (input: { @@ -71,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 @@ -110,17 +103,29 @@ export const createUser = async (input: { }) } - await createDefaultFiltersForUser(t)(user.id) - 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, } + await IntercomClient?.contacts.createUser({ + email: user.email, + externalId: user.id, + name: user.name, + avatar: profile.pictureUrl || undefined, + customAttributes: customAttributes, + signedUpAt: Math.floor(Date.now() / 1000), + }) + + const pubsubClient = createPubSubClient() + await pubsubClient.userCreated( + user.id, + user.email, + user.name, + profile.username + ) analytics.track({ userId: user.id, @@ -132,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 => { const defaultFilters = [ diff --git a/packages/api/src/services/features.ts b/packages/api/src/services/features.ts index 93acfe77d1..e0a9b337aa 100644 --- a/packages/api/src/services/features.ts +++ b/packages/api/src/services/features.ts @@ -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 => { 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 diff --git a/packages/api/src/services/groups.ts b/packages/api/src/services/groups.ts index 7f8c70fe1a..0a3de58913 100644 --- a/packages/api/src/services/groups.ts +++ b/packages/api/src/services/groups.ts @@ -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 => { - const invite = await entityManager.transaction(async (t) => { + const invite = await appDataSource.transaction(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 => { - return entityManager.transaction(async (t) => { + return appDataSource.transaction(async (t) => { const group = await t .getRepository(Group) .createQueryBuilder('group') diff --git a/packages/api/src/services/popular_reads.ts b/packages/api/src/services/popular_reads.ts index 28030be8e6..c98fd98511 100644 --- a/packages/api/src/services/popular_reads.ts +++ b/packages/api/src/services/popular_reads.ts @@ -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 => { const defaultReads = ['omnivore_organize', 'power_read_it_later'] diff --git a/packages/api/src/services/reports.ts b/packages/api/src/services/reports.ts index a0d0400430..f7829dadf0 100644 --- a/packages/api/src/services/reports.ts +++ b/packages/api/src/services/reports.ts @@ -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 ( diff --git a/packages/api/src/services/subscriptions.ts b/packages/api/src/services/subscriptions.ts index 34df424a27..8aaea89b8e 100644 --- a/packages/api/src/services/subscriptions.ts +++ b/packages/api/src/services/subscriptions.ts @@ -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 diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts index 4d73d26ba3..71df1cbc55 100755 --- a/packages/api/src/util.ts +++ b/packages/api/src/util.ts @@ -74,11 +74,6 @@ interface BackendEnv { gcsUploadSAKeyFilePath: string gcsUploadPrivateBucket: string } - elastic: { - url: string - username: string - password: string - } sender: { message: string feedback: string @@ -144,8 +139,6 @@ const nullableEnvVars = [ 'GAUTH_SECRET', 'SEGMENT_WRITE_KEY', 'TWITTER_BEARER_TOKEN', - 'ELASTIC_USERNAME', - 'ELASTIC_PASSWORD', 'GCS_UPLOAD_PRIVATE_BUCKET', 'SENDER_MESSAGE', 'SENDER_FEEDBACK', @@ -267,11 +260,6 @@ export function getEnv(): BackendEnv { gcsUploadSAKeyFilePath: parse('GCS_UPLOAD_SA_KEY_FILE_PATH'), gcsUploadPrivateBucket: parse('GCS_UPLOAD_PRIVATE_BUCKET'), } - const elastic = { - url: parse('ELASTIC_URL'), - username: parse('ELASTIC_USERNAME'), - password: parse('ELASTIC_PASSWORD'), - } const sender = { message: parse('SENDER_MESSAGE'), feedback: parse('SENDER_FEEDBACK'), @@ -317,7 +305,6 @@ export function getEnv(): BackendEnv { dev, fileUpload, queue, - elastic, sender, sendgrid, readwise, diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 988fb13e71..bb3457e005 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -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() } diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index 2e9eab0dab..f8ca6ffa2e 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -401,7 +401,7 @@ const getJSONLdLinkMetadata = async ( return result } catch (error) { - logger.error(`Unable to get JSONLD link of the article`, error) + logger.error('Unable to get JSONLD link of the article') return result } } diff --git a/packages/api/src/utils/sendEmail.ts b/packages/api/src/utils/sendEmail.ts index b99732099b..ef5122be38 100644 --- a/packages/api/src/utils/sendEmail.ts +++ b/packages/api/src/utils/sendEmail.ts @@ -26,7 +26,7 @@ export const sendEmail = async (msg: MailDataRequired): Promise => { const client = new MailService() if (!process.env.SENDGRID_MSGS_API_KEY) { if (env.dev.isLocal) { - logger.error('SendGrid API key not set.\nSending email:', msg) + logger.info('SendGrid API key not set.\nSending email:', msg) return true } diff --git a/packages/api/test/db.ts b/packages/api/test/db.ts index f977a6f03b..d7ed692d9b 100644 --- a/packages/api/test/db.ts +++ b/packages/api/test/db.ts @@ -1,5 +1,4 @@ import { DeepPartial } from 'typeorm' -import { SnakeNamingStrategy } from 'typeorm-naming-strategies' import { appDataSource } from '../src/data_source' import { Filter } from '../src/entity/filter' import { Label } from '../src/entity/label' @@ -7,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' @@ -27,13 +26,18 @@ export const createTestConnection = async (): Promise => { logging: ['query', 'info'], entities: [__dirname + '/../src/entity/**/*{.js,.ts}'], subscribers: [__dirname + '/../src/events/**/*{.js,.ts}'], - namingStrategy: new SnakeNamingStrategy(), + logger: process.env.PG_LOGGER as + | 'advanced-console' + | 'simple-console' + | 'file' + | 'debug' + | undefined, }) await appDataSource.initialize() } 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) diff --git a/packages/api/test/resolvers/webhooks.test.ts b/packages/api/test/resolvers/webhooks.test.ts index 0c5cce41fa..989ab214bb 100644 --- a/packages/api/test/resolvers/webhooks.test.ts +++ b/packages/api/test/resolvers/webhooks.test.ts @@ -80,7 +80,7 @@ describe('Webhooks API', () => { } ` - const res = await graphqlRequest(query, authToken) + const res = await graphqlRequest(query, authToken).expect(200) expect(res.body.data.webhook.webhook.id).to.eql(webhook.id) expect(res.body.data.webhook.webhook.url).to.eql(webhook.url) @@ -108,7 +108,7 @@ describe('Webhooks API', () => { } ` - const res = await graphqlRequest(query, authToken) + const res = await graphqlRequest(query, authToken).expect(200) const webhooks = await findWebhooks(user.id) expect(res.body.data.webhooks.webhooks).to.eql( @@ -165,7 +165,7 @@ describe('Webhooks API', () => { }) it('should create a webhook', async () => { - const res = await graphqlRequest(query, authToken) + const res = await graphqlRequest(query, authToken).expect(200) expect(res.body.data.setWebhook.webhook).to.be.an('object') expect(res.body.data.setWebhook.webhook.url).to.eql(webhookUrl) @@ -195,7 +195,7 @@ describe('Webhooks API', () => { }) it('should update a webhook', async () => { - const res = await graphqlRequest(query, authToken) + const res = await graphqlRequest(query, authToken).expect(200) expect(res.body.data.setWebhook.webhook).to.be.an('object') expect(res.body.data.setWebhook.webhook.url).to.eql(webhookUrl) @@ -240,7 +240,7 @@ describe('Webhooks API', () => { }) it('should delete a webhook', async () => { - const res = await graphqlRequest(query, authToken) + const res = await graphqlRequest(query, authToken).expect(200) const webhook = await findWebhookById(webhookId, user.id) expect(res.body.data.deleteWebhook.webhook).to.be.an('object') diff --git a/packages/api/test/routers/article.test.ts b/packages/api/test/routers/article.test.ts index e3b6638a03..1d0008602f 100644 --- a/packages/api/test/routers/article.test.ts +++ b/packages/api/test/routers/article.test.ts @@ -16,7 +16,9 @@ describe('/article/save API', () => { // We need to mock the pupeeteer-parse // service here because in dev mode the task gets // called immediately. - nock(env.queue.contentFetchUrl).post('/').reply(200) + if (env.queue.contentFetchUrl) { + nock(env.queue.contentFetchUrl).post('/').reply(200) + } before(async () => { // create test user and login