|
// domain models utilizing entities and value objects: |
|
// - entities are globally identifiable and are mutable |
|
// - value objects are non-identifiable other than by their state and are immutable |
|
// they would require some additional work to really make them immutable here, but this demonstrates the premise |
|
|
|
export class User { |
|
constructor( |
|
public readonly bungieAccount: BungieAccount, |
|
public readonly destinyCharacter: DestinyCharacter, |
|
public readonly socialConnector: SocialConnector, |
|
) { } |
|
} |
|
|
|
export class BungieAccount { |
|
constructor( |
|
public readonly id: string, |
|
public readonly username: string, |
|
public readonly refreshToken: JsonWebToken, |
|
) { } |
|
} |
|
|
|
export class JsonWebToken { |
|
constructor( |
|
public readonly header: Map<string, any>, |
|
public readonly payload: Map<string, any>, |
|
public readonly signature: string, |
|
public readonly expiryDate: Date, |
|
public readonly rawToken: string, |
|
) { } |
|
|
|
fromRawToken(rawToken: string): JsonWebToken { |
|
// here "jwt" is some helper library for parsing JWTs |
|
const token = jwt.parse(rawToken); |
|
|
|
return new JsonWebToken( |
|
token.header, |
|
token.payload, |
|
token.signature, |
|
new Date(token.payload.get('exp')), |
|
rawToken, |
|
); |
|
} |
|
} |
|
|
|
export class DestinyCharacter { |
|
constructor( |
|
public readonly id: string, |
|
public readonly accountId: string, |
|
) { } |
|
} |
|
|
|
export class SocialConnector { |
|
constructor( |
|
public readonly id: string, |
|
public readonly channelId: string, |
|
) { } |
|
} |
|
|
|
// using repository to load a User |
|
|
|
interface Logger { |
|
log: (...args: any[]) => void; |
|
debug: (...args: any[]) => void; |
|
error: (...args: any[]) => void; |
|
} |
|
|
|
interface MongooseSchema<T> { |
|
exists(criteria: Partial<T>): Command<boolean>; |
|
updateOne(criteria: Partial<T>, operation: MongooseOperation<T>, callback: (error?: Error) => any): Promise<void>; |
|
} |
|
|
|
interface MongooseOperation<T> { |
|
$set: Partial<T>; |
|
} |
|
|
|
interface Command<T> { |
|
exec(): Promise<T>; |
|
} |
|
|
|
interface UserSchema { |
|
bungie_membership_id: string; |
|
bungie_username: string; |
|
destiny_character_id: string; |
|
destiny_id: string; |
|
discord_channel_id: string; |
|
discord_id: string; |
|
refresh_expiration: string; |
|
refresh_token: string; |
|
} |
|
|
|
interface PersistenceObject { |
|
save(): Promise<void>; |
|
} |
|
|
|
type PersistenceObjectFactory<T> = (model: T) => PersistenceObject; |
|
|
|
interface IRepository<T> { |
|
add(model: T): Promise<void>; |
|
update(model: T): Promise<void>; |
|
delete(model: T): Promise<void>; |
|
} |
|
|
|
interface IUserRepository extends IRepository<User> { |
|
existsByUsername(username: string): Promise<boolean>; |
|
} |
|
|
|
export class UserRepository implements IUserRepository { |
|
constructor( |
|
private readonly schema: MongooseSchema<UserSchema>, |
|
private readonly factory: PersistenceObjectFactory<User>, |
|
private readonly logger: Logger, |
|
) { } |
|
|
|
async add(user: User): Promise<void> { |
|
const userModel = this.factory(user); |
|
|
|
try { |
|
await userModel.save(); |
|
} catch (error) { |
|
this.addErrorHandler(error); |
|
} |
|
} |
|
|
|
async update(user: User): Promise<void> { |
|
const updateModel = { |
|
destiny_id: user.destinyCharacter.accountId, |
|
destiny_character_id: user.destinyCharacter.id, |
|
refresh_expiration: user.bungieAccount.refreshToken.expiryDate.toISOString(), |
|
refresh_token: user.bungieAccount.refreshToken.rawToken, |
|
}; |
|
|
|
try { |
|
await this.schema.updateOne( |
|
{ bungie_membership_id: user.bungieAccount.id }, |
|
{ $set: updateModel }, |
|
this.updateErrorHandler.bind(this), |
|
) |
|
} catch (error) { |
|
throw error; |
|
} |
|
} |
|
|
|
async delete(user: User) { |
|
// TODO: Implement |
|
} |
|
|
|
async existsByUsername(username: string): Promise<boolean> { |
|
return await this.schema.exists({ bungie_username: username }).exec() ? true : false; |
|
} |
|
|
|
private addErrorHandler(error: Error): void { |
|
this.logger.debug('Adding user failed'); |
|
} |
|
|
|
private updateErrorHandler(error: Error): void { |
|
if (error) { |
|
this.logger.debug('Updating user record failed'); |
|
throw error; |
|
} else { |
|
this.logger.debug('Updated user record'); |
|
} |
|
} |
|
} |