-
-
Notifications
You must be signed in to change notification settings - Fork 0
Migrate deleting a boxes measurements #658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
72 commits
Select commit
Hold shift + click to select a range
35eeb87
feat: add draft for port of user registration to resource route
scheidtdav 42593f0
feat: partly implement refresh token
scheidtdav dcd635f
docs: simplify contributing and add info about api routes and shared …
scheidtdav be4ebed
feat(api): finalize user registration endpoint
scheidtdav b4b8421
fix(tests): get the tests to run be reconfiguring build steps
scheidtdav 8fbb075
docs(db): readd db setup and seed scripts with README info for it
scheidtdav 1de6e69
fix: wrong import of utils
scheidtdav 08f4405
refactor: remove leftover custom server stuff
scheidtdav 4a3f8e4
fix(tests): add missing refresh token table
scheidtdav 29d3034
fix(tests): reenable remaining tests for registration
scheidtdav d164738
fix(ci): remove playwright and use correct node version
scheidtdav 7566724
fix(ci): run the tests with a postgres container
scheidtdav 44894b4
feat(tests): add coverage report
scheidtdav 77b4cc9
fix(build): reorganize server modules to correctly split client/ server
scheidtdav 5612a5f
fix(build): miss an import
scheidtdav 2e335d5
fix(build): remove leftovers from custom server implementation
scheidtdav bbf5430
chore(deps): bump react-router dependencies
scheidtdav bf19c7e
chore(deps): update react-router
scheidtdav 7d91045
feat/user me api (#559)
scheidtdav a5699de
feat(api): add root route (#560)
scheidtdav 760914b
start
JerryVincent 81a1f9c
new commit
JerryVincent acf1770
tested docs
JerryVincent eedb806
added a route
JerryVincent a6245ea
Added API Docs
JerryVincent 4712c6a
modified
JerryVincent f63dc07
removed unsupported packages
JerryVincent 7741945
updated
JerryVincent 3b620a0
Modified
JerryVincent f51f518
script generation without using ts-node.
JerryVincent 5f7a6ef
modified
JerryVincent f9ecca4
Merge branch 'api-prod' into feat/user-registration-api
scheidtdav bd0e52e
fix: update package-lock.json
scheidtdav 31f793c
Updated (#575)
JerryVincent 511b94a
Removed duplicate Documentation section (#576)
JerryVincent 98617c3
Update README.md
JerryVincent 9492e13
Feat/api email and password (#561)
scheidtdav 8a721f7
feat/api auth (#562)
scheidtdav ef552e1
feat(api): boxes for user endpoints (#573)
scheidtdav 46e89cd
feat/api misc (#571)
scheidtdav fcf4b8d
Merge branch 'dev' into feat/user-registration-api
jona159 1bd97eb
feat(api): add route and test files
scheidtdav 46b0422
feat: add test code
scheidtdav 787b662
feat: add dummy sensors to devices and implement getting them back
scheidtdav 95acb53
Merge branch 'dev' into feat/api-boxes-sensors
scheidtdav eb84619
feat: prefer dev server in no production envs and hide dev in prod
scheidtdav 84f57ca
feat(docs): start adding docs to route
scheidtdav a232d6b
Merge branch 'dev' into feat/api-boxes-sensors
scheidtdav cf36b0a
feat: finish up to the point where we need measurements
scheidtdav 0a35d3b
fix: api routes without need for measurements
scheidtdav 89e8c04
fix: stats call
scheidtdav 9a8e679
fix: remaining tests
scheidtdav 32bdb9e
fix: frontend issue from changing the service implementation
scheidtdav bba0bf5
feat: use a redirect for a more consistent route path
scheidtdav 3a14e54
feat: add deprecation notices to api info
scheidtdav 153e83a
docs: add deprecation notice for user registration api
scheidtdav a50bc7c
Merge branch 'dev' into feat/api-delete-sensor-data
scheidtdav 6a057f1
Merge branch 'dev' into feat/api-delete-sensor-data
scheidtdav 350364e
fix: remove invalid parameter
scheidtdav abf6a28
feat: add rudimentary delete implementation
scheidtdav b632e35
Merge branch 'dev' into feat/api-delete-sensor-data
scheidtdav cda8787
fix: remove new route
scheidtdav 42a4fea
fix: remove permanent redirect
scheidtdav 7ef062a
feat: add missing parameters for deletion
scheidtdav b82f392
feat: add tests
scheidtdav 29ac73c
fix: tests
scheidtdav 095bd6d
fix: coerce values to properly parse params
scheidtdav 415f724
fix: remove handling unsupported GET requests
scheidtdav 1a34b4c
refactor: remove handling unsupported json bodies
scheidtdav 55aa3c8
Merge branch 'dev' into feat/api-delete-sensor-data
scheidtdav 9f9502e
refactor: replace forbidden with notfound
scheidtdav 1fea522
test: add delete test for foreign devices
scheidtdav File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
app/routes/api.boxes.$deviceId.$sensorId.measurements.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import { type ActionFunctionArgs } from 'react-router' | ||
| import z from 'zod' | ||
| import { getUserFromJwt } from '~/lib/jwt' | ||
| import { getUserDevices } from '~/models/device.server' | ||
| import { | ||
| deleteMeasurementsForSensor, | ||
| deleteSensorMeasurementsForTimeRange, | ||
| deleteSensorMeasurementsForTimes, | ||
| } from '~/models/measurement.server' | ||
| import { StandardResponse } from '~/utils/response-utils' | ||
|
|
||
| export async function action({ request, params }: ActionFunctionArgs) { | ||
| try { | ||
| const { deviceId, sensorId } = params | ||
| if (!deviceId || !sensorId) | ||
| return StandardResponse.badRequest( | ||
| 'Invalid device id or sensor id specified', | ||
| ) | ||
|
|
||
| const jwtResponse = await getUserFromJwt(request) | ||
|
|
||
| if (typeof jwtResponse === 'string') | ||
| return StandardResponse.forbidden( | ||
| 'Invalid JWT authorization. Please sign in to obtain new JWT.', | ||
| ) | ||
|
|
||
| if (request.method !== 'DELETE') | ||
| return StandardResponse.methodNotAllowed('Endpoint only supports DELETE') | ||
|
|
||
| const userDevices = await getUserDevices(jwtResponse.id) | ||
| if (!userDevices.some((d) => d.id === deviceId)) | ||
| return StandardResponse.forbidden( | ||
| 'You are not allowed to delete data of the given device', | ||
| ) | ||
|
|
||
| const device = userDevices.find((d) => d.id === deviceId) | ||
| if (!device?.sensors.some((s) => s.id === sensorId)) | ||
scheidtdav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return StandardResponse.notFound( | ||
| `Sensor with id ${sensorId} not found or not part of this device`, | ||
| ) | ||
|
|
||
| try { | ||
| const parsedParams = await parseQueryParams(request) | ||
| let count = 0 | ||
|
|
||
| if (parsedParams.deleteAllMeasurements) | ||
| count = (await deleteMeasurementsForSensor(sensorId)).count | ||
| else if (parsedParams.timestamps) | ||
| count = ( | ||
| await deleteSensorMeasurementsForTimes( | ||
| sensorId, | ||
| parsedParams.timestamps, | ||
| ) | ||
| ).count | ||
| else if (parsedParams['from-date'] && parsedParams['to-date']) | ||
| count = ( | ||
| await deleteSensorMeasurementsForTimeRange( | ||
| sensorId, | ||
| parsedParams['from-date'], | ||
| parsedParams['to-date'], | ||
| ) | ||
| ).count | ||
|
|
||
| return StandardResponse.ok({ | ||
| message: `Successfully deleted ${count} of sensor ${sensorId}`, | ||
| }) | ||
| } catch (e) { | ||
| if (e instanceof Response) return e | ||
| else throw e | ||
| } | ||
| } catch (err: any) { | ||
| return StandardResponse.internalServerError( | ||
| err.message || 'An unexpected error occured', | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| const DeleteQueryParams = z | ||
scheidtdav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .object({ | ||
| 'from-date': z | ||
| .string() | ||
| .transform((s) => new Date(s)) | ||
| .refine((d) => !isNaN(d.getTime()), { | ||
| message: 'from-date is invalid', | ||
| }) | ||
| .optional(), | ||
| 'to-date': z | ||
| .string() | ||
| .transform((s) => new Date(s)) | ||
| .refine((d) => !isNaN(d.getTime()), { | ||
| message: 'to-date is invalid', | ||
| }) | ||
| .optional(), | ||
| timestamps: z | ||
| .preprocess((val) => { | ||
| if (Array.isArray(val)) return val | ||
| else return [val] | ||
| }, z.array(z.string())) | ||
| .transform((a) => a.map((i) => new Date(i))) | ||
| .refine((a) => a.some((i) => !isNaN(i.getTime())), { | ||
| message: 'timestamps contains invalid input', | ||
| }) | ||
| .optional(), | ||
| deleteAllMeasurements: z.coerce.boolean().optional(), | ||
| }) | ||
| .superRefine((data, ctx) => { | ||
| const fromDateSet = data['from-date'] !== undefined | ||
| const toDateSet = data['to-date'] !== undefined | ||
| const timestampsSet = data.timestamps !== undefined | ||
| const deleteAllSet = data.deleteAllMeasurements !== undefined | ||
|
|
||
| if (deleteAllSet && (timestampsSet || fromDateSet || toDateSet)) { | ||
| const paths: string[] = [] | ||
| if (timestampsSet) paths.push('timestamps') | ||
| if (fromDateSet) paths.push('from-date') | ||
| if (toDateSet) paths.push('to-date') | ||
| ctx.addIssue({ | ||
| code: z.ZodIssueCode.custom, | ||
| message: 'Parameter deleteAllMeasurements can only be used by itself', | ||
| path: paths, | ||
| }) | ||
| } else if (!deleteAllSet && timestampsSet && fromDateSet && toDateSet) | ||
| ctx.addIssue({ | ||
| code: z.ZodIssueCode.custom, | ||
| message: | ||
| 'Please specify only timestamps or a range with from-date and to-date', | ||
| path: ['timestamps', 'from-date', 'to-date'], | ||
| }) | ||
| else if (!deleteAllSet && !timestampsSet && !fromDateSet && !toDateSet) | ||
| ctx.addIssue({ | ||
| code: z.ZodIssueCode.custom, | ||
| message: | ||
| 'Please specify only timestamps or a range with from-date and to-date', | ||
| path: ['timestamps', 'from-date', 'to-date'], | ||
| }) | ||
| }) | ||
|
|
||
| const parseQueryParams = async ( | ||
| request: Request, | ||
| ): Promise<z.infer<typeof DeleteQueryParams>> => { | ||
| const url = new URL(request.url) | ||
| const params: Record<string, any> = Object.fromEntries(url.searchParams) | ||
| const parseResult = DeleteQueryParams.safeParse(params) | ||
|
|
||
| if (!parseResult.success) { | ||
| const firstError = parseResult.error.errors[0] | ||
| const message = firstError.message || 'Invalid query parameters' | ||
| throw StandardResponse.badRequest(message) | ||
| } | ||
|
|
||
| return parseResult.data | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,44 @@ | ||
| import { type LoaderFunction, type LoaderFunctionArgs } from "react-router"; | ||
| import { getLatestMeasurementsForSensor } from "~/lib/measurement-service.server"; | ||
| import { StandardResponse } from "~/utils/response-utils"; | ||
| import { type LoaderFunction, type LoaderFunctionArgs } from 'react-router' | ||
| import { getLatestMeasurementsForSensor } from '~/lib/measurement-service.server' | ||
| import { StandardResponse } from '~/utils/response-utils' | ||
|
|
||
| export const loader: LoaderFunction = async ({ | ||
| request, | ||
| params, | ||
| request, | ||
| params, | ||
| }: LoaderFunctionArgs): Promise<Response> => { | ||
| try { | ||
| const deviceId = params.deviceId; | ||
| if (deviceId === undefined) | ||
| return StandardResponse.badRequest("Invalid device id specified"); | ||
| try { | ||
| const deviceId = params.deviceId | ||
| if (deviceId === undefined) | ||
| return StandardResponse.badRequest('Invalid device id specified') | ||
|
|
||
| const sensorId = params.sensorId; | ||
| if (sensorId === undefined) | ||
| return StandardResponse.badRequest("Invalid sensor id specified"); | ||
| const sensorId = params.sensorId | ||
| if (sensorId === undefined) | ||
| return StandardResponse.badRequest('Invalid sensor id specified') | ||
|
|
||
| const searchParams = new URL(request.url).searchParams; | ||
| const onlyValue = | ||
| (searchParams.get("onlyValue")?.toLowerCase() ?? "") === "true"; | ||
| if (sensorId === undefined && onlyValue) | ||
| return StandardResponse.badRequest("onlyValue can only be used when a sensor id is specified"); | ||
| const searchParams = new URL(request.url).searchParams | ||
| const onlyValue = | ||
| (searchParams.get('onlyValue')?.toLowerCase() ?? '') === 'true' | ||
| if (sensorId === undefined && onlyValue) | ||
| return StandardResponse.badRequest( | ||
| 'onlyValue can only be used when a sensor id is specified', | ||
| ) | ||
|
|
||
| const meas = await getLatestMeasurementsForSensor(deviceId, sensorId, undefined); | ||
| const meas = await getLatestMeasurementsForSensor( | ||
| deviceId, | ||
| sensorId, | ||
| undefined, | ||
| ) | ||
|
|
||
| if (meas == null) | ||
| return StandardResponse.notFound("Device not found."); | ||
| if (meas == null) return StandardResponse.notFound('Device not found.') | ||
|
|
||
| if (onlyValue) | ||
| return StandardResponse.ok(meas["lastMeasurement"]?.value ?? null); | ||
| if (onlyValue) | ||
| return StandardResponse.ok(meas['lastMeasurement']?.value ?? null) | ||
|
|
||
| return StandardResponse.ok({ ...meas, _id: meas.id } /* for legacy purposes */); | ||
| } catch (err) { | ||
| console.warn(err); | ||
| return StandardResponse.internalServerError(); | ||
| } | ||
| }; | ||
| return StandardResponse.ok( | ||
| { ...meas, _id: meas.id } /* for legacy purposes */, | ||
| ) | ||
| } catch (err) { | ||
| console.warn(err) | ||
| return StandardResponse.internalServerError() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.