Skip to content

Instantly share code, notes, and snippets.

@birkir
Created January 18, 2019 15:14
Show Gist options
  • Save birkir/594a8cfbecb7bd5a18479de081f20483 to your computer and use it in GitHub Desktop.
Save birkir/594a8cfbecb7bd5a18479de081f20483 to your computer and use it in GitHub Desktop.
@accounts/typeorm - Typeorm database implementation for @accounts
import { ConnectionInformations, CreateUser, DatabaseInterface, Session } from '@accounts/types';
import { Repository, getRepository } from 'typeorm';
import { User } from '../entity/User';
import { UserEmail } from '../entity/UserEmail';
import { UserService } from '../entity/UserService';
import { UserSession } from '../entity/UserSession';
type ISession = Session & UserSession;
export class Typeorm implements DatabaseInterface {
private userRepository: Repository<User>;
private serviceRepository: Repository<UserService>;
private emailRepository: Repository<UserEmail>;
private sessionRepository: Repository<UserSession>;
constructor() {
this.userRepository = getRepository(User);
this.serviceRepository = getRepository(UserService);
this.emailRepository = getRepository(UserEmail);
this.sessionRepository = getRepository(UserSession);
}
public async findUserByEmail(email: string): Promise<User | null> {
return this.userRepository.findOne({
where: { email },
});
}
public async findUserByUsername(username: string): Promise<User | null> {
return this.userRepository.findOne({
where: { username },
});
}
public async findUserById(userId: string): Promise<User | null> {
const user = await this.userRepository.findOne(userId);
if (!user) {
throw new Error('User not found');
}
return user;
}
public async findUserByResetPasswordToken(token: string): Promise<User | null> {
const service = await this.serviceRepository.findOne({
where: {
name: 'password.reset',
token,
},
});
return service.user;
}
public async findUserByEmailVerificationToken(token: string): Promise<User | null> {
const service = await this.serviceRepository.findOne({
where: {
name: 'email.verification',
token,
},
});
return service.user;
}
public async createUser(createUser: CreateUser): Promise<string> {
const { username, email, password } = createUser;
const user = new User();
if (email) {
const userEmail = new UserEmail();
userEmail.address = email;
userEmail.verified = false;
this.emailRepository.save(userEmail);
user.emails = [userEmail];
}
if (password) {
const userService = new UserService();
userService.name = 'password';
userService.options = { bcrypt: password };
await this.serviceRepository.save(userService);
user.services = [userService];
}
if (username) {
user.username = username;
}
await this.userRepository.save(user);
return user.id;
}
public async setUsername(userId: string, newUsername: string): Promise<void> {
const user = await this.findUserById(userId);
user.username = newUsername;
await this.userRepository.save(user);
}
public async setProfile(userId: string, profile: object): Promise<object> {
const user = await this.findUserById(userId);
user.profile = profile;
await this.userRepository.save(user);
return profile;
}
public async findUserByServiceId(serviceName: string, serviceId: string): Promise<User | null> {
const service = await this.serviceRepository.findOne({
name: serviceName,
id: serviceId,
});
if (service) {
return service.user;
}
}
public async setService(userId: string, serviceName: string, data: object): Promise<void> {
const user = await this.findUserById(userId);
const service = user.services.find((s) => s.name === serviceName);
if (service) {
service.options = data;
await this.serviceRepository.save(service); // @todo is this needed?
await this.userRepository.save(user);
}
}
public async unsetService(userId: string, serviceName: string): Promise<void> {
const user = await this.findUserById(userId);
const service = user.services && user.services.find((s) => s.name === serviceName);
if (service) {
await this.serviceRepository.remove(service);
await this.userRepository.save(user);
}
}
public async findPasswordHash(userId: string): Promise<string | null> {
const user = await this.findUserById(userId);
const service = user.services && user.services.find((s) => s.name === 'password');
return service.options && service.options.bcrypt;
return null;
}
public async setPassword(userId: string, newPassword: string): Promise<void> {
const user = await this.findUserById(userId);
const service = user.services && user.services.find((s) => s.name === 'password');
service.options = { bcrypt: newPassword };
await this.serviceRepository.save(service);
await this.userRepository.save(user);
}
public async addResetPasswordToken(userId: string, email: string, token: string, reason: string): Promise<void> {
const user = await this.findUserById(userId);
const service = new UserService();
service.name = 'password.reset';
service.token = token;
service.options = {
email: email.toLocaleLowerCase(),
when: (new Date()).toJSON(),
reason,
};
await this.serviceRepository.save(service);
user.services.push(service);
await this.userRepository.save(user);
}
public async setResetPassword(userId: string, email: string, newPassword: string, token: string): Promise<void> {
await this.setPassword(userId, newPassword);
}
public async addEmail(userId: string, newEmail: string, verified: boolean): Promise<void> {
const user = await this.findUserById(userId);
const userEmail = new UserEmail();
userEmail.address = newEmail;
userEmail.verified = verified;
user.emails.push(userEmail);
await this.userRepository.save(user);
}
public async removeEmail(userId: string, email: string): Promise<void> {
const user = await this.findUserById(userId);
const userEmail = user.emails.find((s) => s.address === email);
if (!userEmail) {
throw new Error('Email not found');
}
await this.emailRepository.remove(userEmail);
}
public async verifyEmail(userId: string, email: string): Promise<void> {
const user = await this.findUserById(userId);
const userEmail = user.emails.find((s) => s.address === email);
if (!userEmail) {
throw new Error('Email not found');
}
userEmail.verified = true;
await this.emailRepository.save(userEmail);
}
public async addEmailVerificationToken(userId: string, email: string, token: string): Promise<void> {
const user = await this.findUserById(userId);
const service = new UserService();
service.token = token;
service.name = 'email.verification';
await this.serviceRepository.save(service);
user.services.push(service);
await this.userRepository.save(user);
}
public async setUserDeactivated(userId: string, deactivated: boolean): Promise<void> {
const user = await this.findUserById(userId);
user.deactivated = deactivated;
await this.userRepository.save(user);
}
public findSessionById(sessionId: string): Promise<Session | null> {
return this.sessionRepository.findOne(sessionId) as Promise<ISession>;
}
public findSessionByToken(token: string): Promise<ISession | null> {
return this.sessionRepository.findOne({ token }) as Promise<ISession>;
}
public async createSession(
userId: string,
token: string,
connection: ConnectionInformations,
extra?: object,
): Promise<string> {
const user = await this.findUserById(userId);
const session = new UserSession();
session.token = token;
session.userAgent = connection.userAgent;
session.ip = connection.ip;
session.extra = extra;
session.valid = true;
await this.sessionRepository.save(session);
user.sessions.push(session);
await this.userRepository.save(user);
return session.id;
}
public async updateSession(sessionId: string, connection: ConnectionInformations): Promise<void> {
const session = await this.findSessionById(sessionId);
session.userAgent = connection.userAgent;
session.ip = connection.ip;
await this.sessionRepository.save(session);
}
public async invalidateSession(sessionId: string): Promise<void> {
const session = await this.findSessionById(sessionId);
session.valid = false;
await this.sessionRepository.save(session);
}
public async invalidateAllSessions(userId: string): Promise<void> {
const user = await this.findUserById(userId);
await Promise.all(
user.sessions.map((session) =>
this.sessionRepository.delete(session),
),
);
}
}
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { ObjectType, Field } from 'typegql';
import { GraphQLDateTime } from 'graphql-iso-date';
import { UserService } from './UserService';
import { UserEmail } from './UserEmail';
import { UserSession } from './UserSession';
@Entity()
@ObjectType()
export class User {
@PrimaryGeneratedColumn('uuid')
@Field()
public id: string;
@Column()
public username: string;
@Column('jsonb', { nullable: true })
public profile: any;
@OneToMany(() => UserService, (userService) => userService.user, { eager: true })
public services: UserService[];
@OneToMany(() => UserEmail, (userEmail) => userEmail.user, { eager: true })
public emails: UserEmail[];
@OneToMany(() => UserSession, (userSession) => userSession.user, { eager: true })
public sessions: UserSession[];
@Column({ default: false })
public deactivated: boolean;
@CreateDateColumn()
@Field({ type: GraphQLDateTime })
public createdAt: Date;
@UpdateDateColumn()
@Field({ type: GraphQLDateTime })
public updatedAt: Date;
}
import { Entity, Column, ManyToOne, Unique, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './User';
@Entity()
export class UserEmail {
@PrimaryGeneratedColumn('uuid')
public id: string;
@ManyToOne(() => User, (user) => user.emails)
public user: Promise<User>;
@Unique(['address'])
@Column()
public address: string;
@Column({ default: false })
public verified: boolean;
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './User';
@Entity()
export class UserService {
@PrimaryGeneratedColumn('uuid')
public id: string;
@ManyToOne(() => User, (user) => user.services)
public user: Promise<User>;
@Column()
public name: string;
@Column({ nullable: true })
public token: string;
@Column('jsonb')
public options: { bcrypt: string } | any;
}
import { Entity, Column, ManyToOne, Unique, PrimaryGeneratedColumn, UpdateDateColumn, CreateDateColumn } from 'typeorm';
import { User } from './User';
@Entity()
export class UserSession {
@PrimaryGeneratedColumn('uuid')
public id: string;
@ManyToOne(() => User, (user) => user.sessions)
public user: Promise<User>;
public userId: string;
@Column()
public token: string;
@Column()
public valid: boolean;
@Column({ nullable: true })
public userAgent: string;
@Column({ nullable: true })
public ip: string;
@Column('jsonb')
public extra: object;
@CreateDateColumn()
public createdAt: Date;
@UpdateDateColumn()
public updatedAt: Date;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment