Skip to content

Commit

Permalink
Merge pull request #358 from Heba-WebDev/feat/create-organisation
Browse files Browse the repository at this point in the history
feat: organisation creation
  • Loading branch information
buka4rill authored Jul 24, 2024
2 parents 49e2e28 + f62fe83 commit 85af481
Show file tree
Hide file tree
Showing 14 changed files with 426 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import HealthController from './health.controller';
import { AuthModule } from './modules/auth/auth.module';
import { UserModule } from './modules/user/user.module';
import authConfig from '../config/auth.config';
import { OrganisationsModule } from './modules/organisations/organisations.module';
import { AuthGuard } from './guards/auth.guard';

@Module({
Expand Down Expand Up @@ -62,6 +63,7 @@ import { AuthGuard } from './guards/auth.guard';
SeedingModule,
AuthModule,
UserModule,
OrganisationsModule,
],
controllers: [HealthController],
})
Expand Down
27 changes: 27 additions & 0 deletions src/modules/organisations/dto/organisation.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { IsEmail, IsString } from 'class-validator';

export class OrganisationRequestDto {
@IsString()
readonly name: string;

@IsString()
readonly description: string;

@IsEmail()
readonly email: string;

@IsString()
readonly industry: string;

@IsString()
readonly type: string;

@IsString()
readonly country: string;

@IsString()
readonly address: string;

@IsString()
readonly state: string;
}
15 changes: 15 additions & 0 deletions src/modules/organisations/entities/org-preferences.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm';
import { AbstractBaseEntity } from './../../../entities/base.entity';
import { Organisation } from './organisations.entity';

@Entity()
export class OrganisationPreference extends AbstractBaseEntity {
@Column({ nullable: false })
name: string;

@Column({ nullable: false })
value: string;

@ManyToOne(() => Organisation, organisation => organisation.preferences, { nullable: false, onDelete: 'CASCADE' })
organisation: Organisation;
}
51 changes: 51 additions & 0 deletions src/modules/organisations/entities/organisations.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
OneToMany,
} from 'typeorm';
import { User } from '../../user/entities/user.entity';
import { OrganisationPreference } from './org-preferences.entity';
import { AbstractBaseEntity } from '../../../entities/base.entity';

@Entity()
export class Organisation extends AbstractBaseEntity {
@Column({ nullable: false })
name: string;

@Column('text', { nullable: false })
description: string;

@Column({ unique: true, nullable: false })
email: string;

@Column({ nullable: false })
industry: string;

@Column({ nullable: false })
type: string;

@Column({ nullable: false })
country: string;

@Column('text', { nullable: false })
address: string;

@ManyToOne(() => User, user => user.owned_organisations, { nullable: false })
owner: User;

@Column({ nullable: false })
state: string;

@ManyToOne(() => User, user => user.created_organisations, { nullable: false })
creator: User;

@Column('boolean', { default: false, nullable: false })
isDeleted: boolean;

@OneToMany(() => OrganisationPreference, preference => preference.organisation)
preferences: OrganisationPreference[];
}
26 changes: 26 additions & 0 deletions src/modules/organisations/mapper/create-organisation.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { User } from '../../../modules/user/entities/user.entity';
import { OrganisationRequestDto } from '../dto/organisation.dto';
import { Organisation } from '../entities/organisations.entity';

export class CreateOrganisationMapper {
// Maps OrganisationRequestDto to Organisation entity
static mapToEntity(dto: OrganisationRequestDto, owner: User): Organisation {
if (!dto || !owner) {
throw new Error('OrganisationRequestDto and owner are required');
}

const organisation = new Organisation();
organisation.name = dto.name;
organisation.description = dto.description;
organisation.email = dto.email;
organisation.industry = dto.industry;
organisation.type = dto.type;
organisation.country = dto.country;
organisation.address = dto.address;
organisation.state = dto.state;
organisation.owner = owner;
organisation.creator = owner;

return organisation;
}
}
24 changes: 24 additions & 0 deletions src/modules/organisations/mapper/organisation.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Organisation } from '../entities/organisations.entity';

export class OrganisationMapper {
static mapToResponseFormat(organisation: Organisation) {
if (!organisation) {
throw new Error('Organisation entity is required');
}

return {
id: organisation.id,
name: organisation.name,
description: organisation.description,
owner_id: organisation.creator.id,
email: organisation.email,
industry: organisation.industry,
type: organisation.type,
country: organisation.country,
address: organisation.address,
state: organisation.state,
created_at: organisation.created_at.toISOString(),
updated_at: organisation.updated_at.toISOString(),
};
}
}
15 changes: 15 additions & 0 deletions src/modules/organisations/organisations.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common';
import { OrganisationsService } from './organisations.service';
import { OrganisationRequestDto } from './dto/organisation.dto';
import { AuthGuard } from '@nestjs/passport';

@Controller('organisations')
export class OrganisationsController {
constructor(private readonly organisationsService: OrganisationsService) {}

@Post('/')
async create(@Body() createOrganisationDto: OrganisationRequestDto, @Request() req) {
const user = req['user'];
return this.organisationsService.create(createOrganisationDto, user.sub);
}
}
14 changes: 14 additions & 0 deletions src/modules/organisations/organisations.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { OrganisationsService } from './organisations.service';
import { OrganisationsController } from './organisations.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Organisation } from './entities/organisations.entity';
import { User } from '../user/entities/user.entity';
import { UserModule } from '../user/user.module';

@Module({
imports: [TypeOrmModule.forFeature([Organisation, User]), UserModule],
controllers: [OrganisationsController],
providers: [OrganisationsService],
})
export class OrganisationsModule {}
46 changes: 46 additions & 0 deletions src/modules/organisations/organisations.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { Organisation } from './entities/organisations.entity';
import { OrganisationRequestDto } from './dto/organisation.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { OrganisationMapper } from './mapper/organisation.mapper';
import { CreateOrganisationMapper } from './mapper/create-organisation.mapper';

@Injectable()
export class OrganisationsService {
constructor(
@InjectRepository(Organisation)
private readonly organisationRepository: Repository<Organisation>,
@InjectRepository(User)
private readonly userRepository: Repository<User>
) {}

async create(createOrganisationDto: OrganisationRequestDto, userId: string) {
const emailFound = await this.emailExists(createOrganisationDto.email);
if (emailFound)
throw new UnprocessableEntityException({
status: 'Unprocessable entity exception',
message: 'Invalid organisation credentials',
status_code: 422,
});
const owner = await this.userRepository.findOne({
where: { id: userId },
});
if (!owner) {
throw new Error('Owner not found');
}
const mapNewOrganisation = CreateOrganisationMapper.mapToEntity(createOrganisationDto, owner);
const newOrganisation = this.organisationRepository.create({
...mapNewOrganisation,
});
await this.organisationRepository.save(newOrganisation);
const mappedResponse = OrganisationMapper.mapToResponseFormat(newOrganisation);
return { status: 'success', message: 'organisation created successfully', data: mappedResponse };
}

async emailExists(email: string): Promise<boolean> {
const emailFound = await this.organisationRepository.findBy({ email });
return emailFound?.length ? true : false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { orgMock } from './organisation.mock';

export const createOrganisationResponseDtoMock = (overrides?: Partial<typeof OganisationResponseDtoMock>) => {
const defaultResponse = {
status: 'success',
message: 'organisation created successfully',
data: {
id: orgMock.id,
name: orgMock.name,
description: orgMock.description,
owner: orgMock.owner,
creator: orgMock.creator,
email: orgMock.email,
industry: orgMock.industry,
type: orgMock.type,
country: orgMock.country,
address: orgMock.address,
state: orgMock.state,
created_at: new Date(),
updated_at: new Date(),
},
status_code: 201,
};

return { ...defaultResponse, ...overrides };
};

export const OganisationResponseDtoMock = createOrganisationResponseDtoMock();
20 changes: 20 additions & 0 deletions src/modules/organisations/tests/mocks/organisation-dto.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { OrganisationRequestDto } from '../../dto/organisation.dto';

export const createMockOrganisationRequestDto = (
overrides?: Partial<OrganisationRequestDto>
): OrganisationRequestDto => {
const defaultMock: OrganisationRequestDto = {
name: 'John & Co',
description: 'An imports organisation',
email: '[email protected]',
industry: 'Import',
type: 'General',
country: 'Nigeria',
address: 'Street 101 Building 26',
state: 'Lagos',
};

return { ...defaultMock, ...overrides };
};

export const OrgRequestMockDto = createMockOrganisationRequestDto();
47 changes: 47 additions & 0 deletions src/modules/organisations/tests/mocks/organisation.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Organisation } from '../../entities/organisations.entity';
import { v4 as uuidv4 } from 'uuid';

export enum UserType {
SUPER_ADMIN = 'super_admin',
ADMIN = 'admin',
USER = 'vendor',
}

export const createMockOrganisation = (): Organisation => {
const ownerAndCreator = {
id: uuidv4(),
created_at: new Date(),
updated_at: new Date(),
first_name: 'John',
last_name: 'Smith',
email: '[email protected]',
password: 'pass123',
hashPassword: async () => {},
is_active: true,
attempts_left: 3,
time_left: 3600,
owned_organisations: [],
created_organisations: [],
user_type: UserType.ADMIN,
};

return {
id: uuidv4(),
name: 'John & Co',
description: 'An imports organisation',
email: '[email protected]',
industry: 'Import',
type: 'General',
country: 'Nigeria',
address: 'Street 101 Building 26',
state: 'Lagos',
owner: ownerAndCreator,
creator: { ...ownerAndCreator, user_type: UserType.USER },
created_at: new Date(),
updated_at: new Date(),
isDeleted: false,
preferences: [],
};
};

export const orgMock = createMockOrganisation();
Loading

0 comments on commit 85af481

Please sign in to comment.