Skip to content

Commit cb60433

Browse files
authored
feat: add GET /api/v1/vouching/requests endpoint for incoming vouch requests (#54)
1 parent 5a4efa0 commit cb60433

4 files changed

Lines changed: 98 additions & 0 deletions

File tree

src/modules/vouching/dto/vouch.dto.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,37 @@ export class VouchResponseDto {
4646
@ApiProperty() createdAt: string;
4747
@ApiProperty() expiresAt: string;
4848
}
49+
50+
export class VouchRequestItemDto {
51+
@ApiProperty({
52+
description: 'Learner Stellar wallet address',
53+
example: 'GALPHABCDEFGHIJKLMNOPQRSTUVWXYZ23456789ABCDEFGHIJKLMN',
54+
})
55+
learnerWallet: string;
56+
57+
@ApiProperty({
58+
description: 'Learner reputation score (0-100)',
59+
example: 72,
60+
})
61+
reputationScore: number;
62+
63+
@ApiProperty({
64+
description: 'Requested loan amount in USD',
65+
example: 500,
66+
nullable: true,
67+
})
68+
requestedLoanAmount: number | null;
69+
70+
@ApiProperty({
71+
description: 'Loan purpose or message from the learner',
72+
example: 'Need a loan for inventory restocking',
73+
nullable: true,
74+
})
75+
loanPurpose: string | null;
76+
77+
@ApiProperty({
78+
description: 'ISO 8601 timestamp when the vouch was requested',
79+
example: '2026-06-19T12:00:00.000Z',
80+
})
81+
requestedAt: string;
82+
}

src/modules/vouching/vouching.controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ApproveVouchDto,
1919
RequestVouchDto,
2020
VouchResponseDto,
21+
VouchRequestItemDto,
2122
} from './dto/vouch.dto';
2223
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
2324
import { CurrentUser } from '../../common/decorators/current-user.decorator';
@@ -72,4 +73,14 @@ export class VouchingController {
7273
): Promise<VouchResponseDto[]> {
7374
return this.vouchingService.getMentorVouches(user.wallet);
7475
}
76+
77+
@Get('requests')
78+
@HttpCode(HttpStatus.OK)
79+
@ApiOperation({ summary: 'Get incoming vouch requests for the authenticated mentor' })
80+
@ApiResponse({ status: 200, description: 'List of pending vouch requests', type: [VouchRequestItemDto] })
81+
async getRequests(
82+
@CurrentUser() user: { wallet: string },
83+
): Promise<VouchRequestItemDto[]> {
84+
return this.vouchingService.getIncomingRequests(user.wallet);
85+
}
7586
}

src/modules/vouching/vouching.service.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ApproveVouchDto,
1010
RequestVouchDto,
1111
VouchResponseDto,
12+
VouchRequestItemDto,
1213
VouchStatus,
1314
} from './dto/vouch.dto';
1415

@@ -17,6 +18,7 @@ interface VouchRow {
1718
mentor_wallet: string;
1819
learner_wallet: string;
1920
message: string | null;
21+
loan_amount: number | null;
2022
status: VouchStatus;
2123
created_at: string;
2224
expires_at: string;
@@ -167,6 +169,56 @@ export class VouchingService {
167169
return rows.map((row) => this.mapToDto(row));
168170
}
169171

172+
async getIncomingRequests(mentorWallet: string): Promise<VouchRequestItemDto[]> {
173+
const client = this.supabaseService.getClient();
174+
175+
const { data, error } = await client
176+
.from('vouches')
177+
.select('learner_wallet, message, loan_amount, created_at')
178+
.eq('mentor_wallet', mentorWallet)
179+
.eq('status', VouchStatus.PENDING)
180+
.order('created_at', { ascending: false });
181+
182+
if (error) {
183+
this.logger.error(`Failed to fetch incoming requests for ${mentorWallet}: ${error.message}`);
184+
throw new Error('Failed to fetch vouch requests.');
185+
}
186+
187+
const rows = data ?? [];
188+
const results: VouchRequestItemDto[] = [];
189+
190+
for (const row of rows) {
191+
const reputationScore = await this.getLearnerReputationScore(row.learner_wallet);
192+
results.push({
193+
learnerWallet: row.learner_wallet,
194+
reputationScore,
195+
requestedLoanAmount: row.loan_amount ?? null,
196+
loanPurpose: row.message ?? null,
197+
requestedAt: row.created_at,
198+
});
199+
}
200+
201+
return results;
202+
}
203+
204+
private async getLearnerReputationScore(wallet: string): Promise<number> {
205+
try {
206+
const { data, error } = await this.supabaseService.getClient()
207+
.from('reputation_cache')
208+
.select('score')
209+
.eq('wallet_address', wallet)
210+
.maybeSingle();
211+
212+
if (error || !data) {
213+
return 0;
214+
}
215+
216+
return data.score;
217+
} catch {
218+
return 0;
219+
}
220+
}
221+
170222
private mapToDto(data: VouchRow): VouchResponseDto {
171223
return {
172224
id: data.id,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE public.vouches ADD COLUMN loan_amount NUMERIC;

0 commit comments

Comments
 (0)