Skip to content

Commit

Permalink
feat: add the create get and patch action for influencer resource
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelonah committed Jan 4, 2025
1 parent 9ebfbbe commit 83a13a9
Show file tree
Hide file tree
Showing 27 changed files with 542 additions and 36 deletions.
3 changes: 2 additions & 1 deletion server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ ADCASH_INFLUENCER_MANAGER_SERVER_URL=<string>
ADCASH_INFLUENCER_MANAGER_SERVER_PORT=<number>
ADCASH_INFLUENCER_MANAGER_SERVER_MONGO_DB_URI=<string>
ADCASH_INFLUENCER_MANAGER_CLIENT_URL=<string>
ADCASH_INFLUENCER_MANAGER_CLIENT_PORT=<number>
ADCASH_INFLUENCER_MANAGER_CLIENT_PORT=<number>
NODE_ENV=<test | development | production> "it is recommended to use 'development' for local development"
2 changes: 1 addition & 1 deletion server/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"printWidth": 100
"printWidth": 120
}
1 change: 1 addition & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ADCASH_INFLUENCER_MANAGER_SERVER_PORT=<number>
ADCASH_INFLUENCER_MANAGER_SERVER_MONGO_DB_URI=<string>
ADCASH_INFLUENCER_MANAGER_CLIENT_URL=<string>
ADCASH_INFLUENCER_MANAGER_CLIENT_PORT=<number>
NODE_ENV=<test | development | production>
```

## Scripts
Expand Down
4 changes: 3 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"prepare": "cd .. && husky install ./server/.husky"
},
"dependencies": {
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
Expand All @@ -33,7 +34,8 @@
"express-rate-limit": "^7.0.0",
"helmet": "^8.0.0",
"mongoose": "^8.9.3",
"morgan": "^1.10.0"
"morgan": "^1.10.0",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@types/cors": "^2.8.14",
Expand Down
2 changes: 1 addition & 1 deletion server/src/db/mongo/index.helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Schema } from 'mongoose';

class MongoHelpers {
public _idToId = <SchemaT>(schema: Schema<SchemaT>) => {
public _idToId = <SchemaT>(schema: Schema<SchemaT>) => {
schema.virtual('id').get(function () {
return (this as any)._id.toHexString();
});
Expand Down
18 changes: 17 additions & 1 deletion server/src/db/seed/index.seed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import mockedInfluences from './influencers.mock.json';

import { envVars } from '../../utils';
import { InfluencerModel } from '../../routes/influencer/index.model';
import { InfluencerRequest } from '../../routes/influencer/index.types';

export class Seeder {
public static async run() {
await Seeder.seedInfluencers();
}

private static async seedInfluencers() {}
private static async seedInfluencers() {
if (envVars.NODE_ENV != 'development') return;

const influencersResponse = await InfluencerModel.all();

if (!influencersResponse.length) {
for (const influencer of mockedInfluences) {
await InfluencerModel.create(influencer as InfluencerRequest);
}
}
}
}
87 changes: 86 additions & 1 deletion server/src/db/seed/influencers.mock.json
Original file line number Diff line number Diff line change
@@ -1 +1,86 @@
[]
[
{
"firstName": "Alice",
"lastName": "Smith",
"socialMediaHandles": [
{
"type": "instagram",
"userName": "alice_smith"
},
{
"type": "tiktok",
"userName": "alice_tiktok"
}
],
"manager": {
"id": "m1",
"imgUrl": "https://example.com/images/m1.jpg",
"firstName": "Bob",
"lastName": "Johnson",
"email": "[email protected]"
}
},
{
"firstName": "John",
"lastName": "Doe",
"socialMediaHandles": [
{
"type": "instagram",
"userName": "john_doe"
},
{
"type": "tiktok",
"userName": "john_doe_tiktok"
}
],
"manager": {
"id": "m2",
"imgUrl": null,
"firstName": "Sarah",
"lastName": "Connor",
"email": null
}
},
{
"firstName": "Emily",
"lastName": "Davis",
"socialMediaHandles": [
{
"type": "instagram",
"userName": "emily_davis"
},
{
"type": "tiktok",
"userName": "emily_tiktok"
}
],
"manager": {
"id": "m3",
"imgUrl": "https://example.com/images/m3.jpg",
"firstName": "Michael",
"lastName": "Smith",
"email": "[email protected]"
}
},
{
"firstName": "Chris",
"lastName": "Brown",
"socialMediaHandles": [
{
"type": "instagram",
"userName": "chris_brown"
},
{
"type": "tiktok",
"userName": "chris_brown_tiktok"
}
],
"manager": {
"id": "m4",
"imgUrl": null,
"firstName": "Jessica",
"lastName": "Taylor",
"email": null
}
}
]
3 changes: 2 additions & 1 deletion server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'colors';
import 'reflect-metadata';

import fs from 'fs';
import path from 'path';
Expand All @@ -10,7 +11,7 @@ import { connectDb } from './db/index.db';
import { envVars } from './utils';

async function startServer() {
await connectDb(async () => {
await connectDb(() => {
const httpServer = https.createServer(
{
key: fs.readFileSync(path.join(__dirname, '..', 'private', 'key.pem')),
Expand Down
7 changes: 1 addition & 6 deletions server/src/middlewares/error-handler/index.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import { Request, Response, NextFunction } from 'express';

import { HttpException } from '../../services/http-exception/index.service';

export function errorHandler(
err: HttpException,
_req: Request,
res: Response,
_next: NextFunction
) {
export function errorHandler(err: HttpException, _req: Request, res: Response, _next: NextFunction) {
console.error(err);
console.error(err.name);
console.error(Object.keys(err));
Expand Down
1 change: 1 addition & 0 deletions server/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './mongo-id-normalize.plugin';
6 changes: 1 addition & 5 deletions server/src/plugins/mongo-id-normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ describe.skip('_idToId', () => {
_id: 'mockObjectId',
toJSON: () => ({ _id: 'mockObjectId' }),
} as any;
const transformedDoc = (schema as any).options.toJSON.transform.call(
doc,
doc,
doc.toJSON()
);
const transformedDoc = (schema as any).options.toJSON.transform.call(doc, doc, doc.toJSON());

expect(transformedDoc._id).toBeUndefined();
expect(transformedDoc.id).toBe('mockObjectId');
Expand Down
74 changes: 73 additions & 1 deletion server/src/routes/influencer/dto/create-influencer.dto.ts
Original file line number Diff line number Diff line change
@@ -1 +1,73 @@
export {};
/* eslint-disable no-unused-vars */

import {
IsString,
IsNotEmpty,
IsArray,
ValidateNested,
IsOptional,
IsEmail,
MaxLength,
IsEnum,
} from 'class-validator';
import { Type } from 'class-transformer';

export enum SocialMediaType {
Instagram = 'instagram',
TikTok = 'tiktok',
}

class SocialMediaHandleDto {
@IsEnum(SocialMediaType, { message: 'Social media handle must be either instagram or tiktok' })
@IsNotEmpty({ message: 'Social media handle type is required' })
type: SocialMediaType;

@IsString()
@IsNotEmpty({ message: 'Social media handle username is required' })
userName: string;
}

class ManagerDto {
@IsString()
@IsNotEmpty({ message: 'Manager ID is required' })
id: string;

@IsOptional()
@IsString()
imgUrl?: string;

@IsString()
@IsNotEmpty({ message: 'Manager first name is required' })
@MaxLength(50, { message: 'Manager first name cannot exceed 50 characters' })
firstName: string;

@IsString()
@IsNotEmpty({ message: 'Manager last name is required' })
@MaxLength(50, { message: 'Manager last name cannot exceed 50 characters' })
lastName: string;

@IsOptional()
@IsEmail({}, { message: 'Invalid email format' })
email?: string;
}

export class CreateInfluencerDto {
@IsString()
@IsNotEmpty({ message: 'First name is required' })
@MaxLength(50, { message: 'First name cannot exceed 50 characters' })
firstName: string;

@IsString()
@IsNotEmpty({ message: 'Last name is required' })
@MaxLength(50, { message: 'Last name cannot exceed 50 characters' })
lastName: string;

@IsArray()
@ValidateNested({ each: true })
@Type(() => SocialMediaHandleDto)
socialMediaHandles: SocialMediaHandleDto[];

@ValidateNested()
@Type(() => ManagerDto)
manager: ManagerDto;
}
50 changes: 49 additions & 1 deletion server/src/routes/influencer/dto/patch-influencer.dto.ts
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
export {};
import { Type } from 'class-transformer';
import { IsOptional, ValidateNested, IsString, IsNotEmpty, MaxLength } from 'class-validator';

class ManagerDto {
@IsString()
@IsNotEmpty({ message: 'Manager ID is required' })
id: string;

@IsOptional()
@IsString()
imgUrl?: string;

@IsString()
@IsNotEmpty({ message: 'Manager first name is required' })
@MaxLength(50, { message: 'Manager first name cannot exceed 50 characters' })
firstName: string;

@IsString()
@IsNotEmpty({ message: 'Manager last name is required' })
@MaxLength(50, { message: 'Manager last name cannot exceed 50 characters' })
lastName: string;

@IsOptional()
@IsString()
email?: string;
}

export class PatchInfluencerDto {
@ValidateNested()
@Type(() => ManagerDto)
manager: ManagerDto;

@IsOptional()
@IsString()
@MaxLength(50, { message: 'First name cannot exceed 50 characters' })
firstName?: string;

@IsOptional()
@IsString()
@MaxLength(50, { message: 'Last name cannot exceed 50 characters' })
lastName?: string;

@IsOptional()
@ValidateNested({ each: true })
socialMediaHandles?: Array<{
type: 'instagram' | 'tiktok';
userName: string;
}>;
}
11 changes: 10 additions & 1 deletion server/src/routes/influencer/index.controller.ts
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
export {};
import { asyncHandler } from '../../middlewares';
import { InfluencerServices } from './index.services';

export class InfluencerController {
public static httpCreateInfluencer = asyncHandler(InfluencerServices.createInfluencer);

public static httpGetInfluencers = asyncHandler(InfluencerServices.getInfluencers);

public static httpPatchInfluencer = asyncHandler(InfluencerServices.patchInfluencer);
}
1 change: 0 additions & 1 deletion server/src/routes/influencer/index.d.ts

This file was deleted.

25 changes: 24 additions & 1 deletion server/src/routes/influencer/index.model.ts
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
export {};
import { Influencer } from './index.schema';
import { InfluencerRequest, InfluencerQueryParams } from './index.types';

export class InfluencerModel {
public static create(influencer: InfluencerRequest) {
return Influencer.create(influencer);
}

public static all() {
return Influencer.find().lean().exec();
}

public static getByNameOrManager({ firstName, lastName, managerId }: InfluencerQueryParams) {
const query: any = {};
if (firstName) query.firstName = firstName;
if (lastName) query.lastName = lastName;
if (managerId) query['manager.id'] = managerId;
return Influencer.find(query).lean().exec();
}

public static update(id: string, influencer: Partial<InfluencerRequest>) {
return Influencer.findByIdAndUpdate(id, influencer, { new: true, runValidators: true }).lean().exec();
}
}
6 changes: 6 additions & 0 deletions server/src/routes/influencer/index.route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Router } from 'express';

import { InfluencerController } from './index.controller';

const influencerRouter = Router();

influencerRouter.post('/', InfluencerController.httpCreateInfluencer);
influencerRouter.get('/', InfluencerController.httpGetInfluencers);
influencerRouter.patch('/:id', InfluencerController.httpPatchInfluencer);

export { influencerRouter };
Loading

0 comments on commit 83a13a9

Please sign in to comment.