Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore(api-gateway): remove userId from base entity #10

Merged
merged 1 commit into from
Sep 24, 2023
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
Expand Up @@ -5,6 +5,9 @@ import { Collections } from '../../common/constants';
@Entity({ name: Collections.ACTIVITIES })
@Index(`index_${Collections.ACTIVITIES}_on_userId`, ['userId'])
export class Activity extends BaseEntity {
@Column({ type: 'uuid' })
userId!: string;

@Column()
userIP!: string;

Expand Down
8 changes: 4 additions & 4 deletions packages/api-gateway/src/api-keys/api-keys.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { BaseService } from '../base/base.service';
import { ApiKey, ApiKeyType } from './entities/api-key.entity';
import { Repository } from 'typeorm';
import { Repository, UpdateResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateApiKeyDto } from './dto/create-api-key.dto';
import { UpdateApiKeyDto } from './dto/update-api-key.dto';
Expand Down Expand Up @@ -45,16 +45,16 @@ export class ApiKeysService extends BaseService<
return apikey;
}

override async updateById(
override updateById(
id: string,
data: UpdateApiKeyDto,
userId?: string,
): Promise<void> {
): Promise<UpdateResult> {
const update: QueryDeepPartialEntity<ApiKey> = { ...data };
// If userIps is `null`, means that user have
// removed the IPs, so we have to update the apikey expiration
if (data.userIps === null) this.setExpiration(update);
await this.repo.update(
return this.repo.update(
{
id,
...(userId ? { userId: userId } : /* istanbul ignore next */ {}),
Expand Down
3 changes: 3 additions & 0 deletions packages/api-gateway/src/api-keys/entities/api-key.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export enum ApiKeyType {
@Entity({ name: Collections.APIKEYS })
@Index(`index_${Collections.APIKEYS}_on_userId`, ['userId'])
export class ApiKey extends BaseEntity {
@Column({ type: 'uuid' })
userId!: string;

@Column({ unique: true })
public!: string;

Expand Down
25 changes: 6 additions & 19 deletions packages/api-gateway/src/base/base.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { SanitizeTrimPipe } from '../common/pipes/sanitize-trim.pipe';
* updated and delete an entity protected by the Role Base ACL and the api documentaion.
*/
export function ControllerFactory<
Entity extends BaseEntity,
Entity extends BaseEntity & { userId?: string },
CreateDTO extends DeepPartial<Entity>,
UpdateDTO extends QueryDeepPartialEntity<Entity>,
>(
Expand Down Expand Up @@ -64,7 +64,7 @@ export function ControllerFactory<
);

class BaseController<
Entity extends BaseEntity,
Entity extends BaseEntity & { userId?: string },
CreateDTO extends DeepPartial<Entity>,
UpdateDTO extends QueryDeepPartialEntity<Entity>,
> implements IBaseController<Entity, CreateDTO, UpdateDTO>
Expand Down Expand Up @@ -149,8 +149,6 @@ export function ControllerFactory<
id,
userId: user.id,
} as unknown as FindOptionsWhere<Entity>);
// We don't wont to give much info, so always return not found
// even if the user is trying to get a resource of another user
if (!entity) throw new NotFoundException();

return entity;
Expand Down Expand Up @@ -224,14 +222,9 @@ export function ControllerFactory<
@Body() dto: UpdateDTO,
@CurrentUser() user: User,
): Promise<void> {
const entity = await this.service.findById(id);
// We don't wont to give much info, so always return not found
// even if the user is trying to update a resource of another user
if (!entity || (entity.userId && entity.userId !== user.id))
throw new NotFoundException();

// Update owned resource
await this.service.updateById(id, dto, user.id);
const result = await this.service.updateById(id, dto, user.id);
if (result.affected === 0) throw new NotFoundException();
}

/**
Expand Down Expand Up @@ -266,15 +259,9 @@ export function ControllerFactory<
@Param('id', ParseUUIDPipe) id: string,
@CurrentUser() user: User,
): Promise<void> {
const entity = await this.service.findById(id);

// We don't wont to give much info, so always return not found
// even if the user is trying to delete a resource of another user
if (!entity || (entity.userId && entity.userId !== user.id))
throw new NotFoundException();

// Delete owned resource
await this.service.deleteById(id, user.id);
const result = await this.service.deleteById(id, user.id);
if (result.affected === 0) throw new NotFoundException();
}
}

Expand Down
4 changes: 0 additions & 4 deletions packages/api-gateway/src/base/base.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
PrimaryGeneratedColumn,
Expand All @@ -10,9 +9,6 @@ export abstract class BaseEntity {
@PrimaryGeneratedColumn('uuid')
id!: string;

@Column({ type: 'uuid' })
userId!: string;

@CreateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
createdAt!: Date;

Expand Down
22 changes: 12 additions & 10 deletions packages/api-gateway/src/base/base.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
DeepPartial,
DeleteResult,
FindManyOptions,
FindOptionsWhere,
Repository,
UpdateResult,
} from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { BaseEntity } from './base.entity';
Expand Down Expand Up @@ -82,15 +84,15 @@ export abstract class BaseService<
* @param {FindOptionsWhere<Entity>} filter The matching conditions for updating
* @param {UpdateDTO} data The payload to update the entity
*/
async update(
update(
filter: FindOptionsWhere<Entity>,
data: UpdateDTO,
): Promise<void> {
): Promise<UpdateResult> {
// @ts-expect-error Dto should not have userId, but we check anyway at runtime
if (data.userId)
throw new UnprocessableEntityException('Ownership can not be changed');

await this.repo.update(filter, data);
return this.repo.update(filter, data);
}

/**
Expand All @@ -100,16 +102,16 @@ export abstract class BaseService<
* @param {UpdateDTO} data The payload to update the entity
* @param {string} userId The userId of the user owner of the resource
*/
async updateById(
updateById(
id: string,
data: UpdateDTO,
userId?: string,
): Promise<void> {
): Promise<UpdateResult> {
// @ts-expect-error Dto should not have userId, but we check anyway at runtime
if (data.userId)
throw new UnprocessableEntityException('Ownership can not be changed');

await this.repo.update(
return this.repo.update(
{
id,
...(userId ? { userId: userId } : {}),
Expand All @@ -125,8 +127,8 @@ export abstract class BaseService<
* @param {FindOptionsWhere<Entity>} filter The matching conditions for updating
* @param {boolean} soft When true a soft delete is performed otherwise a real delete.
*/
async delete(filter: FindOptionsWhere<Entity>, soft = true): Promise<void> {
await this.repo[soft ? 'softDelete' : 'delete'](filter);
delete(filter: FindOptionsWhere<Entity>, soft = true): Promise<DeleteResult> {
return this.repo[soft ? 'softDelete' : 'delete'](filter);
}

/**
Expand All @@ -137,8 +139,8 @@ export abstract class BaseService<
* @param {string} userId The userId of the user owner of the resource
* @param {boolean} soft When true a soft delete is performed otherwise a real delete.
*/
async deleteById(id: string, userId?: string, soft = true): Promise<void> {
await this.repo[soft ? 'softDelete' : 'delete']({
deleteById(id: string, userId?: string, soft = true): Promise<DeleteResult> {
return this.repo[soft ? 'softDelete' : 'delete']({
id,
...(userId ? { userId: userId } : {}),
} as FindOptionsWhere<Entity>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
import { User } from '../../users/entities/user.entity';

export interface IBaseController<
T extends BaseEntity,
T extends BaseEntity & { userId?: string },
C extends DeepPartial<T>,
U extends QueryDeepPartialEntity<T>,
> {
Expand Down
22 changes: 16 additions & 6 deletions packages/api-gateway/src/base/interfaces/base-service.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { DeepPartial, FindManyOptions, FindOptionsWhere } from 'typeorm';
import {
DeepPartial,
DeleteResult,
FindManyOptions,
FindOptionsWhere,
UpdateResult,
} from 'typeorm';
import { BaseEntity } from '../base.entity';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';

export interface IBaseService<
T extends BaseEntity,
T extends BaseEntity & { userId?: string },
C extends DeepPartial<T>,
U extends QueryDeepPartialEntity<T>,
> {
Expand All @@ -12,8 +18,12 @@ export interface IBaseService<
find(options?: FindManyOptions<T>): Promise<T[]>;
findOne(filter: FindOptionsWhere<T>, unselected?: boolean): Promise<T | null>;
findById(id: string, unselected?: boolean): Promise<T | null>;
update(filter: FindOptionsWhere<T>, data: U): Promise<void>;
updateById(id: string, data: U, userId?: string): Promise<void>;
delete(filter: FindOptionsWhere<T>, soft?: boolean): Promise<void>;
deleteById(id: string, userId?: string, soft?: boolean): Promise<void>;
update(filter: FindOptionsWhere<T>, data: U): Promise<UpdateResult>;
updateById(id: string, data: U, userId?: string): Promise<UpdateResult>;
delete(filter: FindOptionsWhere<T>, soft?: boolean): Promise<DeleteResult>;
deleteById(
id: string,
userId?: string,
soft?: boolean,
): Promise<DeleteResult>;
}
3 changes: 3 additions & 0 deletions packages/api-gateway/src/profiles/entities/profile.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { Collections } from '../../common/constants';
@Entity({ name: Collections.PROFILES })
@Index(`index_${Collections.PROFILES}_on_userId`, ['userId'])
export class Profile extends BaseEntity {
@Column({ type: 'uuid' })
userId!: string;

@Column('varchar', { nullable: true })
firstName!: string | null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { Collections } from '../../common/constants';
@Entity({ name: Collections.RECOVERY_TOKENS })
@Index(`index_${Collections.RECOVERY_TOKENS}_on_userId`, ['userId'])
export class RecoveryToken extends BaseEntity {
@Column({ type: 'uuid' })
userId!: string;

@Column()
token!: string;

Expand Down
25 changes: 3 additions & 22 deletions packages/api-gateway/src/users/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Column, Entity, Index } from 'typeorm';
import { Collections, UserState } from '../../common/constants';
import { UserRole } from '../../app.roles';
import { BaseEntity } from '../../base/base.entity';

@Entity({ name: Collections.USERS })
export class User {
@PrimaryGeneratedColumn('uuid')
id!: string;

export class User extends BaseEntity {
@Index('index_users_on_email', { unique: true })
@Column()
email!: string;
Expand Down Expand Up @@ -58,13 +48,4 @@ export class User {

@Column('timestamptz', { nullable: true, select: false })
verifyExpire!: Date | null;

@CreateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
createdAt!: Date;

@UpdateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
updatedAt!: Date;

@DeleteDateColumn({ type: 'timestamptz', nullable: true })
deletedAt!: Date | null;
}