Skip to content
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
50 changes: 50 additions & 0 deletions src/applications/applications.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Controller, Post, Body, Param, HttpCode, HttpStatus, Logger } from '@nestjs/common';
// Import your application service and notification service

@Controller('applications')
export class ApplicationsController {
private readonly logger = new Logger(ApplicationsController.name);

constructor(
private readonly applicationsService: any,

Check failure on line 9 in src/applications/applications.controller.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

Unexpected any. Specify a different type
private readonly notificationService: any

Check failure on line 10 in src/applications/applications.controller.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

Unexpected any. Specify a different type
) {}

@Post()
@HttpCode(HttpStatus.CREATED)
async createApplication(@Body() createDto: any) {

Check failure on line 15 in src/applications/applications.controller.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

Unexpected any. Specify a different type
// 1. Commit application creation
const application = await this.applicationsService.create(createDto);

// 2. Trigger notification with non-blocking error catching
try {
await this.notificationService.notify({
type: 'application_created',
targetId: application.id,
});
} catch (error) {
this.logger.error(`Failed to record notification log for application ${application.id}: ${error.message}`, error.stack);
}

return application;
}

@Post(':id/review')
@HttpCode(HttpStatus.OK)
async reviewApplication(@Param('id') id: string, @Body() reviewDto: any) {

Check failure on line 34 in src/applications/applications.controller.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

Unexpected any. Specify a different type
// 1. Commit application review update
const updatedApplication = await this.applicationsService.review(id, reviewDto);

// 2. Trigger notification with non-blocking error catching
try {
await this.notificationService.notify({
type: 'application_reviewed',
targetId: updatedApplication.id,
});
} catch (error) {
this.logger.error(`Failed to record notification log for application review ${updatedApplication.id}: ${error.message}`, error.stack);
}

return updatedApplication;
}
}
60 changes: 60 additions & 0 deletions src/donors/donors.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; // adjust path as needed

@Injectable()
export class DonorsService {
constructor(private readonly prisma: PrismaService) {}

async getDonors(limit = 20, cursor?: string) {
let cursorSortValue: number | undefined;
let cursorId: string | undefined;

if (cursor) {
try {
const decoded = Buffer.from(cursor, 'base64').toString('utf8');
const [sortVal, id] = decoded.split('_');
cursorSortValue = parseFloat(sortVal);
cursorId = id;
if (isNaN(cursorSortValue) || !cursorId) {
throw new Error('Invalid cursor format');
}
} catch (err) {

Check failure on line 21 in src/donors/donors.service.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

'err' is defined but never used
throw new BadRequestException('Invalid pagination cursor');
}
}

// Using Prisma raw query or optimized relation aggregation for keyset pagination
// Example keyset logic: order by totalAmount DESC, id DESC
const donors = await this.prisma.$queryRaw`
SELECT
d.id,
d.name,
COALESCE(SUM(s.amount), 0) as "totalAmount"
FROM "Donor" d
LEFT JOIN "Stream" s ON s."donorId" = d.id
GROUP BY d.id, d.name
${
cursorSortValue !== undefined && cursorId !== undefined
? Prisma.sql`HAVING (COALESCE(SUM(s.amount), 0), d.id) < (${cursorSortValue},${cursorId})`
: Prisma.empty
}
ORDER BY "totalAmount" DESC, d.id DESC
LIMIT ${limit + 1}
`;

const hasMore = (donors as any[]).length > limit;

Check failure on line 45 in src/donors/donors.service.ts

View workflow job for this annotation

GitHub Actions / Lint, typecheck, test

Unexpected any. Specify a different type
const paginatedDonors = hasMore ? (donors as any[]).slice(0, limit) : donors;

let nextCursor: string | null = null;
if (hasMore && paginatedDonors.length > 0) {
const lastItem = paginatedDonors[paginatedDonors.length - 1];
const rawCursorString = `${lastItem.totalAmount}_${lastItem.id}`;
nextCursor = Buffer.from(rawCursorString).toString('base64');
}

return {
data: paginatedDonors,
nextCursor,
};
}
}
23 changes: 23 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

// Default to not trusting proxy unless explicitly enabled via environment variable
const trustProxyEnv = process.env.TRUST_PROXY;
const shouldTrustProxy = trustProxyEnv === 'true' || trustProxyEnv === '1';

if (shouldTrustProxy) {
app.set('trust proxy', true);
} else {
app.set('trust proxy', false);
}

const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`Application is running on: await app.getUrl()`);
}

bootstrap();
58 changes: 58 additions & 0 deletions src/streams/streams.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class StreamsService {
constructor(private readonly prisma: PrismaService) {}

async getStreams(limit = 20, cursor?: string) {
let cursorCreatedAt: Date | undefined;
let cursorId: string | undefined;

if (cursor) {
try {
const decoded = Buffer.from(cursor, 'base64').toString('utf8');
const [timeStr, id] = decoded.split('_');
cursorCreatedAt = new Date(timeStr);
cursorId = id;
if (isNaN(cursorCreatedAt.getTime()) || !cursorId) {
throw new Error('Invalid cursor');
}
} catch (err) {
throw new BadRequestException('Invalid pagination cursor');
}
}

// Keyset pagination query with (createdAt, id) tie-breaker
const streams = await this.prisma.stream.findMany({
take: limit + 1,
...(cursorCreatedAt && cursorId
? {
skip: 1,
cursor: {
id: cursorId,
},
}
: {}),
orderBy: [
{ createdAt: 'desc' },
{ id: 'desc' },
],
});

const hasMore = streams.length > limit;
const paginatedStreams = hasMore ? streams.slice(0, limit) : streams;

let nextCursor: string | null = null;
if (hasMore && paginatedStreams.length > 0) {
const lastItem = paginatedStreams[paginatedStreams.length - 1];
const rawCursor = `${lastItem.createdAt.toISOString()}_${lastItem.id}`;
nextCursor = Buffer.from(rawCursor).toString('base64');
}

return {
data: paginatedStreams,
nextCursor,
};
}
}
Loading