Skip to content

Commit

Permalink
feat: add GraphQL to fetch notifications (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
PrinceYadav2 committed Nov 29, 2023
1 parent 334bc78 commit fee3bd8
Show file tree
Hide file tree
Showing 12 changed files with 886 additions and 50 deletions.
3 changes: 3 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
SERVER_PORT=
SERVER_API_KEY= # Add your API Key

# Node env
NODE_ENV= # Use "development" for graphql playground to work

# Database configuration
DB_TYPE=
DB_HOST=
Expand Down
3 changes: 2 additions & 1 deletion apps/api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ lerna-debug.log*
!.vscode/extensions.json

# Environment variable file
.env
.env
.schema.gpl
835 changes: 788 additions & 47 deletions apps/api/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@
"database:reset": "ts-node src/database/database-reset.ts"
},
"dependencies": {
"@apollo/server": "^4.9.5",
"@nestjs/apollo": "^12.0.11",
"@nestjs/axios": "^3.0.0",
"@nestjs/bull": "^10.0.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/graphql": "^12.0.11",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^3.0.3",
"@nestjs/typeorm": "^10.0.0",
"axios": "^1.5.1",
"bull": "^4.11.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"graphql": "^16.8.1",
"graphql-type-json": "^0.3.2",
"mailgun.js": "^9.2.1",
"mysql2": "^3.6.0",
"nest-winston": "^1.9.4",
Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { ScheduleModule } from '@nestjs/schedule';
import { NotificationsModule } from './modules/notifications/notifications.module';
import { BullModule } from '@nestjs/bull';
import { DatabaseModule } from './database/database.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';

const configService = new ConfigService();
@Module({
imports: [
ConfigModule.forRoot(),
Expand All @@ -23,6 +27,12 @@ import { DatabaseModule } from './database/database.module';
}),
ScheduleModule.forRoot(),
NotificationsModule.register(),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gpl'),
sortSchema: true,
playground: configService.getOrThrow('NODE_ENV') === 'development',
}),
],
controllers: [AppController],
providers: [AppService],
Expand Down
15 changes: 14 additions & 1 deletion apps/api/src/common/guards/api-key/api-key.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Observable } from 'rxjs';

const configService = new ConfigService();
Expand All @@ -9,7 +10,19 @@ export class ApiKeyGuard implements CanActivate {
const apiKey = configService.getOrThrow<string>('SERVER_API_KEY');
const request = context.switchToHttp().getRequest();

const authHeader = request.headers['authorization'];
// Get auth header incase of http request
if (request && request.headers) {
const authHeader = request.headers['authorization'];

if (authHeader && authHeader === `Bearer ${apiKey}`) {
return true;
}
}

// Get quth header incase of graphql request
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
const authHeader = req.headers.authorization;

if (authHeader && authHeader === `Bearer ${apiKey}`) {
return true;
Expand Down
8 changes: 7 additions & 1 deletion apps/api/src/common/http-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export class HttpExceptionFilter implements ExceptionFilter {
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const data = exception.getResponse() as HttpExceptionBody;
response.status(status).json(this.jsend.fail(data.message));

try {
response.status(status).json(this.jsend.fail(data.message));
} catch (error) {
// throw the original exception incase response.status doesn't workout
throw exception;
}
}
}
12 changes: 12 additions & 0 deletions apps/api/src/modules/notifications/entities/notification.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ import {
import { IsEnum, IsOptional, IsObject } from 'class-validator';
import { Status } from 'src/common/constants/database';
import { ChannelType, DeliveryStatus } from 'src/common/constants/notifications';
import { Field, ObjectType } from '@nestjs/graphql';
import { GraphQLJSONObject } from 'graphql-type-json';

@Entity({ name: 'notify_notifications' })
@ObjectType()
export class Notification {
@PrimaryGeneratedColumn()
@Field()
id: number;

@Column({ name: 'channel_type', type: 'tinyint', width: 1 })
@IsEnum(ChannelType)
@Field()
channelType: number;

@Column({ type: 'json' })
@IsObject()
@Field(() => GraphQLJSONObject)
data: Record<string, unknown>;

@Column({
Expand All @@ -29,23 +35,28 @@ export class Notification {
default: DeliveryStatus.PENDING,
})
@IsEnum(DeliveryStatus)
@Field()
deliveryStatus: number;

@Column({ type: 'json', nullable: true })
@IsObject()
@IsOptional()
@Field(() => GraphQLJSONObject, { nullable: true })
result: Record<string, unknown>;

@CreateDateColumn({ name: 'created_on' })
@Field()
createdOn: Date;

@UpdateDateColumn({ name: 'updated_on' })
updatedOn: Date;

@Column({ name: 'created_by' })
@Field()
createdBy: string;

@Column({ name: 'updated_by' })
@Field()
updatedBy: string;

@Column({
Expand All @@ -54,6 +65,7 @@ export class Notification {
default: Status.ACTIVE,
})
@IsEnum(Status)
@Field()
status: number;

constructor(notification: Partial<Notification>) {
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/modules/notifications/notifications.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MailgunModule } from '../providers/mailgun/mailgun.module';
import { SmtpModule } from '../providers/smtp/smtp.module';
import { Wa360dialogModule } from '../providers/wa360dialog/wa360dialog.module';
import { ScheduleService } from './schedule/schedule.service';
import { NotificationsResolver } from './notifications.resolver';

@Module({})
export class NotificationsModule {
Expand Down Expand Up @@ -68,6 +69,7 @@ export class NotificationsModule {
ConfigService,
JsendFormatter,
Logger,
NotificationsResolver,
],
exports: [NotificationsService],
controllers: [NotificationsController],
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/modules/notifications/notifications.resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { NotificationsResolver } from './notifications.resolver';

describe('NotificationsResolver', () => {
let resolver: NotificationsResolver;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [NotificationsResolver],
}).compile();

resolver = module.get<NotificationsResolver>(NotificationsResolver);
});

it('should be defined', () => {
expect(resolver).toBeDefined();
});
});
16 changes: 16 additions & 0 deletions apps/api/src/modules/notifications/notifications.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Query, Resolver } from '@nestjs/graphql';
import { NotificationsService } from './notifications.service';
import { Notification } from './entities/notification.entity';
import { ApiKeyGuard } from 'src/common/guards/api-key/api-key.guard';
import { UseGuards } from '@nestjs/common';

@Resolver(() => Notification)
@UseGuards(ApiKeyGuard)
export class NotificationsResolver {
constructor(private readonly notificationsService: NotificationsService) {}

@Query(() => [Notification], { name: 'notifications' })
async findAll(): Promise<Notification[]> {
return this.notificationsService.getAllNotifications();
}
}
9 changes: 9 additions & 0 deletions apps/api/src/modules/notifications/notifications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,13 @@ export class NotificationsService {
},
});
}

getAllNotifications(): Promise<Notification[]> {
this.logger.log('Getting all active notifications');
return this.notificationRepository.find({
where: {
status: Status.ACTIVE,
},
});
}
}

0 comments on commit fee3bd8

Please sign in to comment.