Skip to content

Commit

Permalink
Merge pull request #215 from NoshonNetworks/backend_issue
Browse files Browse the repository at this point in the history
Update landver backend
  • Loading branch information
fishonamos authored Dec 2, 2024
2 parents 60243b2 + 4040e5c commit 6cbfa86
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 0 deletions.
4 changes: 4 additions & 0 deletions land-registry-backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PORT=3000
DATABASE_URL=
NODE_ENV=development
CORS_ORIGINS=
32 changes: 32 additions & 0 deletions land-registry-backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "land-registry-backend",
"version": "1.0.0",
"description": "Backend service for Land Registry dApp",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node-dev src/index.ts",
"lint": "eslint . --ext .ts"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.0",
"dotenv": "^16.0.3",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"express-validator": "^7.0.1",
"winston": "^3.9.0"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/pg": "^8.10.1",
"@types/cors": "^2.8.13",
"@types/node": "^20.2.5",
"typescript": "^5.0.4",
"ts-node-dev": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"eslint": "^8.41.0"
}
}
9 changes: 9 additions & 0 deletions land-registry-backend/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import dotenv from 'dotenv';
dotenv.config();

export const config = {
port: process.env.PORT || 3000,
databaseUrl: process.env.DATABASE_URL || '',
environment: process.env.NODE_ENV || 'development',
corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'],
};
27 changes: 27 additions & 0 deletions land-registry-backend/src/controllers/inspectorController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Controller functions for handling inspector data
*
* These functions manage the retrieval and processing of inspector data,
* including all inspectors and their assignments.
*/

import { Request, Response, NextFunction } from 'express';
import * as inspectorService from '../services/inspectorService';

export async function getAllInspectors(req: Request, res: Response, next: NextFunction) {
try {
const inspectors = await inspectorService.getAllInspectors();
res.json(inspectors);
} catch (error) {
next(error);
}
}

export async function getInspectorAssignments(req: Request, res: Response, next: NextFunction) {
try {
const assignments = await inspectorService.getInspectorAssignments(req.params.address);
res.json(assignments);
} catch (error) {
next(error);
}
}
58 changes: 58 additions & 0 deletions land-registry-backend/src/controllers/landController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Controller functions for handling land registry data
*
* These functions manage the retrieval and processing of land registry data,
* including all lands, individual land details, and related transfers and verifications.
*/

import { Request, Response, NextFunction } from 'express';
import * as landService from '../services/landService';
import { AppError } from '../middleware/errorHandler';

export async function getAllLands(req: Request, res: Response, next: NextFunction) {
try {
const lands = await landService.getAllLands();
res.json(lands);
} catch (error) {
next(error);
}
}

export async function getLandById(req: Request, res: Response, next: NextFunction) {
try {
const land = await landService.getLandById(req.params.landId);
if (!land) {
throw new AppError(404, 'Land not found', 'LAND_NOT_FOUND');
}
res.json(land);
} catch (error) {
next(error);
}
}

export async function getLandTransfers(req: Request, res: Response, next: NextFunction) {
try {
const transfers = await landService.getLandTransfers(req.params.landId);
res.json(transfers);
} catch (error) {
next(error);
}
}

export async function getLandVerifications(req: Request, res: Response, next: NextFunction) {
try {
const verifications = await landService.getLandVerifications(req.params.landId);
res.json(verifications);
} catch (error) {
next(error);
}
}

export async function getLandsByOwner(req: Request, res: Response, next: NextFunction) {
try {
const lands = await landService.getLandsByOwner(req.params.address);
res.json(lands);
} catch (error) {
next(error);
}
}
49 changes: 49 additions & 0 deletions land-registry-backend/src/controllers/listingController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Controller functions for handling marketplace listings
*
* These functions manage the retrieval and processing of marketplace listings,
* including active listings and individual listing details.
*/

import { Request, Response, NextFunction } from 'express';
import * as listingService from '../services/listingService';
import { AppError } from '../middleware/errorHandler';

export async function getActiveListings(req: Request, res: Response, next: NextFunction) {
try {
const listings = await listingService.getActiveListings();
res.json(listings);
} catch (error) {
next(error);
}
}

export async function getListingById(req: Request, res: Response, next: NextFunction) {
try {
const listing = await listingService.getListingById(req.params.listingId);
if (!listing) {
throw new AppError(404, 'Listing not found', 'LISTING_NOT_FOUND');
}
res.json(listing);
} catch (error) {
next(error);
}
}

export async function getListingPriceHistory(req: Request, res: Response, next: NextFunction) {
try {
const priceHistory = await listingService.getListingPriceHistory(req.params.listingId);
res.json(priceHistory);
} catch (error) {
next(error);
}
}

export async function getLandSales(req: Request, res: Response, next: NextFunction) {
try {
const sales = await listingService.getLandSales();
res.json(sales);
} catch (error) {
next(error);
}
}
28 changes: 28 additions & 0 deletions land-registry-backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { landRoutes } from './routes/landRoutes';
import { inspectorRoutes } from './routes/inspectorRoutes';
import { listingRoutes } from './routes/listingRoutes';
import { errorHandler } from './middleware/errorHandler';
import { config } from './config';
import { logger } from './utils/logger';

const app = express();

// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());

// // Routes
// app.use('/api/lands', landRoutes);
// app.use('/api/inspectors', inspectorRoutes);
// app.use('/api/listings', listingRoutes);

// Error handling
app.use(errorHandler);

app.listen(config.port, () => {
logger.info(`Server running on port ${config.port}`);
});
39 changes: 39 additions & 0 deletions land-registry-backend/src/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';

export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public code?: string
) {
super(message);
}
}

export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
logger.error('Error:', {
message: err.message,
stack: err.stack,
path: req.path,
method: req.method,
});

if (err instanceof AppError) {
return res.status(err.statusCode).json({
status: 'error',
code: err.code,
message: err.message,
});
}

return res.status(500).json({
status: 'error',
message: 'Internal server error',
});
};
11 changes: 11 additions & 0 deletions land-registry-backend/src/middleware/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Request, Response, NextFunction } from 'express';
import { validationResult } from 'express-validator';
import { AppError } from './errorHandler';

export const validate = (req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
throw new AppError(400, 'Validation error', 'VALIDATION_ERROR');
}
next();
};
15 changes: 15 additions & 0 deletions land-registry-backend/src/routes/inspectorRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from 'express';
import { param } from 'express-validator';
import { validate } from '../middleware/validate';
import * as inspectorController from '../controllers/inspectorController';

export const inspectorRoutes = Router();

inspectorRoutes.get('/', inspectorController.getAllInspectors);

inspectorRoutes.get(
'/:address/assignments',
[param('address').isString()],
validate,
inspectorController.getInspectorAssignments
);
36 changes: 36 additions & 0 deletions land-registry-backend/src/routes/landRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Router } from 'express';
import { param, query } from 'express-validator';
import { validate } from '../middleware/validate';
import * as landController from '../controllers/landController';

export const landRoutes = Router();

landRoutes.get('/', landController.getAllLands);

landRoutes.get(
'/:landId',
[param('landId').isString()],
validate,
landController.getLandById
);

landRoutes.get(
'/:landId/transfers',
[param('landId').isString()],
validate,
landController.getLandTransfers
);

landRoutes.get(
'/:landId/verifications',
[param('landId').isString()],
validate,
landController.getLandVerifications
);

landRoutes.get(
'/owner/:address',
[param('address').isString()],
validate,
landController.getLandsByOwner
);
24 changes: 24 additions & 0 deletions land-registry-backend/src/routes/listingRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Router } from 'express';
import { param } from 'express-validator';
import { validate } from '../middleware/validate';
import * as listingController from '../controllers/listingController';

export const listingRoutes = Router();

listingRoutes.get('/active', listingController.getActiveListings);

listingRoutes.get(
'/:listingId',
[param('listingId').isString()],
validate,
listingController.getListingById
);

listingRoutes.get(
'/:listingId/price-history',
[param('listingId').isString()],
validate,
listingController.getListingPriceHistory
);

listingRoutes.get('/sales', listingController.getLandSales);
14 changes: 14 additions & 0 deletions land-registry-backend/src/services/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Pool } from 'pg';
import { config } from '../config';

export const pool = new Pool({
connectionString: config.databaseUrl,
});

export async function query(text: string, params?: any[]) {
const start = Date.now();
const res = await pool.query(text, params);
const duration = Date.now() - start;
console.log('Executed query', { text, duration, rows: res.rowCount });
return res;
}
21 changes: 21 additions & 0 deletions land-registry-backend/src/services/inspectorService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { query } from './db';
import { Inspector, InspectorAssignment } from '../types';

export async function getAllInspectors(): Promise<Inspector[]> {
const result = await query(`
SELECT * FROM inspectors
WHERE is_active = true
`);
return result.rows;
}

export async function getInspectorAssignments(inspectorAddress: string): Promise<InspectorAssignment[]> {
const result = await query(`
SELECT ia.*, l.*
FROM inspector_assignments ia
JOIN lands l ON ia.land_id = l.land_id
WHERE ia.inspector_address = $1
ORDER BY ia.timestamp DESC
`, [inspectorAddress]);
return result.rows;
}
Loading

0 comments on commit 6cbfa86

Please sign in to comment.