Change from TSyringe to Needle-DI.

This commit is contained in:
Bradley Shellnut 2024-11-11 17:14:20 -08:00
parent 9694e17844
commit 7770dd6843
46 changed files with 179 additions and 295 deletions

View file

@ -1,15 +1,14 @@
import 'reflect-metadata';
import {StatusCodes} from '$lib/constants/status-codes';
import {Controller} from '$lib/server/api/common/types/controller';
import {allCollections, getCollectionByCUID, numberOfCollections} from '$lib/server/api/controllers/collection.routes';
import {CollectionsService} from '$lib/server/api/services/collections.service';
import {openApi} from 'hono-zod-openapi';
import {inject, injectable} from 'tsyringe';
import { injectable, inject } from '@needle-di/core';
import {requireAuth} from '../middleware/require-auth.middleware';
@injectable()
export class CollectionController extends Controller {
constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {
constructor(private collectionsService = inject(CollectionsService)) {
super();
}

View file

@ -11,16 +11,16 @@ import {LoginRequestsService} from '$lib/server/api/services/loginrequest.servic
import {SessionsService} from '$lib/server/api/services/sessions.service';
import {zValidator} from '@hono/zod-validator';
import {openApi} from 'hono-zod-openapi';
import {inject, injectable} from 'tsyringe';
import { injectable, inject } from "@needle-di/core";
import {requireAuth} from '../middleware/require-auth.middleware';
import {iam, logout, updateEmail, updatePassword, updateProfile, verifyPassword} from './iam.routes';
@injectable()
export class IamController extends Controller {
constructor(
@inject(IamService) private readonly iamService: IamService,
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
@inject(SessionsService) private sessionsService: SessionsService,
private iamService = inject(IamService),
private loginRequestService = inject(LoginRequestsService),
private sessionsService = inject(SessionsService),
) {
super();
}

View file

@ -1,11 +1,10 @@
import 'reflect-metadata';
import {Controller} from '$lib/server/api/common/types/controller';
import {cookieExpiresAt, createSessionTokenCookie, setSessionCookie} from '$lib/server/api/common/utils/cookies';
import {signinUsernameDto} from '$lib/server/api/dtos/signin-username.dto';
import {SessionsService} from '$lib/server/api/services/sessions.service';
import {zValidator} from '@hono/zod-validator';
import {openApi} from 'hono-zod-openapi';
import {inject, injectable} from 'tsyringe';
import { inject, injectable } from '@needle-di/core';
import {limiter} from '../middleware/rate-limiter.middleware';
import {LoginRequestsService} from '../services/loginrequest.service';
import {signinUsername} from './login.routes';
@ -13,8 +12,8 @@ import {signinUsername} from './login.routes';
@injectable()
export class LoginController extends Controller {
constructor(
@inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService,
@inject(SessionsService) private luciaService: SessionsService,
private loginRequestsService = inject(LoginRequestsService),
private sessionsService = inject(SessionsService),
) {
super();
}

View file

@ -1,4 +1,3 @@
import 'reflect-metadata';
import {StatusCodes} from '$lib/constants/status-codes';
import {Controller} from '$lib/server/api/common/types/controller';
import {verifyTotpDto} from '$lib/server/api/dtos/verify-totp.dto';
@ -6,16 +5,16 @@ import {RecoveryCodesService} from '$lib/server/api/services/recovery-codes.serv
import {TotpService} from '$lib/server/api/services/totp.service';
import {UsersService} from '$lib/server/api/services/users.service';
import {zValidator} from '@hono/zod-validator';
import {inject, injectable} from 'tsyringe';
import { inject, injectable } from '@needle-di/core';
import {CredentialsType} from '../databases/postgres/tables';
import {requireAuth} from '../middleware/require-auth.middleware';
@injectable()
export class MfaController extends Controller {
constructor(
@inject(RecoveryCodesService) private readonly recoveryCodesService: RecoveryCodesService,
@inject(TotpService) private readonly totpService: TotpService,
@inject(UsersService) private readonly usersService: UsersService,
private recoveryCodesService = inject(RecoveryCodesService),
private totpService = inject(TotpService),
private usersService = inject(UsersService),
) {
super();
}

View file

@ -1,4 +1,3 @@
import 'reflect-metadata';
import {Controller} from '$lib/server/api/common/types/controller';
import type {OAuthUser} from '$lib/server/api/common/types/oauth';
import {cookieExpiresAt, createSessionTokenCookie, setSessionCookie} from '$lib/server/api/common/utils/cookies';
@ -7,14 +6,13 @@ import {SessionsService} from '$lib/server/api/services/sessions.service';
import {github, google} from '$lib/server/auth';
import {OAuth2RequestError} from 'arctic';
import {getCookie} from 'hono/cookie';
import {inject, injectable} from 'tsyringe';
import { injectable, inject } from "@needle-di/core";
@injectable()
export class OAuthController extends Controller {
constructor(
@inject(SessionsService) private sessionsService: SessionsService,
@inject(OAuthService) private oauthService: OAuthService,
private oauthService = inject(OAuthService),
private sessionsService = inject(SessionsService),
) {
super();
}

View file

@ -6,16 +6,15 @@ import {LoginRequestsService} from '$lib/server/api/services/loginrequest.servic
import {SessionsService} from '$lib/server/api/services/sessions.service';
import {UsersService} from '$lib/server/api/services/users.service';
import {zValidator} from '@hono/zod-validator';
import {setCookie} from 'hono/cookie';
import {TimeSpan} from 'oslo';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {cookieExpiresAt, createSessionTokenCookie, setSessionCookie} from "$lib/server/api/common/utils/cookies";
@injectable()
export class SignupController extends Controller {
constructor(
@inject(UsersService) private readonly usersService: UsersService,
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
@inject(SessionsService) private luciaService: SessionsService,
private usersService = inject(UsersService),
private loginRequestService = inject(LoginRequestsService),
private sessionsService = inject(SessionsService),
) {
super();
}
@ -36,20 +35,9 @@ export class SignupController extends Controller {
}
const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined);
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
console.log('set cookie', sessionCookie);
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,
maxAge:
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
? sessionCookie.attributes.maxAge
: new TimeSpan(2, 'w').seconds(),
domain: sessionCookie.attributes.domain,
sameSite: sessionCookie.attributes.sameSite as any,
secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires,
});
setSessionCookie(c, sessionCookie);
return c.json({ message: 'ok' });
});
}

View file

@ -1,12 +1,11 @@
import 'reflect-metadata';
import {Controller} from '$lib/server/api/common/types/controller';
import {UsersService} from '$lib/server/api/services/users.service';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {requireAuth} from '../middleware/require-auth.middleware';
@injectable()
export class UserController extends Controller {
constructor(@inject(UsersService) private readonly usersService: UsersService) {
constructor(private usersService = inject(UsersService)) {
super();
}

View file

@ -1,12 +1,11 @@
import 'reflect-metadata'
import {Controller} from '$lib/server/api/common/types/controller'
import {WishlistsService} from '$lib/server/api/services/wishlists.service'
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {requireAuth} from '../middleware/require-auth.middleware'
@injectable()
export class WishlistController extends Controller {
constructor(@inject(WishlistsService) private readonly wishlistsService: WishlistsService) {
constructor(private wishlistsService = inject(WishlistsService)) {
super()
}

View file

@ -9,7 +9,8 @@ import {WishlistController} from '$lib/server/api/controllers/wishlist.controlle
import {AuthCleanupJobs} from '$lib/server/api/jobs/auth-cleanup.job';
import {extendZodWithOpenApi} from 'hono-zod-openapi';
import {hc} from 'hono/client';
import {container} from 'tsyringe';
// import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import {z} from 'zod';
import {config} from './common/config';
import {IamController} from './controllers/iam.controller';
@ -17,20 +18,22 @@ import {LoginController} from './controllers/login.controller';
extendZodWithOpenApi(z);
const container = new Container();
export const app = createApp();
/* -------------------------------------------------------------------------- */
/* Routes */
/* -------------------------------------------------------------------------- */
const routes = app
.route('/me', container.resolve(IamController).routes())
.route('/user', container.resolve(UserController).routes())
.route('/login', container.resolve(LoginController).routes())
.route('/oauth', container.resolve(OAuthController).routes())
.route('/signup', container.resolve(SignupController).routes())
.route('/wishlists', container.resolve(WishlistController).routes())
.route('/collections', container.resolve(CollectionController).routes())
.route('/mfa', container.resolve(MfaController).routes())
.route('/me', container.get(IamController).routes())
.route('/user', container.get(UserController).routes())
.route('/login', container.get(LoginController).routes())
.route('/oauth', container.get(OAuthController).routes())
.route('/signup', container.get(SignupController).routes())
.route('/wishlists', container.get(WishlistController).routes())
.route('/collections', container.get(CollectionController).routes())
.route('/mfa', container.get(MfaController).routes())
.get('/', (c) => c.json({ message: 'Server is healthy' }));
configureOpenAPI(app);
@ -38,8 +41,8 @@ configureOpenAPI(app);
/* -------------------------------------------------------------------------- */
/* Cron Jobs */
/* -------------------------------------------------------------------------- */
container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests();
container.resolve(AuthCleanupJobs).deleteStaleLoginRequests();
container.get(AuthCleanupJobs).deleteStaleEmailVerificationRequests();
container.get(AuthCleanupJobs).deleteStaleLoginRequests();
/* -------------------------------------------------------------------------- */
/* Exports */

View file

@ -1,11 +1,11 @@
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {JobsService} from '../services/jobs.service'
@injectable()
export class AuthCleanupJobs {
private queue
constructor(@inject(JobsService) private jobsService: JobsService) {
constructor(private jobsService = inject(JobsService)) {
/* ------------------------------ Create Queue ------------------------------ */
this.queue = this.jobsService.createQueue('test')

View file

@ -1,4 +1,3 @@
import 'reflect-metadata';
import {
cookieExpiresAt,
cookieName,
@ -12,11 +11,12 @@ import type {MiddlewareHandler} from 'hono';
import {getCookie} from 'hono/cookie';
import {createMiddleware} from 'hono/factory';
import {verifyRequestOrigin} from 'oslo/request';
import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import type {AppBindings} from '../common/types/hono';
// resolve dependencies from the container
const sessionService = container.resolve(SessionsService);
const container = new Container();
const sessionService = container.get(SessionsService);
// CSRF protection middleware
export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {

View file

@ -1,11 +1,12 @@
import {rateLimiter} from 'hono-rate-limiter';
import {RedisStore} from 'rate-limit-redis';
import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import type {AppBindings} from '../common/types/hono';
import {RedisService} from '../services/redis.service';
const container = new Container();
// resolve dependencies from the container
const { client } = container.resolve(RedisService);
const { client } = container.get(RedisService);
export function limiter({
limit,

View file

@ -1,11 +0,0 @@
import {container} from 'tsyringe'
import {db} from '../packages/drizzle'
// Symbol
export const DatabaseProvider = Symbol('DATABASE_TOKEN')
// Type
export type DatabaseProvider = typeof db
// Register
container.register<DatabaseProvider>(DatabaseProvider, { useValue: db })

View file

@ -1,11 +0,0 @@
import {container} from 'tsyringe'
import {lucia} from '../packages/lucia'
// Symbol
export const LuciaProvider = Symbol('LUCIA_PROVIDER')
// Type
export type LuciaProvider = typeof lucia
// Register
container.register<LuciaProvider>(LuciaProvider, { useValue: lucia })

View file

@ -1,12 +0,0 @@
import RedisClient from 'ioredis'
import {container} from 'tsyringe'
import {config} from '../common/config'
export const RedisProvider = Symbol('REDIS_TOKEN')
export type RedisProvider = RedisClient
container.register<RedisProvider>(RedisProvider, {
useValue: new RedisClient(config.redis.url, {
maxRetriesPerRequest: null,
}),
})

View file

@ -1,7 +1,7 @@
import {takeFirstOrThrow} from '$lib/server/api/common/utils/repository';
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {collections} from '../databases/postgres/tables';
export type CreateCollection = InferInsertModel<typeof collections>;
@ -9,7 +9,7 @@ export type UpdateCollection = Partial<CreateCollection>;
@injectable()
export class CollectionsRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findAll(db = this.drizzle.db) {
return db.query.collections.findMany();

View file

@ -1,8 +1,7 @@
import 'reflect-metadata';
import {credentialsTable, CredentialsType} from '$lib/server/api/databases/postgres/tables/credentials.table';
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {and, eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
export type CreateCredentials = InferInsertModel<typeof credentialsTable>;
@ -11,7 +10,7 @@ export type DeleteCredentials = Pick<CreateCredentials, 'id'>;
@injectable()
export class CredentialsRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findOneByUserId(userId: string, db = this.drizzle.db) {
return db.query.credentialsTable.findFirst({

View file

@ -1,5 +1,5 @@
import {and, eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
import {federatedIdentityTable} from '../databases/postgres/tables';
import {DrizzleService} from '../services/drizzle.service';
@ -8,7 +8,7 @@ export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityT
@injectable()
export class FederatedIdentityRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findOneByUserIdAndProvider(userId: string, provider: string) {
return this.drizzle.db.query.federatedIdentityTable.findFirst({

View file

@ -1,15 +1,14 @@
import 'reflect-metadata';
import {takeFirstOrThrow} from '$lib/server/api/common/utils/repository';
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {recoveryCodesTable} from '../databases/postgres/tables';
export type CreateRecoveryCodes = InferInsertModel<typeof recoveryCodesTable>;
@injectable()
export class RecoveryCodesRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async create(data: CreateRecoveryCodes, db = this.drizzle.db) {
return db.insert(recoveryCodesTable).values(data).returning().then(takeFirstOrThrow);

View file

@ -1,31 +1,15 @@
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
import {rolesTable} from '../databases/postgres/tables';
/* -------------------------------------------------------------------------- */
/* Repository */
/* -------------------------------------------------------------------------- */
/* ---------------------------------- About --------------------------------- */
/*
Repositories are the layer that interacts with the database. They are responsible for retrieving and
storing data. They should not contain any business logic, only database queries.
*/
/* ---------------------------------- Notes --------------------------------- */
/*
Repositories should only contain methods for CRUD operations and any other database interactions.
Any complex logic should be delegated to a service. If a repository method requires a transaction,
it should be passed in as an argument or the class should have a method to set the transaction.
In our case the method 'trxHost' is used to set the transaction context.
*/
export type CreateRole = InferInsertModel<typeof rolesTable>;
export type UpdateRole = Partial<CreateRole>;
@injectable()
export class RolesRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findOneById(id: string, db = this.drizzle.db) {
return db.query.rolesTable.findFirst({

View file

@ -1,15 +1,14 @@
import 'reflect-metadata';
import {takeFirstOrThrow} from '$lib/server/api/common/utils/repository';
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {sessionsTable, usersTable} from '../databases/postgres/tables';
export type CreateSession = InferInsertModel<typeof sessionsTable>;
@injectable()
export class SessionsRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async create(data: CreateSession, db = this.drizzle.db) {
return db.insert(sessionsTable).values(data).returning().then(takeFirstOrThrow);

View file

@ -1,31 +1,15 @@
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
import {user_roles} from '../databases/postgres/tables';
/* -------------------------------------------------------------------------- */
/* Repository */
/* -------------------------------------------------------------------------- */
/* ---------------------------------- About --------------------------------- */
/*
Repositories are the layer that interacts with the database. They are responsible for retrieving and
storing data. They should not contain any business logic, only database queries.
*/
/* ---------------------------------- Notes --------------------------------- */
/*
Repositories should only contain methods for CRUD operations and any other database interactions.
Any complex logic should be delegated to a service. If a repository method requires a transaction,
it should be passed in as an argument or the class should have a method to set the transaction.
In our case the method 'trxHost' is used to set the transaction context.
*/
export type CreateUserRole = InferInsertModel<typeof user_roles>;
export type UpdateUserRole = Partial<CreateUserRole>;
@injectable()
export class UserRolesRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findOneById(id: string, db = this.drizzle.db) {
return db.query.user_roles.findFirst({

View file

@ -1,31 +1,15 @@
import {usersTable} from '$lib/server/api/databases/postgres/tables/users.table';
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
/* -------------------------------------------------------------------------- */
/* Repository */
/* -------------------------------------------------------------------------- */
/* ---------------------------------- About --------------------------------- */
/*
Repositories are the layer that interacts with the database. They are responsible for retrieving and
storing data. They should not contain any business logic, only database queries.
*/
/* ---------------------------------- Notes --------------------------------- */
/*
Repositories should only contain methods for CRUD operations and any other database interactions.
Any complex logic should be delegated to a service. If a repository method requires a transaction,
it should be passed in as an argument or the class should have a method to set the transaction.
In our case the method 'trxHost' is used to set the transaction context.
*/
export type CreateUser = InferInsertModel<typeof usersTable>;
export type UpdateUser = Partial<CreateUser>;
@injectable()
export class UsersRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findOneById(id: string, db = this.drizzle.db) {
return db.query.usersTable.findFirst({

View file

@ -1,6 +1,6 @@
import {DrizzleService} from '$lib/server/api/services/drizzle.service';
import {eq, type InferInsertModel} from 'drizzle-orm';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {takeFirstOrThrow} from '../common/utils/repository';
import {wishlistsTable} from '../databases/postgres/tables';
@ -9,7 +9,7 @@ export type UpdateWishlist = Partial<CreateWishlist>;
@injectable()
export class WishlistsRepository {
constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {}
constructor(private drizzle = inject(DrizzleService)) {}
async findAll(db = this.drizzle.db) {
return db.query.wishlistsTable.findMany();

View file

@ -1,11 +1,11 @@
import type {db} from '$lib/server/api/packages/drizzle'
import {generateRandomAnimalName} from '$lib/utils/randomDataUtil'
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {CollectionsRepository} from '../repositories/collections.repository'
@injectable()
export class CollectionsService {
constructor(@inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository) {}
constructor(private collectionsRepository = inject(CollectionsRepository)) {}
async findOneByUserId(userId: string) {
return this.collectionsRepository.findOneByUserId(userId)

View file

@ -1,7 +1,6 @@
import 'reflect-metadata';
import {drizzle, type NodePgDatabase} from 'drizzle-orm/node-postgres';
import pg from 'pg';
import {type Disposable, injectable} from 'tsyringe';
import {type Disposable, injectable} from '@needle-di/core';
import {config} from '../common/config';
import * as schema from '../databases/postgres/tables';

View file

@ -1,7 +1,7 @@
import {scrypt} from 'node:crypto'
import {decodeHex, encodeHexLowerCase} from '@oslojs/encoding'
import {constantTimeEqual} from '@oslojs/crypto/subtle'
import {injectable} from 'tsyringe'
import {injectable} from '@needle-di/core'
@injectable()
export class HashingService {

View file

@ -4,30 +4,13 @@ import type {UpdateProfileDto} from '$lib/server/api/dtos/update-profile.dto';
import type {VerifyPasswordDto} from '$lib/server/api/dtos/verify-password.dto';
import {SessionsService} from '$lib/server/api/services/sessions.service';
import {UsersService} from '$lib/server/api/services/users.service';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
/* -------------------------------------------------------------------------- */
/* Service */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* ---------------------------------- About --------------------------------- */
/*
Services are responsible for handling business logic and data manipulation.
They generally call on repositories or other services to complete a use-case.
*/
/* ---------------------------------- Notes --------------------------------- */
/*
Services should be kept as clean and simple as possible.
Create private functions to handle complex logic and keep the public methods as
simple as possible. This makes the service easier to read, test and understand.
*/
/* -------------------------------------------------------------------------- */
@injectable()
export class IamService {
constructor(
@inject(SessionsService) private sessionsService: SessionsService,
@inject(UsersService) private readonly usersService: UsersService,
private readonly sessionsService = inject(SessionsService),
private readonly usersService = inject(UsersService),
) {}
async logout(sessionId: string) {

View file

@ -1,16 +1,25 @@
import {RedisProvider} from '$lib/server/api/providers/redis.provider'
import {type Processor, Queue, Worker} from 'bullmq'
import {inject, injectable} from 'tsyringe'
import RedisClient from 'ioredis';
import { config } from "../common/config";
import { injectable } from '@needle-di/core';
@injectable()
export class JobsService {
constructor(@inject(RedisProvider) private readonly redis: RedisProvider) {}
constructor() { }
createQueue(name: string) {
return new Queue(name, { connection: this.redis })
return new Queue(name, {
connection: new RedisClient(config.redis.url, {
maxRetriesPerRequest: null,
})
})
}
createWorker(name: string, processor: Processor) {
return new Worker(name, processor, { connection: this.redis })
return new Worker(name, processor, {
connection: new RedisClient(config.redis.url, {
maxRetriesPerRequest: null,
})
})
}
}

View file

@ -1,24 +1,24 @@
import type {SigninUsernameDto} from '$lib/server/api/dtos/signin-username.dto';
import {SessionsService} from '$lib/server/api/services/sessions.service';
import type {HonoRequest} from 'hono';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import {BadRequest} from '../common/exceptions';
import type {Credentials} from '../databases/postgres/tables';
import {DatabaseProvider} from '../providers/database.provider';
import {CredentialsRepository} from '../repositories/credentials.repository';
import {UsersRepository} from '../repositories/users.repository';
import {MailerService} from './mailer.service';
import {TokensService} from './tokens.service';
import {DrizzleService} from "$lib/server/api/services/drizzle.service";
@injectable()
export class LoginRequestsService {
constructor(
@inject(SessionsService) private sessionsService: SessionsService,
@inject(DatabaseProvider) private readonly db: DatabaseProvider,
@inject(TokensService) private readonly tokensService: TokensService,
@inject(MailerService) private readonly mailerService: MailerService,
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository,
private sessionsService = inject(SessionsService),
private drizzleService = inject(DrizzleService) ,
private tokensService = inject(TokensService) ,
private mailerService = inject(MailerService) ,
private usersRepository = inject(UsersRepository) ,
private credentialsRepository = inject(CredentialsRepository) ,
) {}
// async create(data: RegisterEmailDto) {
@ -73,7 +73,7 @@ export class LoginRequestsService {
// Create a new user and send a welcome email - or other onboarding process
private async handleNewUserRegistration(email: string) {
const newUser = await this.usersRepository.create({ email, verified: true });
this.mailerService.sendWelcome({ to: email, props: null });
// this.mailerService.sendWelcome({ to: email, props: null });
// TODO: add whatever onboarding process or extra data you need here
return newUser;
}

View file

@ -1,6 +1,6 @@
import {injectable} from 'tsyringe'
import {injectable} from '@needle-di/core'
import {config} from '../common/config'
import type {Email} from '../common/inferfaces/email.interface'
import type {Email} from "$lib/server/api/common/types/email";
type SendProps = {
to: string | string[]

View file

@ -1,4 +1,4 @@
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {FederatedIdentityRepository} from '../repositories/federated_identity.repository'
import {UsersService} from './users.service'
import type {OAuthProviders, OAuthUser} from "$lib/server/api/common/types/oauth";
@ -6,8 +6,8 @@ import type {OAuthProviders, OAuthUser} from "$lib/server/api/common/types/oauth
@injectable()
export class OAuthService {
constructor(
@inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository,
@inject(UsersService) private readonly usersService: UsersService,
private federatedIdentityRepository = inject(FederatedIdentityRepository),
private usersService = inject(UsersService),
) {}
async handleOAuthUser(oAuthUser: OAuthUser, oauthProvider: OAuthProviders) {

View file

@ -1,14 +1,13 @@
import 'reflect-metadata'
import {RecoveryCodesRepository} from '$lib/server/api/repositories/recovery-codes.repository'
import {alphabet, generateRandomString} from 'oslo/crypto'
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {HashingService} from './hashing.service'
@injectable()
export class RecoveryCodesService {
constructor(
@inject(HashingService) private readonly hashingService: HashingService,
@inject(RecoveryCodesRepository) private readonly recoveryCodesRepository: RecoveryCodesRepository
private hashingService = inject(HashingService),
private recoveryCodesRepository = inject(RecoveryCodesRepository),
) {}
async findAllRecoveryCodesByUserId(userId: string) {

View file

@ -1,6 +1,7 @@
import {config} from '$lib/server/api/common/config'
import {Redis} from 'ioredis'
import {type Disposable, injectable} from 'tsyringe'
import { injectable} from '@needle-di/core';
import type {Disposable} from 'tsyringe';
@injectable()
export class RedisService implements Disposable {

View file

@ -1,10 +1,10 @@
import {inject, injectable} from "tsyringe";
import {inject, injectable} from "@needle-di/core";
import {RolesRepository} from "$lib/server/api/repositories/roles.repository";
@injectable()
export class RolesService {
constructor(
@inject(RolesRepository) private readonly rolesRepository: RolesRepository
private rolesRepository = inject(RolesRepository)
) { }

View file

@ -3,7 +3,7 @@ import { UsersRepository } from '$lib/server/api/repositories/users.repository';
import { RedisService } from '$lib/server/api/services/redis.service';
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { inject, injectable } from 'tsyringe';
import { inject, injectable } from '@needle-di/core';
import type { Users } from '../databases/postgres/tables';
export type RedisSession = {
@ -31,8 +31,8 @@ export type SessionValidationResult = { session: Session; user: Users } | { sess
@injectable()
export class SessionsService {
constructor(
@inject(RedisService) private readonly redisService: RedisService,
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
private redisService = inject(RedisService),
private usersRepository = inject(UsersRepository),
) {}
generateSessionToken() {

View file

@ -1,11 +1,11 @@
import {inject, injectable} from "tsyringe";
import {inject, injectable} from "@needle-di/core";
import {generateRandomString} from "oslo/crypto";
import {createDate, TimeSpan, type TimeSpanUnit} from 'oslo';
import {HashingService} from "./hashing.service";
@injectable()
export class TokensService {
constructor(@inject(HashingService) private readonly hashingService: HashingService) { }
constructor(private hashingService = inject(HashingService)) { }
generateToken() {
const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I)

View file

@ -1,12 +1,12 @@
import {CredentialsRepository} from '$lib/server/api/repositories/credentials.repository';
import {decodeHex, encodeHexLowerCase} from '@oslojs/encoding';
import {verifyTOTP} from '@oslojs/otp';
import {inject, injectable} from 'tsyringe';
import {inject, injectable} from '@needle-di/core';
import type {CredentialsType} from '../databases/postgres/tables';
@injectable()
export class TotpService {
constructor(@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository) {}
constructor(private credentialsRepository = inject(CredentialsRepository)) {}
async findOneByUserId(userId: string) {
return this.credentialsRepository.findTOTPCredentialsByUserId(userId);

View file

@ -1,13 +1,13 @@
import type {db} from '$lib/server/api/packages/drizzle'
import {type CreateUserRole, UserRolesRepository} from '$lib/server/api/repositories/user_roles.repository'
import {RolesService} from '$lib/server/api/services/roles.service'
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
@injectable()
export class UserRolesService {
constructor(
@inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository,
@inject(RolesService) private readonly rolesService: RolesService,
private userRolesRepository = inject(UserRolesRepository),
private rolesService = inject(RolesService),
) {}
async findOneById(id: string) {

View file

@ -5,7 +5,7 @@ import {FederatedIdentityRepository} from '$lib/server/api/repositories/federate
import {WishlistsRepository} from '$lib/server/api/repositories/wishlists.repository';
import {TokensService} from '$lib/server/api/services/tokens.service';
import {UserRolesService} from '$lib/server/api/services/user_roles.service';
import {inject, injectable} from 'tsyringe';
import { inject, injectable } from '@needle-di/core';
import {CredentialsType, RoleName} from '../databases/postgres/tables';
import {type UpdateUser, UsersRepository} from '../repositories/users.repository';
import {CollectionsService} from './collections.service';
@ -15,15 +15,15 @@ import {WishlistsService} from './wishlists.service';
@injectable()
export class UsersService {
constructor(
@inject(CollectionsService) private readonly collectionsService: CollectionsService,
@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository,
@inject(DrizzleService) private readonly drizzleService: DrizzleService,
@inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository,
@inject(TokensService) private readonly tokenService: TokensService,
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
@inject(UserRolesService) private readonly userRolesService: UserRolesService,
@inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository,
@inject(WishlistsService) private readonly wishlistsService: WishlistsService,
private collectionsService = inject(CollectionsService),
private credentialsRepository = inject(CredentialsRepository),
private drizzleService = inject(DrizzleService),
private federatedIdentityRepository = inject(FederatedIdentityRepository),
private tokenService = inject(TokensService),
private usersRepository = inject(UsersRepository),
private userRolesService = inject(UserRolesService),
private wishlistsRepository = inject(WishlistsRepository),
private wishlistsService = inject(WishlistsService),
) {}
async create(data: SignupUsernameEmailDto) {
@ -63,6 +63,7 @@ export class UsersService {
await this.wishlistsService.createEmptyNoName(createdUser.id, trx);
await this.collectionsService.createEmptyNoName(createdUser.id, trx);
return createdUser;
});
}

View file

@ -1,11 +1,11 @@
import type {db} from '$lib/server/api/packages/drizzle'
import {generateRandomAnimalName} from '$lib/utils/randomDataUtil'
import {inject, injectable} from 'tsyringe'
import {inject, injectable} from '@needle-di/core'
import {WishlistsRepository} from '../repositories/wishlists.repository'
@injectable()
export class WishlistsService {
constructor(@inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository) {}
constructor(private wishlistsRepository = inject(WishlistsRepository)) {}
async findAllByUserId(userId: string) {
return this.wishlistsRepository.findAllByUserId(userId)

View file

@ -1,13 +1,13 @@
import 'reflect-metadata'
import {container} from 'tsyringe'
import { Container } from '@needle-di/core';
import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest'
import {HashingService} from '../services/hashing.service'
describe('HashingService', () => {
let service: HashingService
const container = new Container()
beforeAll(() => {
service = container.resolve(HashingService)
service = container.get(HashingService)
})
afterAll(() => {

View file

@ -1,21 +1,22 @@
import 'reflect-metadata';
import {IamService} from '$lib/server/api/services/iam.service';
import {SessionsService} from '$lib/server/api/services/sessions.service';
import {UsersService} from '$lib/server/api/services/users.service';
import {faker} from '@faker-js/faker';
import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
describe('IamService', () => {
let service: IamService;
const luciaService = vi.mocked(SessionsService.prototype);
const container = new Container();
const sessionService = vi.mocked(SessionsService.prototype);
const userService = vi.mocked(UsersService.prototype);
beforeAll(() => {
service = container
.register<SessionsService>(SessionsService, { useValue: luciaService })
.register<UsersService>(UsersService, { useValue: userService })
.resolve(IamService);
container
.bind<SessionsService>({ provide: SessionsService, useValue: sessionService })
.bind<UsersService>({ provide: UsersService, useValue: userService });
service = container.get(IamService);
});
beforeEach(() => {

View file

@ -1,15 +1,17 @@
import 'reflect-metadata'
import {container} from 'tsyringe'
import { Container } from '@needle-di/core'
import {afterAll, beforeAll, describe, expect, expectTypeOf, it, vi} from 'vitest'
import {HashingService} from '../services/hashing.service'
import {TokensService} from '../services/tokens.service'
describe('TokensService', () => {
const container = new Container()
let service: TokensService
const hashingService = vi.mocked(HashingService.prototype)
beforeAll(() => {
service = container.register<HashingService>(HashingService, { useValue: hashingService }).resolve(TokensService)
container.bind<HashingService>({ provide: HashingService, useValue: hashingService });
service = container.get(TokensService);
})
afterAll(() => {

View file

@ -1,6 +1,6 @@
import 'reflect-metadata';
import {faker} from '@faker-js/faker';
import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest';
import {RoleName} from '../databases/postgres/tables';
import {UserRolesRepository} from '../repositories/user_roles.repository';
@ -8,15 +8,17 @@ import {RolesService} from '../services/roles.service';
import {UserRolesService} from '../services/user_roles.service';
describe('UserRolesService', () => {
const container = new Container();
let service: UserRolesService;
const userRolesRepository = vi.mocked(UserRolesRepository.prototype);
const rolesService = vi.mocked(RolesService.prototype);
beforeAll(() => {
service = container
.register<UserRolesRepository>(UserRolesRepository, { useValue: userRolesRepository })
.register<RolesService>(RolesService, { useValue: rolesService })
.resolve(UserRolesService);
container
.bind<UserRolesRepository>({ provide: UserRolesRepository, useValue: userRolesRepository })
.bind<RolesService>({ provide: RolesService, useValue: rolesService });
service = container.get(UserRolesService);
});
afterAll(() => {

View file

@ -1,6 +1,6 @@
import 'reflect-metadata';
import {faker} from '@faker-js/faker';
import {container} from 'tsyringe';
import { Container } from '@needle-di/core';
import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest';
import {CredentialsType} from '../databases/postgres/tables';
import {CredentialsRepository} from '../repositories/credentials.repository';
@ -13,6 +13,7 @@ import {UsersService} from '../services/users.service';
import {WishlistsService} from '../services/wishlists.service';
describe('UsersService', () => {
const container = new Container();
let service: UsersService;
const credentialsRepository = vi.mocked(CredentialsRepository.prototype);
const drizzleService = vi.mocked(DrizzleService.prototype, { deep: true });
@ -38,15 +39,16 @@ describe('UsersService', () => {
}));
beforeAll(() => {
service = container
.register<CredentialsRepository>(CredentialsRepository, { useValue: credentialsRepository })
.register<DrizzleService>(DrizzleService, { useValue: drizzleService })
.register<TokensService>(TokensService, { useValue: tokensService })
.register<UsersRepository>(UsersRepository, { useValue: usersRepository })
.register<UserRolesService>(UserRolesService, { useValue: userRolesService })
.register<WishlistsService>(WishlistsService, { useValue: wishlistsService })
.register<CollectionsService>(CollectionsService, { useValue: collectionsService })
.resolve(UsersService);
container
.bind<CredentialsRepository>({ provide: CredentialsRepository, useValue: credentialsRepository })
.bind<DrizzleService>({ provide: DrizzleService, useValue: drizzleService })
.bind<TokensService>({ provide: TokensService, useValue: tokensService })
.bind<UsersRepository>({ provide: UsersRepository, useValue: usersRepository })
.bind<UserRolesService>({ provide: UserRolesService, useValue: userRolesService })
.bind<WishlistsService>({ provide: WishlistsService, useValue: wishlistsService })
.bind<CollectionsService>({ provide: CollectionsService, useValue: collectionsService });
service = container.get(UsersService);
drizzleService.db = {
transaction: vi.fn().mockImplementation(async (callback) => {
@ -87,37 +89,22 @@ describe('UsersService', () => {
it('should resolve', async () => {
const hashedPassword = 'testhash';
tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword);
// drizzleService.db = {
// transaction: vi.fn().mockResolvedValue(dbUser satisfies Awaited<ReturnType<typeof drizzleService.db.transaction>>),
// }
usersRepository.create = vi.fn().mockResolvedValue(dbUser satisfies Awaited<ReturnType<typeof usersRepository.create>>);
credentialsRepository.create = vi.fn().mockResolvedValue(dbCredentials satisfies Awaited<ReturnType<typeof credentialsRepository.create>>);
userRolesService.addRoleToUser = vi.fn().mockResolvedValue(undefined);
wishlistsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined);
collectionsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined);
drizzleService.db.transaction = vi.fn().mockImplementation(async (callback) => {
return dbUser satisfies Awaited<ReturnType<typeof callback>>
});
const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken');
const spy_usersRepository_create = vi.spyOn(usersRepository, 'create');
const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create');
const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser');
const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName');
const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName');
await expect(
service.create({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
username: faker.internet.userName(),
password: faker.string.alphanumeric(10),
confirm_password: faker.string.alphanumeric(10),
}),
).resolves.toEqual(dbUser);
const createdUser = await service.create({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
username: faker.internet.userName(),
password: faker.string.alphanumeric(10),
confirm_password: faker.string.alphanumeric(10),
});
expect(createdUser).toEqual(dbUser);
expect(spy_tokensService_createHashToken).toBeCalledTimes(1);
expect(spy_usersRepository_create).toBeCalledTimes(1);
expect(spy_credentialsRepository_create).toBeCalledTimes(1);
expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1);
expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1);
expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1);
});
});
describe('Update User', () => {