Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix(models): move User to DBUser #3020

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { expect, describe, it, vi, beforeAll } from 'vitest';
import type { Request, NextFunction } from 'express';
import type { User } from '@nangohq/shared';
import { NangoError } from '@nangohq/shared';
import db, { multipleMigrations } from '@nangohq/database';
import configController from './config.controller';
import type { RequestLocals } from '../utils/express.js';
import type { DBTeam, DBEnvironment, ConnectSession } from '@nangohq/types';
import type { DBTeam, DBEnvironment, ConnectSession, DBUser } from '@nangohq/types';

const locals: Required<RequestLocals> = {
authType: 'secretKey',
account: { id: 0 } as DBTeam,
environment: { id: 1 } as DBEnvironment,
user: { id: 0 } as User,
user: { id: 0 } as DBUser,
connectSession: { id: 0 } as ConnectSession
};
/**
Expand Down
9 changes: 4 additions & 5 deletions packages/server/lib/helpers/email.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { basePublicUrl } from '@nangohq/utils';
import { EmailClient } from '../clients/email.client.js';
import type { User } from '@nangohq/shared';
import type { DBInvitation, DBTeam } from '@nangohq/types';
import type { DBInvitation, DBTeam, DBUser } from '@nangohq/types';

export function sendVerificationEmail(email: string, name: string, token: string) {
const emailClient = EmailClient.getInstance();
Expand All @@ -21,7 +20,7 @@ Team Nango</p>
);
}

export async function sendResetPasswordEmail({ user, token }: { user: User; token: string }) {
export async function sendResetPasswordEmail({ user, token }: { user: DBUser; token: string }) {
const emailClient = EmailClient.getInstance();
await emailClient.send(
user.email,
Expand All @@ -46,13 +45,13 @@ export async function sendInviteEmail({
}: {
email: string;
account: DBTeam;
user: Pick<User, 'name'>;
user: Pick<DBUser, 'name'>;
invitation: DBInvitation;
}) {
const emailClient = EmailClient.getInstance();
await emailClient.send(
email,
`Youre Invited! Join "${account.name}" on Nango`,
`You're Invited! Join "${account.name}" on Nango`,
`<p>Hi,</p>

<p>${user.name} invites you to join "${account.name}" on Nango.</p>
Expand Down
5 changes: 2 additions & 3 deletions packages/server/lib/utils/express.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { User } from '@nangohq/shared';
import type { DBEnvironment, DBTeam, ConnectSession } from '@nangohq/types';
import type { DBEnvironment, DBTeam, ConnectSession, DBUser } from '@nangohq/types';

export interface RequestLocals {
authType?: 'secretKey' | 'publicKey' | 'basic' | 'adminKey' | 'none' | 'session' | 'connectSession';
user?: Pick<User, 'id' | 'email' | 'name'>;
user?: Pick<DBUser, 'id' | 'email' | 'name'>;
account?: DBTeam;
environment?: DBEnvironment;
connectSession?: ConnectSession;
Expand Down
6 changes: 2 additions & 4 deletions packages/server/lib/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import type { Request } from 'express';
import type { User } from '@nangohq/shared';
import type { Provider, ProviderTwoStep } from '@nangohq/types';
import type { DBUser, Provider, ProviderTwoStep } from '@nangohq/types';
import type { Result } from '@nangohq/utils';
import { getLogger, Err, Ok } from '@nangohq/utils';
import type { WSErr } from './web-socket-error.js';
Expand All @@ -11,7 +10,7 @@ import { OrchestratorClient } from '@nangohq/nango-orchestrator';

const logger = getLogger('Server.Utils');

export async function getUserFromSession(req: Request<any>): Promise<Result<User, NangoError>> {
export async function getUserFromSession(req: Request<any>): Promise<Result<DBUser, NangoError>> {
const sessionUser = req.user;
if (!sessionUser) {
const error = new NangoError('user_not_found');
Expand All @@ -20,7 +19,6 @@ export async function getUserFromSession(req: Request<any>): Promise<Result<User
}

const user = await userService.getUserById(sessionUser.id);

if (!user) {
const error = new NangoError('user_not_found');
return Err(error);
Expand Down
17 changes: 0 additions & 17 deletions packages/shared/lib/models/Admin.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/shared/lib/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './Telemetry.js';
export * from './Admin.js';
export * from './Connection.js';
export * from './Generic.js';
export * from './Provider.js';
Expand Down
5 changes: 2 additions & 3 deletions packages/shared/lib/seeders/global.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { User } from '../models/index.js';
import { seedUser } from './user.seeder.js';
import { createEnvironmentSeed } from './environment.seeder.js';
import { createAccount } from './account.seeder.js';
import type { DBEnvironment, DBTeam } from '@nangohq/types';
import type { DBEnvironment, DBTeam, DBUser } from '@nangohq/types';

export async function seedAccountEnvAndUser(): Promise<{ account: DBTeam; env: DBEnvironment; user: User }> {
export async function seedAccountEnvAndUser(): Promise<{ account: DBTeam; env: DBEnvironment; user: DBUser }> {
const account = await createAccount();
const env = await createEnvironmentSeed(account.id, 'dev');
const user = await seedUser(account.id);
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/lib/seeders/user.seeder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { promisify } from 'node:util';
import crypto from 'node:crypto';
import { nanoid } from '@nangohq/utils';
import type { User } from '../models';
import userService from '../services/user.service.js';
import type { DBUser } from '@nangohq/types';

const promisePdkdf2 = promisify(crypto.pbkdf2);
export async function seedUser(accountId: number): Promise<User> {
export async function seedUser(accountId: number): Promise<DBUser> {
const uniqueId = nanoid();

const salt = crypto.randomBytes(16).toString('base64');
Expand Down
49 changes: 24 additions & 25 deletions packages/shared/lib/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@ import db from '@nangohq/database';
import * as uuid from 'uuid';
import type { Result } from '@nangohq/utils';
import { Ok, Err } from '@nangohq/utils';
import type { User } from '../models/Admin.js';
import type { DBUser } from '@nangohq/types';

const VERIFICATION_EMAIL_EXPIRATION = 3 * 24 * 60 * 60 * 1000;

class UserService {
async getUserById(id: number): Promise<User | null> {
const result = await db.knex.select<User>('*').from<User>(`_nango_users`).where({ id, suspended: false }).first();
async getUserById(id: number): Promise<DBUser | null> {
const result = await db.knex.select<DBUser>('*').from<DBUser>(`_nango_users`).where({ id, suspended: false }).first();

return result || null;
}

async getUserByUuid(uuid: string): Promise<User | null> {
const result = await db.knex.select('*').from<User>(`_nango_users`).where({ uuid }).first();
async getUserByUuid(uuid: string): Promise<DBUser | null> {
const result = await db.knex.select('*').from<DBUser>(`_nango_users`).where({ uuid }).first();

return result || null;
}
Expand All @@ -36,12 +35,12 @@ class UserService {
return Err(new Error('user_not_found'));
}

async refreshEmailVerificationToken(expiredToken: string): Promise<User | null> {
async refreshEmailVerificationToken(expiredToken: string): Promise<DBUser | null> {
const newToken = uuid.v4();
const expires_at = new Date(new Date().getTime() + VERIFICATION_EMAIL_EXPIRATION);

const result = await db.knex
.from<User>(`_nango_users`)
.from<DBUser>(`_nango_users`)
.where({ email_verification_token: expiredToken })
.update({
email_verification_token: newToken,
Expand All @@ -52,26 +51,26 @@ class UserService {
return result[0] || null;
}

async getUsersByAccountId(accountId: number): Promise<User[]> {
const result = await db.knex.select('*').from<User>(`_nango_users`).where({ account_id: accountId, suspended: false });
async getUsersByAccountId(accountId: number): Promise<DBUser[]> {
const result = await db.knex.select('*').from<DBUser>(`_nango_users`).where({ account_id: accountId, suspended: false });

return result;
}

async countUsers(accountId: number): Promise<number> {
const result = await db.knex
.select(db.knex.raw('COUNT(id) as total'))
.from<User>(`_nango_users`)
.from<DBUser>(`_nango_users`)
.where({ account_id: accountId, suspended: false })
.first();

return result.total || 0;
return result.total ? parseInt(result.total, 10) : 0;
}

async getAnUserByAccountId(accountId: number): Promise<User | null> {
async getAnUserByAccountId(accountId: number): Promise<DBUser | null> {
const result = await db.knex
.select('*')
.from<User>(`_nango_users`)
.from<DBUser>(`_nango_users`)
.where({
account_id: accountId,
suspended: false
Expand All @@ -86,14 +85,14 @@ class UserService {
return result[0];
}

async getUserByEmail(email: string): Promise<User | null> {
const result = await db.knex.select('*').from<User>(`_nango_users`).where({ email: email }).first();
async getUserByEmail(email: string): Promise<DBUser | null> {
const result = await db.knex.select('*').from<DBUser>(`_nango_users`).where({ email: email }).first();

return result || null;
}

async getUserByResetPasswordToken(link: string): Promise<User | null> {
const result = await db.knex.select('*').from<User>(`_nango_users`).where({ reset_password_token: link }).first();
async getUserByResetPasswordToken(link: string): Promise<DBUser | null> {
const result = await db.knex.select('*').from<DBUser>(`_nango_users`).where({ reset_password_token: link }).first();

return result || null;
}
Expand All @@ -112,10 +111,10 @@ class UserService {
salt?: string;
account_id: number;
email_verified: boolean;
}): Promise<User | null> {
}): Promise<DBUser | null> {
const expires_at = new Date(new Date().getTime() + VERIFICATION_EMAIL_EXPIRATION);
const result: Pick<User, 'id'>[] = await db.knex
.from<User>('_nango_users')
const result: Pick<DBUser, 'id'>[] = await db.knex
.from<DBUser>('_nango_users')
.insert({
email,
name,
Expand All @@ -136,28 +135,28 @@ class UserService {
return null;
}

async editUserPassword(user: User) {
return db.knex.from<User>(`_nango_users`).where({ id: user.id }).update({
async editUserPassword(user: Pick<DBUser, 'id' | 'reset_password_token' | 'hashed_password'>) {
return db.knex.from<DBUser>(`_nango_users`).where({ id: user.id }).update({
reset_password_token: user.reset_password_token,
hashed_password: user.hashed_password
});
}

async changePassword(newPassword: string, oldPassword: string, id: number) {
return db.knex.from<User>(`_nango_users`).where({ id }).update({
return db.knex.from<DBUser>(`_nango_users`).where({ id }).update({
hashed_password: newPassword,
salt: oldPassword
});
}

async suspendUser(id: number) {
if (id !== null && id !== undefined) {
await db.knex.from<User>(`_nango_users`).where({ id }).update({ suspended: true, suspended_at: new Date() });
await db.knex.from<DBUser>(`_nango_users`).where({ id }).update({ suspended: true, suspended_at: new Date() });
}
}

async verifyUserEmail(id: number) {
return db.knex.from<User>(`_nango_users`).where({ id }).update({ email_verified: true, email_verification_token: null });
return db.knex.from<DBUser>(`_nango_users`).where({ id }).update({ email_verified: true, email_verification_token: null });
}

async update({ id, ...data }: { id: number } & Omit<Partial<DBUser>, 'id'>): Promise<DBUser | null> {
Expand Down
6 changes: 2 additions & 4 deletions packages/shared/lib/utils/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import errorManager, { ErrorSourceEnum } from './error.manager.js';
import accountService from '../services/account.service.js';
import environmentService from '../services/environment.service.js';
import userService from '../services/user.service.js';
import type { User } from '../models/Admin.js';
import { LogActionEnum } from '../models/Telemetry.js';
import { NANGO_VERSION } from '../version.js';
import type { DBTeam } from '@nangohq/types';

export enum AnalyticsTypes {
ACCOUNT_CREATED = 'server:account_created',
Expand Down Expand Up @@ -133,9 +131,9 @@ class Analytics {
eventProperties['nango-server-version'] = this.packageVersion || 'unknown';

if (isCloud && accountId != null) {
const account: DBTeam | null = await accountService.getAccountById(accountId);
const account = await accountService.getAccountById(accountId);
if (account !== null && account.id !== undefined) {
const users: User[] = await userService.getUsersByAccountId(account.id);
const users = await userService.getUsersByAccountId(account.id);

if (users.length > 0) {
userProperties['email'] = users.map((user) => user.email).join(',');
Expand Down
2 changes: 1 addition & 1 deletion packages/types/lib/user/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface DBUser extends Timestamps {
hashed_password: string;
salt: string;
account_id: number;
reset_password_token: string | undefined;
reset_password_token: string | null;
suspended: boolean;
suspended_at: Date;
email_verified: boolean;
Expand Down
Loading