feat(web): add remote start transaction feature and QR code scanning for charging

This commit is contained in:
2026-03-11 18:09:00 +08:00
parent 8ee2378c78
commit 73f0c6243a
6 changed files with 822 additions and 2 deletions

View File

@@ -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));