Skip to content

Commit

Permalink
feat: Add user-based organization retrieval methods, update DTOs for …
Browse files Browse the repository at this point in the history
…organization creation and updates, Add Swagger decorators
  • Loading branch information
PooyaRaki committed Feb 7, 2025
1 parent 81872a7 commit 3c13f71
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 39 deletions.
10 changes: 10 additions & 0 deletions src/domain/organizations/organizations.repository.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ export interface IOrganizationsRepository {
relations?: FindOptionsRelations<Organization>;
}): Promise<Array<Organization>>;

findOneByUserIdOrFail(
args: Parameters<OrganizationsRepository['findByUserId']>[0],
): Promise<Organization>;

findOneByUserId(args: {
userId: User['id'];
select?: FindOptionsSelect<Organization>;
relations?: FindOptionsRelations<Organization>;
}): Promise<Organization | null>;

update(args: {
id: Organization['id'];
updatePayload: Partial<Pick<Organization, 'name' | 'status'>>;
Expand Down
26 changes: 26 additions & 0 deletions src/domain/organizations/organizations.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ export class OrganizationsRepository implements IOrganizationsRepository {
});
}

public async findOneByUserIdOrFail(
args: Parameters<OrganizationsRepository['findByUserId']>[0],
): Promise<Organization> {
const organization = await this.findOneByUserId(args);

if (!organization) {
throw new NotFoundException(
'Organization not found. UserId = ' + args.userId,
);
}

return organization;
}

public async findOneByUserId(args: {
userId: number;
select?: FindOptionsSelect<Organization>;
relations?: FindOptionsRelations<Organization>;
}): Promise<Organization | null> {
return await this.findOne({
where: {
user_organizations: { user: { id: args.userId } },
},
});
}

public async update(args: {
id: Organization['id'];
updatePayload: QueryDeepPartialEntity<Organization>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import { ApiProperty } from '@nestjs/swagger';
import { z } from 'zod';

export class CreateOrganizationDto {
@ApiProperty()
name!: Organization['name'];
export const CreateOrganizationSchema = z.object({
name: z.string(),
});

export class CreateOrganizationDto
implements z.infer<typeof CreateOrganizationSchema>
{
@ApiProperty({ type: String })
public readonly name!: Organization['name'];
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import { ApiProperty } from '@nestjs/swagger';
import { number } from 'zod';

export class CreateOrganizationResponse {
@ApiProperty()
name!: string;
@ApiProperty({ type: String })
public readonly name!: Organization['name'];

@ApiProperty({ type: number })
public readonly id!: Organization['id'];
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import { OrganizationStatus } from '@/domain/organizations/entities/organization.entity';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class UpdateOrganizationDto {
@ApiPropertyOptional()
public name?: string;
@ApiPropertyOptional({ type: String })
public readonly name?: Organization['name'];

@ApiPropertyOptional()
public status?: OrganizationStatus;
@ApiPropertyOptional({
enum: OrganizationStatus,
})
public readonly status?: OrganizationStatus;
}

export class UpdateOrganizationResponse {
@ApiProperty()
public id!: number;
@ApiProperty({ type: Number })
public readonly id!: Organization['id'];
}
69 changes: 55 additions & 14 deletions src/routes/organizations/organizations.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { ApiTags } from '@nestjs/swagger';
import {
ApiForbiddenResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiResponse,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import {
Body,
Controller,
Delete,
Get,
HttpStatus,
Param,
ParseIntPipe,
Patch,
Post,
UseGuards,
Expand All @@ -16,16 +23,19 @@ import { Auth } from '@/routes/auth/decorators/auth.decorator';
import { AuthPayload } from '@/domain/auth/entities/auth-payload.entity';
import { OrganizationStatus } from '@/domain/organizations/entities/organization.entity';
import { CreateOrganizationResponse } from '@/routes/organizations/entities/create-organizations.dto.entity';
import { CreateOrganizationDto } from '@/routes/organizations/entities/create-organization.dto.entity';
import {
CreateOrganizationDto,
CreateOrganizationSchema,
} from '@/routes/organizations/entities/create-organization.dto.entity';
import { GetOrganizationResponse } from '@/routes/organizations/entities/get-organization.dto.entity';
import {
UpdateOrganizationDto,
UpdateOrganizationResponse,
} from '@/routes/organizations/entities/update-organization.dto.entity';
import { ValidationPipe } from '@/validation/pipes/validation.pipe';
import { z } from 'zod';

import { RowSchema } from '@/datasources/db/v1/entities/row.entity';
@ApiTags('organizations')
@UseGuards(AuthGuard)
@Controller({ path: 'organizations', version: '1' })
export class OrganizationsController {
public constructor(
Expand All @@ -35,9 +45,15 @@ export class OrganizationsController {
}

@Post()
@UseGuards(AuthGuard)
@ApiOkResponse({
description: 'Organizations created',
})
@ApiNotFoundResponse({ description: 'User not found.' })
@ApiForbiddenResponse({ description: 'Forbidden resource' })
@ApiUnauthorizedResponse({ description: 'Signer address not provided' })
public async create(
@Body() body: CreateOrganizationDto,
@Body(new ValidationPipe(CreateOrganizationSchema))
body: CreateOrganizationDto,
@Auth() authPayload: AuthPayload,
): Promise<CreateOrganizationResponse> {
return await this.organizationsService.create({
Expand All @@ -48,27 +64,45 @@ export class OrganizationsController {
}

@Get()
@UseGuards(AuthGuard)
@ApiOkResponse({
description: 'Organizations found',
})
@ApiNotFoundResponse({ description: 'User not found.' })
@ApiForbiddenResponse({ description: 'Forbidden resource' })
@ApiUnauthorizedResponse({ description: 'Signer address not provided' })
public async get(
@Auth() authPayload: AuthPayload,
): Promise<Array<GetOrganizationResponse>> {
return await this.organizationsService.get(authPayload);
}

@Get('/:id')
@UseGuards(AuthGuard)
@ApiOkResponse({
description: 'Organization found',
})
@ApiNotFoundResponse({
description: 'Organization not found. OR User not found.',
})
@ApiForbiddenResponse({ description: 'Forbidden resource' })
@ApiUnauthorizedResponse({ description: 'Signer address not provided' })
public async getOne(
@Param('id', new ValidationPipe(z.number())) id: number,
@Param('id', new ValidationPipe(RowSchema.shape.id)) id: number,
@Auth() authPayload: AuthPayload,
): Promise<GetOrganizationResponse> {
return await this.organizationsService.getOne(id, authPayload);
}

@Patch('/:id')
@UseGuards(AuthGuard)
@ApiOkResponse({
description: 'Organization updated',
})
@ApiForbiddenResponse({ description: 'Forbidden resource' })
@ApiUnauthorizedResponse({
description: 'Signer address not provided OR User is unauthorized',
})
public async update(
@Body() payload: UpdateOrganizationDto,
@Param('id', new ValidationPipe(z.number())) id: number,
@Param('id', new ValidationPipe(RowSchema.shape.id)) id: number,
@Auth() authPayload: AuthPayload,
): Promise<UpdateOrganizationResponse> {
return await this.organizationsService.update({
Expand All @@ -79,9 +113,16 @@ export class OrganizationsController {
}

@Delete('/:id')
@UseGuards(AuthGuard)
@ApiResponse({
description: 'Organization deleted',
status: HttpStatus.NO_CONTENT,
})
@ApiForbiddenResponse({ description: 'Forbidden resource' })
@ApiUnauthorizedResponse({
description: 'Signer address not provided OR User is unauthorized',
})
public async delete(
@Param('id', ParseIntPipe) id: number,
@Param('id', new ValidationPipe(RowSchema.shape.id)) id: number,
@Auth() authPayload: AuthPayload,
): Promise<void> {
return await this.organizationsService.delete({ id, authPayload });
Expand Down
7 changes: 6 additions & 1 deletion src/routes/organizations/organizations.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import { OrganizationsRepositoryModule } from '@/domain/organizations/organizati
import { OrganizationsController } from '@/routes/organizations/organizations.controller';
import { OrganizationsService } from '@/routes/organizations/organizations.service';
import { AuthRepositoryModule } from '@/domain/auth/auth.repository.interface';
import { UserRepositoryModule } from '@/domain/users/users.repository.module';

@Module({
imports: [OrganizationsRepositoryModule, AuthRepositoryModule],
imports: [
OrganizationsRepositoryModule,
AuthRepositoryModule,
UserRepositoryModule,
],
controllers: [OrganizationsController],
providers: [OrganizationsService],
})
Expand Down
26 changes: 13 additions & 13 deletions src/routes/organizations/organizations.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import type { AuthPayload } from '@/domain/auth/entities/auth-payload.entity';
import type { IOrganizationsRepository } from '@/domain/organizations/organizations.repository.interface';
import { IOrganizationsRepository } from '@/domain/organizations/organizations.repository.interface';
import { UserOrganizationRole } from '@/domain/users/entities/user-organization.entity';
import type { IUsersRepository } from '@/domain/users/users.repository.interface';
import { IUsersRepository } from '@/domain/users/users.repository.interface';
import type { GetOrganizationResponse } from '@/routes/organizations/entities/get-organization.dto.entity';
import type {
UpdateOrganizationDto,
UpdateOrganizationResponse,
} from '@/routes/organizations/entities/update-organization.dto.entity';
import { UnauthorizedException } from '@nestjs/common';
import { Inject, UnauthorizedException } from '@nestjs/common';

export class OrganizationsService {
public constructor(
@Inject(IUsersRepository)
private readonly userRepository: IUsersRepository,
@Inject(IOrganizationsRepository)
private readonly organizationsRepository: IOrganizationsRepository,
) {}

Expand All @@ -34,10 +36,11 @@ export class OrganizationsService {
): Promise<Array<GetOrganizationResponse>> {
this.assertSignerAddress(authPayload);

const { id: userId } =
await this.userRepository.getWithWallets(authPayload);
const { id: userId } = await this.userRepository.findByWalletAddressOrFail(
authPayload.signer_address,
);

return await this.organizationsRepository.findByUserIdOrFail({
return await this.organizationsRepository.findByUserId({
userId,
select: {
// id: true,
Expand Down Expand Up @@ -69,17 +72,14 @@ export class OrganizationsService {
): Promise<GetOrganizationResponse> {
this.assertSignerAddress(authPayload);

const { id: userId } =
await this.userRepository.getWithWallets(authPayload);
const { id: userId } = await this.userRepository.findByWalletAddressOrFail(
authPayload.signer_address,
);

return await this.organizationsRepository.findOneOrFail({
where: {
id,
user_organizations: {
user: {
id: userId,
},
},
user_organizations: { user: { id: userId } },
},
select: {
// id: true,
Expand Down

0 comments on commit 3c13f71

Please sign in to comment.