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
2 changes: 1 addition & 1 deletion apps/api/src/modules/reports/reports.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ export const getMapReports = async (
}
};

export const getReportList = async (
export const getReportById = async (
req: Request,
res: Response,
next: NextFunction,
Expand Down
64 changes: 64 additions & 0 deletions apps/api/src/modules/reports/reports.create.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import express from 'express';
import request from 'supertest';
import jwt from 'jsonwebtoken';
import reportsRoutes from './reports.routes';
import { ReportModel } from './report.model';
import { MediaUploadModel } from '../media/media-upload.model';
import { errorHandler, notFoundHandler } from '../../core/errors/error-handler';

process.env.JWT_SECRET = 'test-secret';

const buildApp = () => {
const app = express();
app.use(express.json());
app.use('/api/reports', reportsRoutes);
app.use(notFoundHandler);
app.use(errorHandler);
return app;
};

test('POST /api/reports creates a report and queues anchoring', async () => {
const originalFindOne = MediaUploadModel.findOne;
const originalCreate = ReportModel.create;

try {
MediaUploadModel.findOne = (async () => null) as unknown as typeof MediaUploadModel.findOne;
ReportModel.create = (async (payload: {
title: string;
category: string;
status?: string;
data_hash: string;
}) =>
({
_id: '507f1f77bcf86cd799439011',
...payload,
}) as never) as unknown as typeof ReportModel.create;

const token = jwt.sign(
{ sub: 'user-123', role: 'CITIZEN', tokenType: 'access' },
process.env.JWT_SECRET!,
{ algorithm: 'HS256', expiresIn: '15m' },
);

const response = await request(buildApp())
.post('/api/reports')
.set('Authorization', `Bearer ${token}`)
.send({
title: 'Road cave-in',
description: 'The asphalt has opened beside the curb.',
category: 'INFRASTRUCTURE',
location: { type: 'Point', coordinates: [3.45, 6.47] },
media_urls: [],
});

assert.equal(response.status, 202);
assert.equal(response.body.report_id, '507f1f77bcf86cd799439011');
assert.equal(response.body.anchor_status, 'ANCHOR_QUEUED');
assert.equal(typeof response.body.content_hash, 'string');
} finally {
MediaUploadModel.findOne = originalFindOne;
ReportModel.create = originalCreate;
}
});
12 changes: 12 additions & 0 deletions packages/contracts/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const js = require("@eslint/js");
const tseslint = require("typescript-eslint");

module.exports = tseslint.config(js.configs.recommended, ...tseslint.configs.recommended, {
languageOptions: {
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
},
},
ignores: ["dist/**"],
});
19 changes: 19 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@sidewalk/contracts",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@types/node": "^22.19.7",
"eslint": "^9.18.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.21.0"
}
}
80 changes: 80 additions & 0 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export type AuthSessionResponse = {
accessToken: string;
refreshToken?: string;
refreshTokenExpiresAt?: string;
expiresIn: string;
};

export type ReportSummary = {
id: string;
title: string;
category: string;
status: string;
location?: {
type: 'Point';
coordinates: [number, number];
};
anchorStatus?: string;
stellarTxHash?: string | null;
integrityFlag?: string;
createdAt?: string | null;
updatedAt?: string | null;
};

export type ReportListResponse = {
data: ReportSummary[];
pagination: {
page: number;
pageSize: number;
total: number;
totalPages: number;
};
};

export type ReportDetailResponse = {
data: {
id: string;
title: string;
description: string;
category: string;
status: string;
location: {
type: 'Point';
coordinates: [number, number];
};
media: Array<{
id: string;
url: string;
originalUrl: string;
processingStatus: string;
exifVerified: boolean;
}>;
anchor: {
status: string;
attempts: number;
txHash: string | null;
lastError: string | null;
needsAttention: boolean;
failedAt: string | null;
snapshotHash: string | null;
contentHash: string;
explorerUrl: string | null;
};
integrity: {
exifVerified: boolean;
exifDistanceMeters: number | null;
flag: string;
};
history: Array<{
id: string;
previousStatus: string;
nextStatus: string;
note: string | null;
actorId: string | null;
createdAt: string | null;
updatedAt: string | null;
}>;
createdAt: string | null;
updatedAt: string | null;
};
};
10 changes: 10 additions & 0 deletions packages/contracts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"module": "commonjs"
},
"include": ["src/**/*"]
}
Loading