diff --git a/packages/api/package.json b/packages/api/package.json index ceeb2a8bb..9d0cfacb6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -75,8 +75,8 @@ "snake-case": "^3.0.3", "supertest": "^6.2.2", "ts-loader": "^8.0.3", - "typeorm": "^0.2.37", - "typeorm-naming-strategies": "^2.0.0", + "typeorm": "^0.3.4", + "typeorm-naming-strategies": "^4.1.0", "urlsafe-base64": "^1.0.0", "uuid": "^8.3.1", "voca": "^1.4.0", diff --git a/packages/api/src/entity/follower.ts b/packages/api/src/entity/follower.ts index f00510059..9932cd8d1 100644 --- a/packages/api/src/entity/follower.ts +++ b/packages/api/src/entity/follower.ts @@ -1,16 +1,15 @@ import { - Entity, - BaseEntity, - PrimaryGeneratedColumn, CreateDateColumn, - OneToOne, + Entity, JoinColumn, + OneToOne, + PrimaryGeneratedColumn, } from 'typeorm' import { User } from './user' @Entity({ name: 'user_friends' }) -export class Follower extends BaseEntity { +export class Follower { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/groups/group.ts b/packages/api/src/entity/groups/group.ts index f5a25ba76..bce8d2234 100644 --- a/packages/api/src/entity/groups/group.ts +++ b/packages/api/src/entity/groups/group.ts @@ -1,18 +1,17 @@ import { - Entity, - BaseEntity, Column, - PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, - OneToOne, + Entity, JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm' import { User } from '../user' @Entity() -export class Group extends BaseEntity { +export class Group { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/groups/group_membership.ts b/packages/api/src/entity/groups/group_membership.ts index d33401701..1cb1e537c 100644 --- a/packages/api/src/entity/groups/group_membership.ts +++ b/packages/api/src/entity/groups/group_membership.ts @@ -1,11 +1,10 @@ import { - Entity, - BaseEntity, - PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, - OneToOne, + Entity, JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm' import { User } from '../user' @@ -13,7 +12,7 @@ import { Group } from './group' import { Invite } from './invite' @Entity() -export class GroupMembership extends BaseEntity { +export class GroupMembership { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/groups/invite.ts b/packages/api/src/entity/groups/invite.ts index 70e3cc37e..7dce6fab7 100644 --- a/packages/api/src/entity/groups/invite.ts +++ b/packages/api/src/entity/groups/invite.ts @@ -1,19 +1,18 @@ import { - Entity, - BaseEntity, Column, - PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, - OneToOne, + Entity, JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm' import { User } from '../user' import { Group } from './group' @Entity() -export class Invite extends BaseEntity { +export class Invite { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/highlight.ts b/packages/api/src/entity/highlight.ts index d2f6ff4d4..6ef44d9af 100644 --- a/packages/api/src/entity/highlight.ts +++ b/packages/api/src/entity/highlight.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -12,7 +11,7 @@ import { User } from './user' import { Page } from './page' @Entity({ name: 'highlight' }) -export class Highlight extends BaseEntity { +export class Highlight { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/label.ts b/packages/api/src/entity/label.ts index 0b36eddec..fc45f2179 100644 --- a/packages/api/src/entity/label.ts +++ b/packages/api/src/entity/label.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -10,7 +9,7 @@ import { import { User } from './user' @Entity({ name: 'labels' }) -export class Label extends BaseEntity { +export class Label { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/link.ts b/packages/api/src/entity/link.ts index 40c0f208a..f53d6e8d7 100644 --- a/packages/api/src/entity/link.ts +++ b/packages/api/src/entity/link.ts @@ -10,7 +10,6 @@ // shared_with_highlights | boolean | | | false import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -27,7 +26,7 @@ import { Page } from './page' import { Label } from './label' @Entity({ name: 'links' }) -export class Link extends BaseEntity { +export class Link { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/link_label.ts b/packages/api/src/entity/link_label.ts index 5bcdd64d6..744393c3c 100644 --- a/packages/api/src/entity/link_label.ts +++ b/packages/api/src/entity/link_label.ts @@ -1,5 +1,4 @@ import { - BaseEntity, CreateDateColumn, Entity, JoinColumn, @@ -10,7 +9,7 @@ import { Link } from './link' import { Label } from './label' @Entity({ name: 'link_labels' }) -export class LinkLabel extends BaseEntity { +export class LinkLabel { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/newsletter_email.ts b/packages/api/src/entity/newsletter_email.ts index d69e97298..bdd4beac0 100644 --- a/packages/api/src/entity/newsletter_email.ts +++ b/packages/api/src/entity/newsletter_email.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -11,7 +10,7 @@ import { import { User } from './user' @Entity({ name: 'newsletter_emails' }) -export class NewsletterEmail extends BaseEntity { +export class NewsletterEmail { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/page.ts b/packages/api/src/entity/page.ts index 2f9065977..2ac8abbee 100644 --- a/packages/api/src/entity/page.ts +++ b/packages/api/src/entity/page.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -8,7 +7,7 @@ import { } from 'typeorm' @Entity({ name: 'pages' }) -export class Page extends BaseEntity { +export class Page { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/profile.ts b/packages/api/src/entity/profile.ts index 6c5313f36..a30d057cf 100644 --- a/packages/api/src/entity/profile.ts +++ b/packages/api/src/entity/profile.ts @@ -1,18 +1,17 @@ import { - Entity, - BaseEntity, Column, - PrimaryGeneratedColumn, CreateDateColumn, - UpdateDateColumn, - OneToOne, + Entity, JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm' import { User } from './user' @Entity({ name: 'user_profile' }) -export class Profile extends BaseEntity { +export class Profile { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/reminder.ts b/packages/api/src/entity/reminder.ts index 6b0fabaa4..e5dcd9ae1 100644 --- a/packages/api/src/entity/reminder.ts +++ b/packages/api/src/entity/reminder.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -11,7 +10,7 @@ import { import { User } from './user' @Entity({ name: 'reminders' }) -export class Reminder extends BaseEntity { +export class Reminder { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/reports/abuse_report.ts b/packages/api/src/entity/reports/abuse_report.ts index a38b0b939..8da8c1444 100644 --- a/packages/api/src/entity/reports/abuse_report.ts +++ b/packages/api/src/entity/reports/abuse_report.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -9,7 +8,7 @@ import { import { ReportType } from '../../generated/graphql' @Entity() -export class AbuseReport extends BaseEntity { +export class AbuseReport { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/reports/content_display_report.ts b/packages/api/src/entity/reports/content_display_report.ts index d5396d010..b15b45d41 100644 --- a/packages/api/src/entity/reports/content_display_report.ts +++ b/packages/api/src/entity/reports/content_display_report.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -8,7 +7,7 @@ import { } from 'typeorm' @Entity() -export class ContentDisplayReport extends BaseEntity { +export class ContentDisplayReport { @PrimaryGeneratedColumn('uuid') id?: string diff --git a/packages/api/src/entity/upload_file.ts b/packages/api/src/entity/upload_file.ts index 937777048..7547b4e76 100644 --- a/packages/api/src/entity/upload_file.ts +++ b/packages/api/src/entity/upload_file.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -11,7 +10,7 @@ import { import { User } from './user' @Entity({ name: 'upload_files' }) -export class UploadFile extends BaseEntity { +export class UploadFile { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/user.ts b/packages/api/src/entity/user.ts index 5aa1e5d05..1b7a55da3 100644 --- a/packages/api/src/entity/user.ts +++ b/packages/api/src/entity/user.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -14,7 +13,7 @@ import { Profile } from './profile' import { Label } from './label' @Entity() -export class User extends BaseEntity { +export class User { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/user_device_tokens.ts b/packages/api/src/entity/user_device_tokens.ts index 79c33a45c..0f642d245 100644 --- a/packages/api/src/entity/user_device_tokens.ts +++ b/packages/api/src/entity/user_device_tokens.ts @@ -1,5 +1,4 @@ import { - BaseEntity, Column, CreateDateColumn, Entity, @@ -10,7 +9,7 @@ import { import { User } from './user' @Entity({ name: 'user_device_tokens' }) -export class UserDeviceToken extends BaseEntity { +export class UserDeviceToken { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/entity/utils.ts b/packages/api/src/entity/utils.ts index 2127099cb..1ee7933cc 100644 --- a/packages/api/src/entity/utils.ts +++ b/packages/api/src/entity/utils.ts @@ -1,4 +1,5 @@ -import { EntityManager } from 'typeorm' +import { EntityManager, EntityTarget, Repository } from 'typeorm' +import { AppDataSource } from '../server' export const setClaims = async ( t: EntityManager, @@ -9,3 +10,7 @@ export const setClaims = async ( .query('SELECT * from omnivore.set_claims($1, $2)', [uid, dbRole]) .then() } + +export const getRepository = (entity: EntityTarget): Repository => { + return AppDataSource.getRepository(entity) +} diff --git a/packages/api/src/events/user/user_created.ts b/packages/api/src/events/user/user_created.ts index f634f73d4..71d49db17 100644 --- a/packages/api/src/events/user/user_created.ts +++ b/packages/api/src/events/user/user_created.ts @@ -32,8 +32,7 @@ export class FollowOmnivoreUser implements EntitySubscriberInterface { await event.manager .getRepository(Follower) - .create({ user: event.entity.user, followee: omnivoreProfile.user }) - .save() + .save({ user: event.entity.user, followee: omnivoreProfile.user }) await event.manager.query( `insert into omnivore.links (user_id, article_id, article_url, article_hash, slug) diff --git a/packages/api/src/resolvers/labels/index.ts b/packages/api/src/resolvers/labels/index.ts index 35a73e483..5183674ad 100644 --- a/packages/api/src/resolvers/labels/index.ts +++ b/packages/api/src/resolvers/labels/index.ts @@ -20,10 +20,11 @@ import { analytics } from '../../utils/analytics' import { env } from '../../env' import { User } from '../../entity/user' import { Label } from '../../entity/label' -import { getManager, getRepository, ILike } from 'typeorm' -import { setClaims } from '../../entity/utils' +import { ILike, In } from 'typeorm' +import { getRepository, setClaims } from '../../entity/utils' import { deleteLabelInPages, getPageById, updatePage } from '../../elastic' import { createPubSubClient } from '../../datalayer/pubsub' +import { AppDataSource } from '../../server' export const labelsResolver = authorized( async (_obj, _params, { claims: { uid }, log }) => { @@ -38,7 +39,8 @@ export const labelsResolver = authorized( }) try { - const user = await User.findOne(uid, { + const user = await getRepository(User).findOne({ + where: { id: uid }, relations: ['labels'], }) if (!user) { @@ -69,7 +71,7 @@ export const createLabelResolver = authorized< const { name, color, description } = input try { - const user = await getRepository(User).findOne(uid) + const user = await getRepository(User).findOneBy({ id: uid }) if (!user) { return { errorCodes: [CreateLabelErrorCode.Unauthorized], @@ -77,11 +79,9 @@ export const createLabelResolver = authorized< } // Check if label already exists ignoring case of name - const existingLabel = await getRepository(Label).findOne({ - where: { - user, - name: ILike(name), - }, + const existingLabel = await getRepository(Label).findOneBy({ + user: { id: user.id }, + name: ILike(name), }) if (existingLabel) { return { @@ -89,14 +89,12 @@ export const createLabelResolver = authorized< } } - const label = await getRepository(Label) - .create({ - user, - name, - color, - description: description || '', - }) - .save() + const label = await getRepository(Label).save({ + user, + name, + color, + description: description || '', + }) analytics.track({ userId: uid, @@ -128,14 +126,15 @@ export const deleteLabelResolver = authorized< log.info('deleteLabelResolver') try { - const user = await getRepository(User).findOne(uid) + const user = await getRepository(User).findOneBy({ id: uid }) if (!user) { return { errorCodes: [DeleteLabelErrorCode.Unauthorized], } } - const label = await getRepository(Label).findOne(labelId, { + const label = await getRepository(Label).findOne({ + where: { id: labelId }, relations: ['user'], }) if (!label) { @@ -150,7 +149,7 @@ export const deleteLabelResolver = authorized< } } - const result = await getManager().transaction(async (t) => { + const result = await AppDataSource.transaction(async (t) => { await setClaims(t, uid) return t.getRepository(Label).delete(labelId) }) @@ -197,7 +196,7 @@ export const setLabelsResolver = authorized< const { linkId: pageId, labelIds } = input try { - const user = await getRepository(User).findOne(uid) + const user = await getRepository(User).findOneBy({ id: uid }) if (!user) { return { errorCodes: [SetLabelsErrorCode.Unauthorized], @@ -211,10 +210,8 @@ export const setLabelsResolver = authorized< } } - const labels = await getRepository(Label).findByIds(labelIds, { - where: { - user, - }, + const labels = await getRepository(Label).find({ + where: { id: In(labelIds), user: { id: user.id } }, relations: ['user'], }) if (labels.length !== labelIds.length) { diff --git a/packages/api/src/resolvers/newsletters/index.ts b/packages/api/src/resolvers/newsletters/index.ts index 737f3d830..d8d34a7ce 100644 --- a/packages/api/src/resolvers/newsletters/index.ts +++ b/packages/api/src/resolvers/newsletters/index.ts @@ -1,15 +1,15 @@ import { authorized } from '../../utils/helpers' import { - CreateNewsletterEmailSuccess, CreateNewsletterEmailError, CreateNewsletterEmailErrorCode, - NewsletterEmailsSuccess, - NewsletterEmailsError, - NewsletterEmailsErrorCode, + CreateNewsletterEmailSuccess, + DeleteNewsletterEmailError, DeleteNewsletterEmailErrorCode, DeleteNewsletterEmailSuccess, - DeleteNewsletterEmailError, MutationDeleteNewsletterEmailArgs, + NewsletterEmailsError, + NewsletterEmailsErrorCode, + NewsletterEmailsSuccess, } from '../../generated/graphql' import { createNewsletterEmail, @@ -19,6 +19,8 @@ import { import { NewsletterEmail } from '../../entity/newsletter_email' import { analytics } from '../../utils/analytics' import { env } from '../../env' +import { AppDataSource } from '../../server' +import { User } from '../../entity/user' export const createNewsletterEmailResolver = authorized< CreateNewsletterEmailSuccess, @@ -55,7 +57,16 @@ export const newsletterEmailsResolver = authorized< console.log('newsletterEmailsResolver') try { - const newsletterEmails = await getNewsletterEmails(claims.uid) + const user = await AppDataSource.getRepository(User).findOneBy({ + id: claims.uid, + }) + if (!user) { + return Promise.reject({ + errorCode: NewsletterEmailsErrorCode.Unauthorized, + }) + } + + const newsletterEmails = await getNewsletterEmails(user.id) return { newsletterEmails: newsletterEmails, @@ -84,10 +95,14 @@ export const deleteNewsletterEmailResolver = authorized< }) try { - const newsletterEmail = await NewsletterEmail.findOne( - args.newsletterEmailId, - { relations: ['user'] } - ) + const newsletterEmail = await AppDataSource.getRepository( + NewsletterEmail + ).findOne({ + where: { + id: args.newsletterEmailId, + }, + relations: ['user'], + }) if (!newsletterEmail) { return { diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index a87dc975a..27967ce5a 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -19,7 +19,7 @@ import { articleRouter } from './routers/article_router' import { mobileAuthRouter } from './routers/auth/mobile/mobile_auth_router' import { contentServiceRouter } from './routers/svc/content' import { localDebugRouter } from './routers/local_debug_router' -import { Connection, createConnection } from 'typeorm' +import { DataSource } from 'typeorm' import { SnakeNamingStrategy } from 'typeorm-naming-strategies' import { linkServiceRouter } from './routers/svc/links' import UserModel from './datalayer/user' @@ -57,21 +57,19 @@ export const initModels = (kx: Knex, cache = true): DataModels => ({ reminder: new ReminderModel(kx, cache), }) -const initEntities = async (): Promise => { - return createConnection({ - type: 'postgres', - host: env.pg.host, - port: env.pg.port, - schema: 'omnivore', - username: env.pg.userName, - password: env.pg.password, - database: env.pg.dbName, - logging: ['query', 'info'], - entities: [__dirname + '/entity/**/*{.js,.ts}'], - subscribers: [__dirname + '/events/**/*{.js,.ts}'], - namingStrategy: new SnakeNamingStrategy(), - }) -} +export const AppDataSource = new DataSource({ + type: 'postgres', + host: env.pg.host, + port: env.pg.port, + schema: 'omnivore', + username: env.pg.userName, + password: env.pg.password, + database: env.pg.dbName, + logging: ['query', 'info'], + entities: [__dirname + '/entity/**/*{.js,.ts}'], + subscribers: [__dirname + '/events/**/*{.js,.ts}'], + namingStrategy: new SnakeNamingStrategy(), +}) export const createApp = (): { app: Express @@ -126,7 +124,7 @@ const main = async (): Promise => { // If creating the DB entities fails, we want this to throw // so the container will be restarted and not come online // as healthy. - await initEntities() + await AppDataSource.initialize() await initElasticsearch() diff --git a/packages/api/src/services/archive_link.ts b/packages/api/src/services/archive_link.ts index 13609402b..226df4672 100644 --- a/packages/api/src/services/archive_link.ts +++ b/packages/api/src/services/archive_link.ts @@ -1,13 +1,13 @@ -import { getManager } from 'typeorm' import { Link } from '../entity/link' import { setClaims } from '../entity/utils' +import { AppDataSource } from '../server' export const setLinkArchived = async ( userId: string, linkId: string, archived: boolean ): Promise => { - await getManager().transaction(async (t) => { + await AppDataSource.transaction(async (t) => { await setClaims(t, userId) await t.getRepository(Link).update( { diff --git a/packages/api/src/services/create_group.ts b/packages/api/src/services/create_group.ts index d7f19e466..a8897460b 100644 --- a/packages/api/src/services/create_group.ts +++ b/packages/api/src/services/create_group.ts @@ -1,9 +1,9 @@ -import { getManager } from 'typeorm' import { User } from '../entity/user' import { Group } from '../entity/groups/group' import { Invite } from '../entity/groups/invite' import { GroupMembership } from '../entity/groups/group_membership' import { nanoid } from 'nanoid' +import { AppDataSource } from '../server' export const createGroup = async (input: { admin: User @@ -11,15 +11,12 @@ export const createGroup = async (input: { maxMembers?: number expiresInDays?: number }): Promise<[Group, Invite]> => { - const [group, invite] = await getManager().transaction<[Group, Invite]>( + const [group, invite] = await AppDataSource.transaction<[Group, Invite]>( async (t) => { - const group = await t - .getRepository(Group) - .create({ - name: input.name, - createdBy: input.admin, - }) - .save() + const group = await t.getRepository(Group).save({ + name: input.name, + createdBy: input.admin, + }) const code = nanoid(8) const expirationTime = (() => { @@ -27,25 +24,19 @@ export const createGroup = async (input: { r.setDate(r.getDate() + (input.expiresInDays || 7)) return r })() - const invite = await t - .getRepository(Invite) - .create({ - group, - code, - createdBy: input.admin, - maxMembers: input.maxMembers || 50, - expirationTime: expirationTime, - }) - .save() + const invite = await t.getRepository(Invite).save({ + group, + code, + createdBy: input.admin, + maxMembers: input.maxMembers || 50, + expirationTime: expirationTime, + }) // Add the admin to the group as its first user - await t - .getRepository(GroupMembership) - .create({ - user: input.admin, - group, - invite, - }) - .save() + await t.getRepository(GroupMembership).save({ + user: input.admin, + group, + invite, + }) return [group, invite] } ) diff --git a/packages/api/src/services/create_user.ts b/packages/api/src/services/create_user.ts index 44cc6f721..bd94ead22 100644 --- a/packages/api/src/services/create_user.ts +++ b/packages/api/src/services/create_user.ts @@ -1,12 +1,14 @@ import { AuthProvider } from '../routers/auth/auth_types' import { MembershipTier } from '../datalayer/user/model' -import { EntityManager, getManager, getRepository } from 'typeorm' +import { EntityManager } from 'typeorm' import { User } from '../entity/user' import { Profile } from '../entity/profile' import { SignupErrorCode } from '../generated/graphql' import { validateUsername } from '../utils/usernamePolicy' import { Invite } from '../entity/groups/invite' import { GroupMembership } from '../entity/groups/group_membership' +import { AppDataSource } from '../server' +import { getRepository } from '../entity/utils' export const createUser = async (input: { provider: AuthProvider @@ -28,15 +30,12 @@ export const createUser = async (input: { } // create profile if user exists but profile does not exist - const profile = await getManager() - .getRepository(Profile) - .create({ - username: input.username, - pictureUrl: input.pictureUrl, - bio: input.bio, - user: existingUser, - }) - .save() + const profile = await getRepository(Profile).save({ + username: input.username, + pictureUrl: input.pictureUrl, + bio: input.bio, + user: existingUser, + }) return [existingUser, profile] } @@ -45,10 +44,10 @@ export const createUser = async (input: { return Promise.reject({ errorCode: SignupErrorCode.InvalidUsername }) } - const [user, profile] = await getManager().transaction<[User, Profile]>( + const [user, profile] = await AppDataSource.transaction<[User, Profile]>( async (t) => { let hasInvite = false - let invite: Invite | undefined = undefined + let invite: Invite | null = null if (input.inviteCode) { const inviteCodeRepo = t.getRepository(Invite) @@ -60,37 +59,28 @@ export const createUser = async (input: { hasInvite = true } } - const user = await t - .getRepository(User) - .create({ - source: input.provider, - membership: - input.membershipTier || - (hasInvite ? MembershipTier.Beta : MembershipTier.WaitList), - name: input.name, - email: input.email, - sourceUserId: input.sourceUserId, - password: input.password, - }) - .save() - const profile = await t - .getRepository(Profile) - .create({ - username: input.username, - pictureUrl: input.pictureUrl, - bio: input.bio, - user, - }) - .save() + const user = await t.getRepository(User).save({ + source: input.provider, + membership: + input.membershipTier || + (hasInvite ? MembershipTier.Beta : MembershipTier.WaitList), + name: input.name, + email: input.email, + sourceUserId: input.sourceUserId, + password: input.password, + }) + const profile = await t.getRepository(Profile).save({ + username: input.username, + pictureUrl: input.pictureUrl, + bio: input.bio, + user, + }) if (hasInvite && invite) { - await t - .getRepository(GroupMembership) - .create({ - user: user, - invite: invite, - group: invite.group, - }) - .save() + await t.getRepository(GroupMembership).save({ + user: user, + invite: invite, + group: invite.group, + }) } return [user, profile] } @@ -119,7 +109,7 @@ const validateInvite = async ( return true } -const getUser = async (email: string): Promise => { +const getUser = async (email: string): Promise => { const userRepo = getRepository(User) return userRepo.findOne({ diff --git a/packages/api/src/services/followers.ts b/packages/api/src/services/followers.ts index 2cba149cd..0b93c2e81 100644 --- a/packages/api/src/services/followers.ts +++ b/packages/api/src/services/followers.ts @@ -1,6 +1,6 @@ -import { getRepository } from 'typeorm' import { User } from '../entity/user' import { Follower } from '../entity/follower' +import { getRepository } from '../entity/utils' export const getUserFollowers = async ( user: User, @@ -9,7 +9,7 @@ export const getUserFollowers = async ( ): Promise => { return ( await getRepository(Follower).find({ - where: { user: user }, + where: { user: { id: user.id } }, relations: ['user', 'followee'], skip: offset, take: count, @@ -24,7 +24,7 @@ export const getUserFollowing = async ( ): Promise => { return ( await getRepository(Follower).find({ - where: { followee: user }, + where: { followee: { id: user.id } }, relations: ['user', 'followee'], skip: offset, take: count, diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index daf2d798a..5b3be6915 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -1,10 +1,11 @@ -import DataLoader from 'dataloader' import { Label } from '../entity/label' -import { getRepository, ILike, In } from 'typeorm' -import { Link } from '../entity/link' +import { ILike, In } from 'typeorm' import { PageContext } from '../elastic/types' import { User } from '../entity/user' import { addLabelInPage } from '../elastic' +import { getRepository } from '../entity/utils' +import { Link } from '../entity/link' +import DataLoader from 'dataloader' const batchGetLabelsFromLinkIds = async ( linkIds: readonly string[] @@ -30,13 +31,16 @@ export const addLabelToPage = async ( description?: string } ): Promise => { - const user = await getRepository(User).findOne(ctx.uid) + const user = await getRepository(User).findOneBy({ + id: ctx.uid, + }) + if (!user) { + return false + } - let labelEntity = await getRepository(Label).findOne({ - where: { - user: user, - name: ILike(label.name), - }, + let labelEntity = await getRepository(Label).findOneBy({ + user: { id: user.id }, + name: ILike(label.name), }) if (!labelEntity) { diff --git a/packages/api/src/services/newsletters.ts b/packages/api/src/services/newsletters.ts index 99937bc4a..d091a18c3 100644 --- a/packages/api/src/services/newsletters.ts +++ b/packages/api/src/services/newsletters.ts @@ -1,9 +1,11 @@ -import { getRepository } from 'typeorm' import { NewsletterEmail } from '../entity/newsletter_email' import { nanoid } from 'nanoid' import { User } from '../entity/user' import { CreateNewsletterEmailErrorCode } from '../generated/graphql' import { env } from '../env' +import { AppDataSource } from '../server' +import { getRepository } from '../entity/utils' + import addressparser = require('nodemailer/lib/addressparser') const parsedAddress = (emailAddress: string): string | undefined => { @@ -17,7 +19,8 @@ const parsedAddress = (emailAddress: string): string | undefined => { export const createNewsletterEmail = async ( userId: string ): Promise => { - const user = await getRepository(User).findOne(userId, { + const user = await getRepository(User).findOne({ + where: { id: userId }, relations: ['profile'], }) if (!user) { @@ -28,19 +31,17 @@ export const createNewsletterEmail = async ( // generate a random email address with username prefix const emailAddress = createRandomEmailAddress(user.profile.username, 8) - return getRepository(NewsletterEmail) - .create({ - address: emailAddress, - user: user, - }) - .save() + return getRepository(NewsletterEmail).save({ + address: emailAddress, + user: user, + }) } export const getNewsletterEmails = async ( userId: string ): Promise => { return getRepository(NewsletterEmail).find({ - where: { user: userId }, + where: { user: { id: userId } }, order: { createdAt: 'DESC' }, }) } @@ -69,7 +70,7 @@ export const updateConfirmationCode = async ( export const getNewsletterEmail = async ( emailAddress: string -): Promise => { +): Promise => { const address = parsedAddress(emailAddress) return getRepository(NewsletterEmail) .createQueryBuilder('newsletter_email') diff --git a/packages/api/src/services/reports.ts b/packages/api/src/services/reports.ts index f58e7e74b..ed545b075 100644 --- a/packages/api/src/services/reports.ts +++ b/packages/api/src/services/reports.ts @@ -1,8 +1,8 @@ -import { getRepository } from 'typeorm' import { ReportItemInput, ReportType } from '../generated/graphql' import { ContentDisplayReport } from '../entity/reports/content_display_report' import { AbuseReport } from '../entity/reports/abuse_report' import { getPageById } from '../elastic' +import { getRepository } from '../entity/utils' export const saveContentDisplayReport = async ( uid: string, @@ -20,16 +20,14 @@ 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 repo - .create({ - userId: uid, - elasticPageId: input.pageId, - content: page.content, - originalHtml: page.originalHtml || undefined, - originalUrl: page.url, - reportComment: input.reportComment, - }) - .save() + const result = await repo.save({ + userId: uid, + elasticPageId: input.pageId, + content: page.content, + originalHtml: page.originalHtml || undefined, + originalUrl: page.url, + reportComment: input.reportComment, + }) return !!result } @@ -55,16 +53,14 @@ export const saveAbuseReport = 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 repo - .create({ - reportedBy: uid, - sharedBy: input.sharedBy, - elasticPageId: input.pageId, - itemUrl: input.itemUrl, - reportTypes: [ReportType.Abusive], - reportComment: input.reportComment, - }) - .save() + const result = await repo.save({ + reportedBy: uid, + sharedBy: input.sharedBy, + elasticPageId: input.pageId, + itemUrl: input.itemUrl, + reportTypes: [ReportType.Abusive], + reportComment: input.reportComment, + }) return !!result } diff --git a/packages/api/src/services/user_device_tokens.ts b/packages/api/src/services/user_device_tokens.ts index 259da1708..66942d380 100644 --- a/packages/api/src/services/user_device_tokens.ts +++ b/packages/api/src/services/user_device_tokens.ts @@ -1,34 +1,36 @@ -import { getManager, getRepository } from 'typeorm' import { UserDeviceToken } from '../entity/user_device_tokens' import { User } from '../entity/user' import { SetDeviceTokenErrorCode } from '../generated/graphql' -import { setClaims } from '../entity/utils' +import { getRepository, setClaims } from '../entity/utils' import { analytics } from '../utils/analytics' import { env } from '../env' +import { AppDataSource } from '../server' export const getDeviceToken = async ( id: string -): Promise => { - return getRepository(UserDeviceToken).findOne(id) +): Promise => { + return getRepository(UserDeviceToken).findOneBy({ id }) } export const getDeviceTokenByToken = async ( token: string -): Promise => { - return getRepository(UserDeviceToken).findOne({ token }) +): Promise => { + return getRepository(UserDeviceToken).findOneBy({ token }) } export const getDeviceTokensByUserId = async ( userId: string ): Promise => { - return getRepository(UserDeviceToken).find({ where: { user: userId } }) + return getRepository(UserDeviceToken).find({ + where: { user: { id: userId } }, + }) } export const createDeviceToken = async ( userId: string, token: string ): Promise => { - const user = await getRepository(User).findOne(userId) + const user = await getRepository(User).findOneBy({ id: userId }) if (!user) { return Promise.reject({ errorCode: SetDeviceTokenErrorCode.Unauthorized, @@ -43,19 +45,17 @@ export const createDeviceToken = async ( }, }) - return getRepository(UserDeviceToken) - .create({ - token: token, - user: user, - }) - .save() + return getRepository(UserDeviceToken).save({ + token: token, + user: user, + }) } export const deleteDeviceToken = async ( id: string, userId: string ): Promise => { - const user = await getRepository(User).findOne(userId) + const user = await getRepository(User).findOneBy({ id: userId }) if (!user) { return Promise.reject({ errorCode: SetDeviceTokenErrorCode.Unauthorized, @@ -70,7 +70,7 @@ export const deleteDeviceToken = async ( }, }) - return getManager().transaction(async (t) => { + return AppDataSource.transaction(async (t) => { await setClaims(t, userId) const result = await t.getRepository(UserDeviceToken).delete(id) diff --git a/packages/api/test/db.ts b/packages/api/test/db.ts index b92af6ead..182248196 100644 --- a/packages/api/test/db.ts +++ b/packages/api/test/db.ts @@ -1,13 +1,5 @@ -import { - createConnection, - getConnection, - getManager, - getRepository, -} from 'typeorm' -import { SnakeNamingStrategy } from 'typeorm-naming-strategies' import Postgrator from 'postgrator' import { User } from '../src/entity/user' -import { createUser } from '../src/services/create_user' import { Profile } from '../src/entity/profile' import { Page } from '../src/entity/page' import { Link } from '../src/entity/link' @@ -15,6 +7,10 @@ import { Reminder } from '../src/entity/reminder' import { NewsletterEmail } from '../src/entity/newsletter_email' import { UserDeviceToken } from '../src/entity/user_device_tokens' import { Label } from '../src/entity/label' +import { AppDataSource } from '../src/server' +import { getRepository } from '../src/entity/utils' +import { createUser } from '../src/services/create_user' +import { SnakeNamingStrategy } from 'typeorm-naming-strategies' const runMigrations = async () => { const migrationDirectory = __dirname + '/../../db/migrations' @@ -43,8 +39,10 @@ const runMigrations = async () => { } } -const createEntityConnection = async (): Promise => { - await createConnection({ +export const createTestConnection = async (): Promise => { + await runMigrations() + + AppDataSource.setOptions({ type: 'postgres', host: process.env.PG_HOST, port: Number(process.env.PG_PORT), @@ -57,21 +55,11 @@ const createEntityConnection = async (): Promise => { subscribers: [__dirname + '/../src/events/**/*{.js,.ts}'], namingStrategy: new SnakeNamingStrategy(), }) -} - -export const createTestConnection = async (): Promise => { - try { - getConnection() - // eslint-disable-next-line no-empty - } catch (error) {} - - await runMigrations() - await createEntityConnection() + await AppDataSource.initialize() } export const deleteTestUser = async (name: string) => { - await getConnection() - .createQueryBuilder() + await AppDataSource.createQueryBuilder() .delete() .from(User) .where({ email: `${name}@fake.com` }) @@ -93,68 +81,58 @@ export const createTestUser = async ( inviteCode: invite, password: password, }) + return newUser } export const createUserWithoutProfile = async (name: string): Promise => { - return getManager() - .getRepository(User) - .create({ - source: 'GOOGLE', - sourceUserId: 'fake-user-id-' + name, - email: `${name}@fake.com`, - name: name, - }) - .save() + return getRepository(User).save({ + source: 'GOOGLE', + sourceUserId: 'fake-user-id-' + name, + email: `${name}@fake.com`, + name: name, + }) } -export const getProfile = async (user: User): Promise => { - return Profile.findOne({ where: { user: user } }) +export const getProfile = async (user: User): Promise => { + return getRepository(Profile).findOneBy({ user: { id: user.id } }) } export const createTestPage = async (): Promise => { - return getRepository(Page) - .create({ - originalHtml: 'html', - content: 'Test content', - description: 'Test description', - title: 'Test title', - author: 'Test author', - url: 'Test url', - hash: 'Test hash', - }) - .save() + return getRepository(Page).save({ + originalHtml: 'html', + content: 'Test content', + description: 'Test description', + title: 'Test title', + author: 'Test author', + url: 'Test url', + hash: 'Test hash', + }) } export const createTestLink = async (user: User, page: Page): Promise => { - return getRepository(Link) - .create({ - user: user, - page: page, - slug: 'Test slug', - articleUrl: 'Test url', - articleHash: 'Test hash', - }) - .save() + return getRepository(Link).save({ + user: user, + page: page, + slug: 'Test slug', + articleUrl: 'Test url', + articleHash: 'Test hash', + }) } export const createTestReminder = async ( user: User, link?: string ): Promise => { - return getRepository(Reminder) - .create({ - user: user, - link: link, - remindAt: new Date(), - }) - .save() + return getRepository(Reminder).save({ + user: user, + link: link, + remindAt: new Date(), + }) } -export const getReminder = async ( - id: string -): Promise => { - return getRepository(Reminder).findOne(id) +export const getReminder = async (id: string): Promise => { + return getRepository(Reminder).findOneBy({ id }) } export const createTestNewsletterEmail = async ( @@ -162,44 +140,40 @@ export const createTestNewsletterEmail = async ( emailAddress?: string, confirmationCode?: string ): Promise => { - return getRepository(NewsletterEmail) - .create({ - user: user, - address: emailAddress, - confirmationCode: confirmationCode, - }) - .save() + return getRepository(NewsletterEmail).save({ + user: user, + address: emailAddress, + confirmationCode: confirmationCode, + }) } export const getNewsletterEmail = async ( id: string -): Promise => { - return getRepository(NewsletterEmail).findOne(id) +): Promise => { + return getRepository(NewsletterEmail).findOneBy({ id }) } export const createTestDeviceToken = async ( user: User ): Promise => { - return getRepository(UserDeviceToken) - .create({ - user: user, - token: 'Test token', - }) - .save() + return getRepository(UserDeviceToken).save({ + user: user, + token: 'Test token', + }) } export const getDeviceToken = async ( id: string -): Promise => { - return getRepository(UserDeviceToken).findOne(id) +): Promise => { + return getRepository(UserDeviceToken).findOneBy({ id }) } -export const getUser = async (id: string): Promise => { - return getRepository(User).findOne(id) +export const getUser = async (id: string): Promise => { + return getRepository(User).findOneBy({ id }) } -export const getLink = async (id: string): Promise => { - return getRepository(Link).findOne(id) +export const getLink = async (id: string): Promise => { + return getRepository(Link).findOneBy({ id }) } export const createTestLabel = async ( @@ -207,11 +181,9 @@ export const createTestLabel = async ( name: string, color: string ): Promise