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
33 changes: 33 additions & 0 deletions app/api/activity/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export const dynamic = 'force-dynamic'

import { NextRequest, NextResponse } from 'next/server'
import { withRbac, RbacContext } from '@/lib/auth/rbacMiddleware'
import { activityService } from '@/lib/activity'

export const GET = withRbac('activity:view', async (request: NextRequest, auth: RbacContext) => {
try {
const { searchParams } = new URL(request.url)

const result = await activityService.list(
{
walletAddress: auth.walletAddress,
limitParam: searchParams.get('limit'),
offsetParam: searchParams.get('offset'),
contractId: searchParams.get('contractId'),
projectId: searchParams.get('projectId'),
actionType: searchParams.get('actionType'),
actorId: searchParams.get('actorId'),
},
auth.userId,
auth.role
)

return NextResponse.json(result)
} catch (err) {
console.error('[activity] Failed to list activity logs:', err)
return NextResponse.json(
{ error: 'Failed to fetch activity logs', code: 'ACTIVITY_FETCH_FAILED' },
{ status: 500 }
)
}
})
20 changes: 20 additions & 0 deletions app/api/contracts/deploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ export const dynamic = 'force-dynamic'

import { NextRequest, NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/middleware'
import { sql } from '@/lib/db'
import { deploySorobanEscrow, SorobanDeployError } from '@/lib/soroban/deploy'
import { activityService } from '@/lib/activity'
import {
createContract,
createMilestones,
Expand Down Expand Up @@ -164,6 +166,24 @@ export const POST = withAuth(async (request: NextRequest, auth) => {
return NextResponse.json({ error: 'Failed to persist contract data', code: 'DB_ERROR' }, { status: 500 })
}

const users = await sql`SELECT id FROM users WHERE wallet_address = ${auth.walletAddress} LIMIT 1`
const actorId = users[0]?.id as string | undefined
if (actorId) {
activityService.log({
actorId,
contractId: String(contract.id),
projectId: String(job.id),
actionType: 'contract_created',
description: `Contract created for project "${job.title}" with freelancer ${freelancer.wallet_address}`,
metadata: {
totalAmount: body.totalAmount,
currency,
milestonesCount: milestones.length,
freelancerId: body.freelancerId,
},
}).catch((err: unknown) => console.error('[activity] Failed to log contract_created:', err))
}

return NextResponse.json(
{
contractId: contract.id,
Expand Down
10 changes: 10 additions & 0 deletions app/api/disputes/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const dynamic = 'force-dynamic'
import { NextRequest, NextResponse } from 'next/server'
import { sql } from '@/lib/db'
import { withRbac, RbacContext } from '@/lib/auth/rbacMiddleware'
import { activityService } from '@/lib/activity'

export const POST = withRbac('dispute:create', async (request: NextRequest, auth: RbacContext) => {
try {
Expand All @@ -15,6 +16,15 @@ export const POST = withRbac('dispute:create', async (request: NextRequest, auth
if (!job) return NextResponse.json({ error: 'Job not found', code: 'JOB_NOT_FOUND' }, { status: 404 })
const [dispute] = await sql`INSERT INTO disputes (job_id, raised_by, reason) VALUES (${job.id}, ${auth.userId}, ${reason}) RETURNING *`
await sql`UPDATE jobs SET status = 'disputed', updated_at = CURRENT_TIMESTAMP WHERE id = ${jobId}`

activityService.log({
actorId: auth.userId,
disputeId: dispute.id,
actionType: 'dispute_created',
description: `Dispute raised on job "${job.title}": "${reason}"`,
metadata: { jobId, reason },
}).catch((err: unknown) => console.error('[activity] Failed to log dispute_created:', err))

return NextResponse.json(dispute, { status: 201 })
} catch {
return NextResponse.json({ error: 'Failed to raise dispute', code: 'DISPUTE_CREATION_FAILED' }, { status: 500 })
Expand Down
1 change: 1 addition & 0 deletions app/api/escrow/fund/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import { NextRequest, NextResponse } from 'next/server'
import { withRbac, RbacContext } from '@/lib/auth/rbacMiddleware'
import { sql } from '@/lib/db'
import { escrowService, EscrowError, escrowErrorToHttpStatus } from '@/lib/escrow'
import { dispatchNotification } from '@/lib/notifications'

Expand Down
12 changes: 12 additions & 0 deletions app/api/milestones/[id]/approve/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const dynamic = 'force-dynamic'
import { NextRequest, NextResponse } from 'next/server'
import { withAnyRbac, RbacContext } from '@/lib/auth/rbacMiddleware'
import { sql } from '@/lib/db'
import { activityService } from '@/lib/activity'

// Only the contract client can approve (or reject) a submitted milestone
export const POST = withAnyRbac(['milestone:approve', 'milestone:reject'], async (request: NextRequest, auth: RbacContext) => {
Expand Down Expand Up @@ -59,6 +60,17 @@ export const POST = withAnyRbac(['milestone:approve', 'milestone:reject'], async
RETURNING *
`

activityService.log({
actorId: auth.userId,
milestoneId: id,
contractId: milestone.contract_id,
actionType: action === 'approve' ? 'milestone_approved' : 'milestone_rejected',
description: action === 'approve'
? `Milestone "${updated.title}" approved`
: `Milestone "${updated.title}" rejected: "${rejection_reason}"`,
metadata: { action, rejection_reason: rejection_reason ?? null },
}).catch((err: unknown) => console.error('[activity] Failed to log milestone approval:', err))

return NextResponse.json({ milestone: updated })
} catch {
return NextResponse.json({ error: 'Failed to process milestone approval', code: 'MILESTONE_APPROVE_FAILED' }, { status: 500 })
Expand Down
10 changes: 10 additions & 0 deletions app/api/milestones/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/middleware'
import { sql } from '@/lib/db'
import { UpdateMilestoneSchema, IMMUTABLE_MILESTONE_STATUS_VALUES } from '@/lib/validations'
import { activityService } from '@/lib/activity'

export const PATCH = withAuth(async (request: NextRequest, auth) => {
const id = request.nextUrl.pathname.split('/').at(-1)
Expand Down Expand Up @@ -65,6 +66,15 @@ export const PATCH = withAuth(async (request: NextRequest, auth) => {
RETURNING *
`

activityService.log({
actorId: user.id,
milestoneId: id,
projectId: milestone.project_id,
actionType: 'milestone_updated',
description: `Milestone "${updated.title}" updated`,
metadata: { previousTitle: milestone.title },
}).catch((err: unknown) => console.error('[activity] Failed to log milestone_updated:', err))

return NextResponse.json({ milestone: updated })
} catch {
return NextResponse.json({ error: 'Failed to update milestone', code: 'MILESTONE_UPDATE_FAILED' }, { status: 500 })
Expand Down
10 changes: 10 additions & 0 deletions app/api/milestones/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/middleware'
import { sql } from '@/lib/db'
import { CreateMilestoneSchema } from '@/lib/validations'
import { activityService } from '@/lib/activity'

export const POST = withAuth(async (request: NextRequest, auth) => {
let body: unknown
Expand Down Expand Up @@ -50,6 +51,15 @@ export const POST = withAuth(async (request: NextRequest, auth) => {
RETURNING *
`

activityService.log({
actorId: user.id,
projectId: project_id,
milestoneId: milestone.id,
actionType: 'milestone_created',
description: `Milestone "${title}" created with amount ${amount} ${currency}`,
metadata: { amount, currency, sort_order },
}).catch((err: unknown) => console.error('[activity] Failed to log milestone_created:', err))

return NextResponse.json({ milestone }, { status: 201 })
} catch {
return NextResponse.json({ error: 'Failed to create milestone', code: 'MILESTONE_CREATE_FAILED' }, { status: 500 })
Expand Down
Loading
Loading