ngsi-ld public api code

This commit is contained in:
Kostas Drakontidis
2026-05-25 05:55:43 +03:00
parent c2b947a197
commit 46016d966d
3 changed files with 288 additions and 0 deletions
@@ -0,0 +1,10 @@
import express from "express";
import { getEntities, getEntitiesOfType, getEntity } from "./livedata.controller.js";
const router = express.Router();
router.get("/entities", getEntities);
router.get("/entities", getEntitiesOfType);
router.get("/entities/:urn", getEntity);
export default router;
@@ -0,0 +1,172 @@
import type { Request, Response, NextFunction } from "express";
import { getActiveUserData, getRedisRoomData, type RedisRoomData, type RedisUserData } from "./livedata.repository.js";
import { prisma } from "../../lib/prisma.js";
interface Params {
urn: string;
}
type Relationship = {
type: "Relationship";
object: string;
};
type Property<T = any> = {
type: "Property";
value: T;
};
interface Entity {
id: string;
type: string;
name: {
type: "Property";
value: string;
};
}
interface User extends Entity {
type: "User";
locatedIn: Relationship;
rssi: Property<number>;
authenticationStatus: Property<string>;
observedAt: Property<string>;
}
interface Room extends Entity {
type: "Room";
occupancy: Property<number>;
}
function convertToNGSIUser(user: RedisUserData): User {
return {
id: `urn:ngsi-ld:User:${user.userID}`,
type: "User",
name: {
type: "Property",
value: user.name
},
locatedIn: {
type: "Relationship",
object: user.room
},
rssi: {
type: "Property",
value: user.rssi,
},
authenticationStatus: {
type: "Property",
value: user.verified ? "verified" : "unverified"
},
observedAt: {
type: "Property",
value: (new Date(user.timestamp)).toISOString()
}
}
}
async function convertToNGSIRoom(room: RedisRoomData): Promise<Room | null> {
const roomName = await prisma.room.findUnique({
where: { id: room.roomID },
select: { name: true }
})
if (!roomName?.name) return null;
return {
id: `urn:ngsi-ld:Room:${room.roomID}`,
type: "Room",
name: {
type: "Property",
value: roomName.name
},
occupancy: {
type: "Property",
value: room.occupancy
}
}
}
async function getNGSIUsers(): Promise<User[]> {
let users: User[] = [];
const result: RedisUserData[] = await getActiveUserData();
for (const r of result) {
const user = convertToNGSIUser(r);
users.push(user);
}
return users;
}
async function getNGSIRooms(): Promise<Room[]> {
let rooms: Room[] = [];
const result: RedisRoomData[] = await getRedisRoomData();
for (const r of result) {
const room = await convertToNGSIRoom(r);
if (room == null) continue;
rooms.push(room);
}
return rooms;
}
export async function getEntities(req: Request, res: Response, next: NextFunction) {
if (Object.keys(req.query).length > 0) return next();
let entities: Entity[] = [];
entities.push(...await getNGSIUsers());
entities.push(...await getNGSIRooms());
return res.status(200).send(entities);
}
export async function getEntitiesOfType(req: Request, res: Response) {
const { type } = req.query;
switch (type) {
case "user": return res.status(200).send(await getNGSIUsers());
case "room": return res.status(200).send(await getNGSIRooms());
default:
return res.sendStatus(400);
}
}
export async function getEntity(req: Request<Params>, res: Response) {
const { urn } = req.params;
if (!urn || !urn.startsWith("urn:ngsi-ld:")) return res.sendStatus(400);
const entity = urn.slice(12);
if (!entity) return res.sendStatus(400);
if (entity.toLowerCase().startsWith("room:")) {
const roomID = entity.slice(5);
if (!roomID) return res.sendStatus(400);
const data = await getRedisRoomData(roomID);
if (!data.length) return res.sendStatus(404);
return res.status(200).send(await convertToNGSIRoom(data[0]!));
} else if (entity.toLowerCase().startsWith("user:")) {
const userID = entity.slice(5);
if (!userID) return res.sendStatus(400);
const data = await getActiveUserData(userID);
if (!data.length) return res.sendStatus(404);
return res.status(200).send(convertToNGSIUser(data[0]!));
} else
return res.sendStatus(400);
}
@@ -0,0 +1,106 @@
import { prisma } from "../../lib/prisma.js";
import { getRedis } from "../../redis/redis.js";
export interface RedisUserData {
userID: string;
name: string;
rssi: number;
room: string;
verified: boolean;
timestamp: number;
}
export interface RedisRoomData {
roomID: string;
occupancy: number;
}
export async function getActiveUserData(userID?: string): Promise<RedisUserData[]> {
const redis = getRedis();
if (!userID) {
let cursor = "0";
let result: RedisUserData[] = [];
do {
const res = await redis.scan(cursor, {
MATCH: "user:*",
COUNT: 100,
});
cursor = res.cursor;
const values = await Promise.all(
res.keys.map((key) => redis.get(key))
);
result.push(...values
.filter((v): v is string => v !== null)
.map((v) => JSON.parse(v))
);
} while (cursor !== "0");
return result;
}
const _userID = await redis.get(`_user:${userID}`);
if (!_userID) return [];
const res = await redis.get(`user:${_userID}`);
if (!res) return [];
const resObj: RedisUserData = JSON.parse(res);
return [resObj];
}
export async function getRedisRoomData(roomID?: string): Promise<RedisRoomData[]> {
const redis = getRedis();
if (!roomID) {
let cursor = "0";
let result: RedisRoomData[] = [];
do {
const res = await redis.scan(cursor, {
MATCH: "room:*",
COUNT: 100,
});
cursor = res.cursor;
const values = await Promise.all(
res.keys.map(async (key) => ({
roomID: key,
occupancy: await redis.get(key),
}))
);
result.push(...values
.filter((v) => v.occupancy !== null)
.map((v) => ({
roomID: v.roomID.slice(5),
occupancy: Number(v.occupancy),
}))
);
} while (cursor !== "0");
return result;
}
const res = await redis.get(`room:${roomID}`);
if (!res) return [];
return [{ roomID: roomID, occupancy: Number(res) }];
}
export async function getRoomIDs(): Promise<{ id: string; }[]> {
return await prisma.room.findMany({
select: { id: true }
});
}