Skip to content

Commit e627a2c

Browse files
scheidtdavJerryVincentjona159
authored
Feat/api boxes sensors (#580)
* feat: add draft for port of user registration to resource route * feat: partly implement refresh token * docs: simplify contributing and add info about api routes and shared logic * feat(api): finalize user registration endpoint * fix(tests): get the tests to run be reconfiguring build steps * docs(db): readd db setup and seed scripts with README info for it * fix: wrong import of utils * refactor: remove leftover custom server stuff * fix(tests): add missing refresh token table * fix(tests): reenable remaining tests for registration * fix(ci): remove playwright and use correct node version * fix(ci): run the tests with a postgres container * feat(tests): add coverage report * fix(build): reorganize server modules to correctly split client/ server * fix(build): miss an import * fix(build): remove leftovers from custom server implementation * chore(deps): bump react-router dependencies * chore(deps): update react-router * feat/user me api (#559) * feat(api): add api routes for /users/me * fix(tests): api me PUT * feat(api): add delete me endpoint * feat(api): add root route (#560) * start * new commit * tested docs * added a route * Added API Docs * modified * removed unsupported packages * updated * Modified * script generation without using ts-node. * modified * fix: update package-lock.json * Updated (#575) * Updated README * Updated README * Removed duplicate Documentation section (#576) * Updated README * Updated README * Removed duplicate section. * Update README.md * Feat/api email and password (#561) * feat(api): add email-confirmation endpoint * feat(api): add request password reset * feat(api): add password reset * feat(api): implement resend email confirmation (without sending yet) * feat/api auth (#562) * feat(api): add email-confirmation endpoint * feat(api): add request password reset * feat(api): add password reset * feat(api): implement resend email confirmation (without sending yet) * feat(api): add sign-in, sign-out and refresh-routes to api * feat(api): implement refresh endpoint --------- Co-authored-by: jona159 <[email protected]> * feat(api): boxes for user endpoints (#573) * feat/api misc (#571) * feat(api): boxes for user endpoints * feat(api): add tags and stats route scaffold * feat(api): implement tags route * refactor: remove unnecessary imports * feat(api): implement statistics route --------- Co-authored-by: jona159 <[email protected]> * feat(api): add route and test files * feat: add test code * feat: add dummy sensors to devices and implement getting them back * feat: prefer dev server in no production envs and hide dev in prod * feat(docs): start adding docs to route * feat: finish up to the point where we need measurements * fix: api routes without need for measurements * fix: stats call * fix: remaining tests * fix: frontend issue from changing the service implementation * Feat/api boxes (#582) * feat: add command for drizzle studio * feat: devices loader * feat: load single device * feat: uncomment get boxes, delete box path * feat(wip): add boxes test suite * feat: add devices service * fix: some types, formatting * feat: wip devices api * fix: tests * refactor: use modern syntax for assertion * feat: adjust for zod schema * feat: add drizzle check * feat: add phenomenon and dates to where clause * fix: tests and validation schema * fix: cast types as any temporarily --------- Co-authored-by: jona159 <[email protected]> --------- Co-authored-by: JerryVincent <[email protected]> Co-authored-by: Jerry Vincent <[email protected]> Co-authored-by: jona159 <[email protected]> Co-authored-by: jona159 <[email protected]>
1 parent d4969e2 commit e627a2c

17 files changed

+2767
-689
lines changed

app/components/device-detail/device-detail-box.tsx

Lines changed: 639 additions & 639 deletions
Large diffs are not rendered by default.

app/lib/devices-service.server.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { Device, User } from '~/schema'
2+
import {
3+
deleteDevice as deleteDeviceById,
4+
} from '~/models/device.server'
5+
import { verifyLogin } from '~/models/user.server'
6+
import { z } from 'zod'
7+
8+
export const BoxesQuerySchema = z.object({
9+
format: z.enum(["json", "geojson"] ,{
10+
errorMap: () => ({ message: "Format must be either 'json' or 'geojson'" }),
11+
}).default("json"),
12+
minimal: z.enum(["true", "false"]).default("false")
13+
.transform((v) => v === "true"),
14+
full: z.enum(["true", "false"]).default("false")
15+
.transform((v) => v === "true"),
16+
limit: z
17+
.string()
18+
.default("5")
19+
.transform((val) => parseInt(val, 10))
20+
.refine((val) => !isNaN(val), { message: "Limit must be a number" })
21+
.refine((val) => val >= 1, { message: "Limit must be at least 1" })
22+
.refine((val) => val <= 20, { message: "Limit must not exceed 20" }),
23+
24+
name: z.string().optional(),
25+
date: z.preprocess(
26+
(val) => {
27+
if (typeof val === "string") return [val];
28+
if (Array.isArray(val)) return val;
29+
return val;
30+
},
31+
z.array(z.string())
32+
.min(1, "At least one date required")
33+
.max(2, "At most two dates allowed")
34+
.transform((arr) => {
35+
const [fromDateStr, toDateStr] = arr;
36+
const fromDate = new Date(fromDateStr);
37+
if (isNaN(fromDate.getTime())) throw new Error(`Invalid date: ${fromDateStr}`);
38+
39+
if (!toDateStr) {
40+
return {
41+
fromDate: new Date(fromDate.getTime() - 4 * 60 * 60 * 1000),
42+
toDate: new Date(fromDate.getTime() + 4 * 60 * 60 * 1000),
43+
};
44+
}
45+
46+
const toDate = new Date(toDateStr);
47+
if (isNaN(toDate.getTime())) throw new Error(`Invalid date: ${toDateStr}`);
48+
return { fromDate, toDate };
49+
})
50+
).optional(),
51+
phenomenon: z.string().optional(),
52+
grouptag: z.string().transform((v) => [v]).optional(),
53+
model: z.string().transform((v) => [v]).optional(),
54+
exposure: z.string().transform((v) => [v]).optional(),
55+
56+
near: z
57+
.string()
58+
.regex(/^[-+]?\d+(\.\d+)?,[-+]?\d+(\.\d+)?$/, {
59+
message: "Invalid 'near' parameter format. Expected: 'lat,lng'",
60+
})
61+
.transform((val) => val.split(",").map(Number) as [number, number])
62+
.optional(),
63+
64+
maxDistance: z.string().transform((v) => Number(v)).optional(),
65+
66+
bbox: z
67+
.string()
68+
.transform((val) => {
69+
const coords = val.split(",").map(Number);
70+
if (coords.length !== 4 || coords.some((n) => isNaN(n))) {
71+
throw new Error("Invalid bbox parameter");
72+
}
73+
const [swLng, swLat, neLng, neLat] = coords;
74+
return {
75+
coordinates: [
76+
[
77+
[swLat, swLng],
78+
[neLat, swLng],
79+
[neLat, neLng],
80+
[swLat, neLng],
81+
[swLat, swLng],
82+
],
83+
],
84+
};
85+
})
86+
.optional(),
87+
88+
fromDate: z.string().datetime().transform((v) => new Date(v)).optional(),
89+
toDate: z.string().datetime().transform((v) => new Date(v)).optional(),
90+
})
91+
// .refine(
92+
// (data) =>
93+
// !(data.date && !data.phenomenon) && !(data.phenomenon && !data.date),
94+
// {
95+
// message: "Date and phenomenon must be used together",
96+
// path: ["date"],
97+
// }
98+
// );
99+
100+
101+
export type BoxesQueryParams = z.infer<typeof BoxesQuerySchema>;
102+
103+
/**
104+
* Deletes a device after verifiying that the user is entitled by checking
105+
* the password.
106+
* @param user The user deleting the device
107+
* @param password The users password to verify
108+
* @returns True if the device was deleted, otherwise false or "unauthorized"
109+
* if the user is not entitled to delete the device with the given parameters
110+
*/
111+
export const deleteDevice = async (
112+
user: User,
113+
device: Device,
114+
password: string,
115+
): Promise<boolean | 'unauthorized'> => {
116+
const verifiedUser = await verifyLogin(user.email, password)
117+
if (verifiedUser === null) return 'unauthorized'
118+
return (await deleteDeviceById({ id: device.id })).count > 0
119+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { getDeviceWithoutSensors } from "~/models/device.server";
2+
import { getSensorsWithLastMeasurement } from "~/models/sensor.server";
3+
4+
/**
5+
*
6+
* @param boxId
7+
* @param sensorId
8+
* @param count
9+
*/
10+
export const getLatestMeasurements = async (
11+
boxId: string,
12+
sensorId: string | undefined,
13+
count: number | undefined,
14+
): Promise<any | null> => {
15+
const device = await getDeviceWithoutSensors({ id: boxId });
16+
if (!device) return null;
17+
18+
const sensorsWithMeasurements = await getSensorsWithLastMeasurement(
19+
device.id,
20+
sensorId,
21+
count,
22+
);
23+
if (sensorId !== undefined) return sensorsWithMeasurements; // single sensor, no need for having info about device
24+
25+
(device as any).sensors = sensorsWithMeasurements;
26+
return device;
27+
};

app/lib/openapi.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import swaggerJsdoc from "swagger-jsdoc";
22

3+
const DEV_SERVER = {
4+
url: "http://localhost:3000",
5+
description: "Development server",
6+
};
7+
38
const options: swaggerJsdoc.Options = {
49
definition: {
510
openapi: "3.0.0",
611
info: {
7-
title: "",
12+
title: "openSenseMap API",
813
version: "1.0.0",
914
description: `## Documentation of the routes and methods to manage users, stations (also called boxes or senseBoxes), and measurements in the openSenseMap API. You can find the API running at [https://opensensemap.org/api/](https://opensensemap.org/api/).
1015
# Timestamps
@@ -46,14 +51,11 @@ const options: swaggerJsdoc.Options = {
4651
## If there is something unclear or there is a mistake in this documentation please open an [issue](https://github.com/openSenseMap/frontend/issues/new) in the GitHub repository.`,
4752
},
4853
servers: [
54+
...(process.env.NODE_ENV !== "production" ? [DEV_SERVER] : []),
4955
{
5056
url: process.env.OSEM_API_URL || "https://opensensemap.org/api", // Uses environment variable or defaults to production URL
5157
description: "Production server",
5258
},
53-
{
54-
url: "http://localhost:3000",
55-
description: "Development server",
56-
},
5759
],
5860
components: {
5961
schemas: {

0 commit comments

Comments
 (0)