feat(web): add remote start transaction feature and QR code scanning for charging
This commit is contained in:
@@ -9,6 +9,80 @@ import type { HonoEnv } from "@/types/hono.ts";
|
||||
|
||||
const app = new Hono<HonoEnv>();
|
||||
|
||||
/**
|
||||
* POST /api/transactions/remote-start
|
||||
* Send RemoteStartTransaction to a charge point.
|
||||
* Non-admin users can only use their own id-tags.
|
||||
*/
|
||||
app.post("/remote-start", async (c) => {
|
||||
const currentUser = c.get("user");
|
||||
if (!currentUser) return c.json({ error: "Unauthorized" }, 401);
|
||||
|
||||
const db = useDrizzle();
|
||||
const body = await c.req.json<{
|
||||
chargePointIdentifier: string;
|
||||
connectorId: number;
|
||||
idTag: string;
|
||||
}>().catch(() => null);
|
||||
|
||||
if (
|
||||
!body ||
|
||||
!body.chargePointIdentifier?.trim() ||
|
||||
!Number.isInteger(body.connectorId) ||
|
||||
body.connectorId < 1 ||
|
||||
!body.idTag?.trim()
|
||||
) {
|
||||
return c.json(
|
||||
{ error: "chargePointIdentifier, connectorId (>=1), and idTag are required" },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
// Non-admin: verify idTag belongs to current user and is Accepted
|
||||
if (currentUser.role !== "admin") {
|
||||
const [tag] = await db
|
||||
.select({ status: idTag.status })
|
||||
.from(idTag)
|
||||
.where(and(eq(idTag.idTag, body.idTag.trim()), eq(idTag.userId, currentUser.id)))
|
||||
.limit(1);
|
||||
if (!tag) return c.json({ error: "idTag not found or not authorized" }, 403);
|
||||
if (tag.status !== "Accepted") return c.json({ error: "idTag is not accepted" }, 400);
|
||||
}
|
||||
|
||||
// Verify charge point exists and is Accepted
|
||||
const [cp] = await db
|
||||
.select({ id: chargePoint.id, registrationStatus: chargePoint.registrationStatus })
|
||||
.from(chargePoint)
|
||||
.where(eq(chargePoint.chargePointIdentifier, body.chargePointIdentifier.trim()))
|
||||
.limit(1);
|
||||
|
||||
if (!cp) return c.json({ error: "ChargePoint not found" }, 404);
|
||||
if (cp.registrationStatus !== "Accepted") {
|
||||
return c.json({ error: "ChargePoint is not accepted" }, 400);
|
||||
}
|
||||
|
||||
// Require the charge point to be online
|
||||
const ws = ocppConnections.get(body.chargePointIdentifier.trim());
|
||||
if (!ws) return c.json({ error: "ChargePoint is offline" }, 503);
|
||||
|
||||
const uniqueId = crypto.randomUUID();
|
||||
ws.send(
|
||||
JSON.stringify([
|
||||
OCPP_MESSAGE_TYPE.CALL,
|
||||
uniqueId,
|
||||
"RemoteStartTransaction",
|
||||
{ connectorId: body.connectorId, idTag: body.idTag.trim() },
|
||||
]),
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[OCPP] RemoteStartTransaction cp=${body.chargePointIdentifier} ` +
|
||||
`connector=${body.connectorId} idTag=${body.idTag} user=${currentUser.id}`,
|
||||
);
|
||||
|
||||
return c.json({ success: true });
|
||||
});
|
||||
|
||||
/** GET /api/transactions?page=1&limit=20&status=active|completed&chargePointId=... */
|
||||
app.get("/", async (c) => {
|
||||
const page = Math.max(1, Number(c.req.query("page") ?? 1));
|
||||
|
||||
Reference in New Issue
Block a user