Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
35eeb87
feat: add draft for port of user registration to resource route
scheidtdav May 14, 2025
42593f0
feat: partly implement refresh token
scheidtdav May 19, 2025
dcd635f
docs: simplify contributing and add info about api routes and shared …
scheidtdav May 21, 2025
be4ebed
feat(api): finalize user registration endpoint
scheidtdav May 21, 2025
b4b8421
fix(tests): get the tests to run be reconfiguring build steps
scheidtdav May 21, 2025
8fbb075
docs(db): readd db setup and seed scripts with README info for it
scheidtdav May 21, 2025
1de6e69
fix: wrong import of utils
scheidtdav May 21, 2025
08f4405
refactor: remove leftover custom server stuff
scheidtdav May 21, 2025
4a3f8e4
fix(tests): add missing refresh token table
scheidtdav May 21, 2025
29d3034
fix(tests): reenable remaining tests for registration
scheidtdav May 22, 2025
d164738
fix(ci): remove playwright and use correct node version
scheidtdav May 27, 2025
7566724
fix(ci): run the tests with a postgres container
scheidtdav May 27, 2025
44894b4
feat(tests): add coverage report
scheidtdav May 27, 2025
77b4cc9
fix(build): reorganize server modules to correctly split client/ server
scheidtdav May 27, 2025
5612a5f
fix(build): miss an import
scheidtdav May 27, 2025
2e335d5
fix(build): remove leftovers from custom server implementation
scheidtdav May 27, 2025
bbf5430
chore(deps): bump react-router dependencies
scheidtdav May 27, 2025
bf19c7e
chore(deps): update react-router
scheidtdav May 27, 2025
7d91045
feat/user me api (#559)
scheidtdav May 27, 2025
a5699de
feat(api): add root route (#560)
scheidtdav May 27, 2025
760914b
start
JerryVincent Jun 13, 2025
81a1f9c
new commit
JerryVincent Jun 13, 2025
acf1770
tested docs
JerryVincent Jun 16, 2025
eedb806
added a route
JerryVincent Jun 16, 2025
a6245ea
Added API Docs
JerryVincent Jun 16, 2025
4712c6a
modified
JerryVincent Jun 16, 2025
f63dc07
removed unsupported packages
JerryVincent Jun 16, 2025
7741945
updated
JerryVincent Jun 16, 2025
3b620a0
Modified
JerryVincent Jun 17, 2025
f51f518
script generation without using ts-node.
JerryVincent Jun 17, 2025
5f7a6ef
modified
JerryVincent Jun 17, 2025
f9ecca4
Merge branch 'api-prod' into feat/user-registration-api
scheidtdav Jun 18, 2025
bd0e52e
fix: update package-lock.json
scheidtdav Jun 18, 2025
31f793c
Updated (#575)
JerryVincent Jun 24, 2025
c5b9835
feat: add command for drizzle studio
jona159 Jun 25, 2025
43798ff
feat: devices loader
jona159 Jun 25, 2025
a9262df
feat: load single device
jona159 Jun 25, 2025
e5f0e9f
feat: uncomment get boxes, delete box path
jona159 Jun 25, 2025
87c390d
feat(wip): add boxes test suite
jona159 Jun 25, 2025
6301974
feat: add devices service
jona159 Jun 25, 2025
e96766c
fix: some types, formatting
jona159 Jun 25, 2025
511b94a
Removed duplicate Documentation section (#576)
JerryVincent Jun 25, 2025
98617c3
Update README.md
JerryVincent Jun 25, 2025
9492e13
Feat/api email and password (#561)
scheidtdav Jun 25, 2025
8a721f7
feat/api auth (#562)
scheidtdav Jun 25, 2025
ef552e1
feat(api): boxes for user endpoints (#573)
scheidtdav Jun 25, 2025
46e89cd
feat/api misc (#571)
scheidtdav Jun 25, 2025
fcf4b8d
Merge branch 'dev' into feat/user-registration-api
jona159 Jun 25, 2025
1bd97eb
feat(api): add route and test files
scheidtdav Jun 18, 2025
46b0422
feat: add test code
scheidtdav Jun 18, 2025
787b662
feat: add dummy sensors to devices and implement getting them back
scheidtdav Jun 25, 2025
95acb53
Merge branch 'dev' into feat/api-boxes-sensors
scheidtdav Jul 2, 2025
eb84619
feat: prefer dev server in no production envs and hide dev in prod
scheidtdav Jul 2, 2025
84f57ca
feat(docs): start adding docs to route
scheidtdav Jul 2, 2025
eeb6049
feat: wip devices api
jona159 Jul 16, 2025
a232d6b
Merge branch 'dev' into feat/api-boxes-sensors
scheidtdav Jul 23, 2025
cf36b0a
feat: finish up to the point where we need measurements
scheidtdav Jul 23, 2025
0a35d3b
fix: api routes without need for measurements
scheidtdav Jul 30, 2025
89e8c04
fix: stats call
scheidtdav Jul 30, 2025
9a8e679
fix: remaining tests
scheidtdav Jul 30, 2025
32bdb9e
fix: frontend issue from changing the service implementation
scheidtdav Jul 30, 2025
711ea65
fix: tests
scheidtdav Aug 13, 2025
6f1520f
Merge branch 'feat/api-boxes-sensors' into feat/api-boxes
scheidtdav Aug 13, 2025
c0713e9
refactor: use modern syntax for assertion
scheidtdav Aug 13, 2025
1bbf604
feat: adjust for zod schema
jona159 Sep 10, 2025
38c763f
feat: add drizzle check
jona159 Sep 10, 2025
0e60abb
feat: add phenomenon and dates to where clause
jona159 Sep 10, 2025
33d52cd
feat: update returned data format of users/me/boxes endpoint to match…
mashazyu Sep 29, 2025
cd3ad95
feat: expose additional device attributes
mashazyu Sep 29, 2025
f2ebd75
fix: return jwt token as access_token
mashazyu Sep 29, 2025
8e45d96
feat: add post and get /boxes
mashazyu Sep 29, 2025
527d3ef
refactor: move CreateBoxSchema to devices-service.server.ts
mashazyu Sep 30, 2025
aa60342
fix: comment get /boxes route since this functionality is not impleme…
mashazyu Sep 30, 2025
65ad8ad
fix: linting errors
mashazyu Sep 30, 2025
8dad3b8
fix: failing tests
mashazyu Sep 30, 2025
a000876
feat: add type check
mashazyu Sep 30, 2025
2c359d2
feat: add check for authHeader for GET /users/me/boxes
mashazyu Sep 30, 2025
1541ffe
refactor: return box with sensor data from createDevice
mashazyu Sep 30, 2025
fc7b3f3
test: createDevice
mashazyu Sep 30, 2025
1a21ea8
test: post /boxes
mashazyu Sep 30, 2025
7120d4b
fix: return last measurements as string
mashazyu Sep 30, 2025
98417e4
fix: lastMeasurements types
mashazyu Sep 30, 2025
f0e928f
fix: linter warnings
mashazyu Sep 30, 2025
a91509c
refactor: device.server.ts
mashazyu Sep 30, 2025
527ebf1
fix: linter warnings
mashazyu Sep 30, 2025
392635b
doc: udpate post api documentation
mashazyu Sep 30, 2025
9e41ba3
Merge branch 'dev' into feat/post-api-boxes
mashazyu Oct 8, 2025
8a3a949
fix: after merging dev into current branch.
mashazyu Oct 8, 2025
73021d2
refactor: remove access_token mentions from api responses
mashazyu Oct 8, 2025
d39c4fc
refactor: add check for request type for api/boxes endpoint
mashazyu Oct 8, 2025
3fe045c
doc: update api/boxes documentation after allowing different request …
mashazyu Oct 8, 2025
97c5915
refactor: remove unused code and fix linter errors
mashazyu Oct 8, 2025
501a3fd
Merge branch 'dev' into feat/post-api-boxes
mashazyu Oct 8, 2025
9e4d525
fix: linting issues
scheidtdav Oct 8, 2025
396f241
Merge branch 'dev' into feat/post-api-boxes
scheidtdav Oct 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions app/lib/device-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { type Device, type Sensor } from '~/schema';

export type DeviceWithSensors = Device & {
sensors: Sensor[];
};

export type TransformedDevice = {
_id: string;
name: string;
description: string | null;
image: string | null;
link: string | null;
grouptag: string[];
exposure: string | null;
model: string | null;
latitude: number;
longitude: number;
useAuth: boolean | null;
public: boolean | null;
status: string | null;
createdAt: Date;
updatedAt: Date;
expiresAt: Date | null;
userId: string;
sensorWikiModel?: string | null;
currentLocation: {
type: "Point";
coordinates: number[];
timestamp: string;
};
lastMeasurementAt: string;
loc: Array<{
type: "Feature";
geometry: {
type: "Point";
coordinates: number[];
timestamp: string;
};
}>;
integrations: {
mqtt: {
enabled: boolean;
};
};
sensors: Array<{
_id: string;
title: string | null;
unit: string | null;
sensorType: string | null;
lastMeasurement: {
value: string;
createdAt: string;
} | null;
}>;
};

/**
* Transforms a device with sensors from database format to openSenseMap API format
* @param box - Device object with sensors from database
* @returns Transformed device in openSenseMap API format
*
* Note: Converts lastMeasurement.value from number to string to match API specification
*/
export function transformDeviceToApiFormat(
box: DeviceWithSensors
): TransformedDevice {
const { id, tags, sensors, ...rest } = box;
const timestamp = box.updatedAt.toISOString();
const coordinates = [box.longitude, box.latitude];

return {
_id: id,
grouptag: tags || [],
...rest,
currentLocation: {
type: "Point",
coordinates,
timestamp
},
lastMeasurementAt: timestamp,
loc: [{
geometry: { type: "Point", coordinates, timestamp },
type: "Feature"
}],
integrations: { mqtt: { enabled: false } },
sensors: sensors?.map((sensor) => ({
_id: sensor.id,
title: sensor.title,
unit: sensor.unit,
sensorType: sensor.sensorType,
lastMeasurement: sensor.lastMeasurement
? {
createdAt: sensor.lastMeasurement.createdAt,
// Convert numeric values to string to match API specification
value: typeof sensor.lastMeasurement.value === 'number'
? String(sensor.lastMeasurement.value)
: sensor.lastMeasurement.value,
}
: null,
})) || [],
};
}
19 changes: 17 additions & 2 deletions app/lib/devices-service.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { Device, User } from '~/schema'
import { z } from 'zod'
import {
deleteDevice as deleteDeviceById,
} from '~/models/device.server'
import { verifyLogin } from '~/models/user.server'
import { z } from 'zod'
import { type Device, type User } from '~/schema'

export const CreateBoxSchema = z.object({
name: z.string().min(1, "Name is required").max(100, "Name too long"),
exposure: z.enum(["indoor", "outdoor", "mobile", "unknown"]).optional().default("unknown"),
location: z.array(z.number()).length(2, "Location must be [longitude, latitude]"),
grouptag: z.array(z.string()).optional().default([]),
model: z.enum(["homeV2Lora", "homeV2Ethernet", "homeV2Wifi", "senseBox:Edu", "luftdaten.info", "Custom"]).optional().default("Custom"),
sensors: z.array(z.object({
id: z.string(),
icon: z.string().optional(),
title: z.string().min(1, "Sensor title is required"),
unit: z.string().min(1, "Sensor unit is required"),
sensorType: z.string().min(1, "Sensor type is required"),
})).optional().default([]),
});

export const BoxesQuerySchema = z.object({
format: z.enum(["json", "geojson"] ,{
Expand Down
92 changes: 55 additions & 37 deletions app/models/device.server.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
import { point } from '@turf/helpers'
import { eq, sql, desc, ilike, inArray, arrayContains, and } from 'drizzle-orm'
import { eq, sql, desc, ilike, arrayContains, and } from 'drizzle-orm'
import { type Point } from 'geojson'
import { drizzleClient } from '~/db.server'
import { device, location, sensor, type Device, type Sensor } from '~/schema'

const BASE_DEVICE_COLUMNS = {
id: true,
name: true,
description: true,
image: true,
link: true,
tags: true,
exposure: true,
model: true,
latitude: true,
longitude: true,
status: true,
createdAt: true,
updatedAt: true,
expiresAt: true,
sensorWikiModel: true,
} as const;

const DEVICE_COLUMNS_WITH_SENSORS = {
...BASE_DEVICE_COLUMNS,
useAuth: true,
public: true,
userId: true,
} as const;

export function getDevice({ id }: Pick<Device, 'id'>) {
return drizzleClient.query.device.findFirst({
where: (device, { eq }) => eq(device.id, id),
columns: {
createdAt: true,
description: true,
exposure: true,
id: true,
image: true,
latitude: true,
longitude: true,
link: true,
model: true,
name: true,
sensorWikiModel: true,
status: true,
updatedAt: true,
tags: true,
expiresAt: true,
},
columns: BASE_DEVICE_COLUMNS,
with: {
user: {
columns: {
Expand Down Expand Up @@ -109,15 +118,9 @@ export function deleteDevice({ id }: Pick<Device, 'id'>) {
export function getUserDevices(userId: Device['userId']) {
return drizzleClient.query.device.findMany({
where: (device, { eq }) => eq(device.userId, userId),
columns: {
id: true,
name: true,
latitude: true,
longitude: true,
exposure: true,
model: true,
createdAt: true,
updatedAt: true,
columns: DEVICE_COLUMNS_WITH_SENSORS,
with: {
sensors: true,
},
})
}
Expand Down Expand Up @@ -356,9 +359,7 @@ interface BuildWhereClauseOptions {
) {
const { minimal, limit } = opts;
const { includeColumns, whereClause } = buildWhereClause(opts);

columns = minimal ? MINIMAL_COLUMNS : { ...DEFAULT_COLUMNS, ...columns };

relations = {
...relations,
...includeColumns
Expand Down Expand Up @@ -388,7 +389,11 @@ export async function createDevice(deviceData: any, userId: string) {
tags: deviceData.tags,
userId: userId,
name: deviceData.name,
description: deviceData.description,
image: deviceData.image,
link: deviceData.link,
exposure: deviceData.exposure,
public: deviceData.public ?? false,
expiresAt: deviceData.expiresAt
? new Date(deviceData.expiresAt)
: null,
Expand All @@ -400,18 +405,31 @@ export async function createDevice(deviceData: any, userId: string) {
if (!createdDevice) {
throw new Error('Failed to create device.')
}
// Add sensors in the same transaction

// Add sensors in the same transaction and collect them
const createdSensors = [];
if (deviceData.sensors && Array.isArray(deviceData.sensors)) {
for (const sensorData of deviceData.sensors) {
await tx.insert(sensor).values({
title: sensorData.title,
unit: sensorData.unit,
sensorType: sensorData.sensorType,
deviceId: createdDevice.id, // Reference the created device ID
})
const [newSensor] = await tx.insert(sensor)
.values({
title: sensorData.title,
unit: sensorData.unit,
sensorType: sensorData.sensorType,
deviceId: createdDevice.id, // Reference the created device ID
})
.returning();

if (newSensor) {
createdSensors.push(newSensor);
}
}
}
return createdDevice

// Return device with sensors
return {
...createdDevice,
sensors: createdSensors
};
})
return newDevice
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion app/models/sensor.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export async function getSensorsWithLastMeasurement(
count?: number,
): Promise<SensorWithLatestMeasurement | SensorWithLatestMeasurement[]>;


export async function getSensorsWithLastMeasurement(
deviceId: Sensor["deviceId"],
sensorId: Sensor["id"] | undefined = undefined,
Expand Down
Loading
Loading