style: run prettier
This commit is contained in:
@ -65,6 +65,6 @@ You can run this yourself to host your own instance, or contribute to the offici
|
|||||||
- `OIDC_JWKS_URL` (the JWKS/Certificate URL of your OIDC server)
|
- `OIDC_JWKS_URL` (the JWKS/Certificate URL of your OIDC server)
|
||||||
- `REVIEWS_ENABLED` (optional, set to `true` to enable POI reviews by users, requires OIDC)
|
- `REVIEWS_ENABLED` (optional, set to `true` to enable POI reviews by users, requires OIDC)
|
||||||
- `HAZARDS_ENABLED` (optional, set to `true` to enable hazard reporting by users, requires OIDC)
|
- `HAZARDS_ENABLED` (optional, set to `true` to enable hazard reporting by users, requires OIDC)
|
||||||
- `STORES_ENALED` (optional, set to `true` to enable user stores, requires OIDC)
|
- `STORES_ENALED` (optional, set to `true` to enable user stores, requires OIDC)
|
||||||
|
|
||||||
When configuring your OIDC server, make sure to enable Public Client and PCKE support.
|
When configuring your OIDC server, make sure to enable Public Client and PCKE support.
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export class Store extends BaseEntity {
|
|||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "enum",
|
type: "enum",
|
||||||
enum: STORE_TYPE
|
enum: STORE_TYPE,
|
||||||
})
|
})
|
||||||
type: StoreType;
|
type: StoreType;
|
||||||
|
|
||||||
|
|||||||
@ -396,11 +396,9 @@ if (process.env.STORES_ENABLED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOneBy(
|
const user = await User.findOneBy(
|
||||||
c.req.param("user")
|
c.req.param("user") ? { username: c.req.param("user") } : { id: uid },
|
||||||
? { username: c.req.param("user") }
|
|
||||||
: { id: uid }
|
|
||||||
);
|
);
|
||||||
if(!user) {
|
if (!user) {
|
||||||
return c.json({ error: "Invalid user ID" }, 400);
|
return c.json({ error: "Invalid user ID" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
129
src/stores.ts
129
src/stores.ts
@ -2,41 +2,49 @@ import z from "zod";
|
|||||||
import { Store, type StoreType } from "./entities/Stores";
|
import { Store, type StoreType } from "./entities/Stores";
|
||||||
import type { User } from "./entities/User";
|
import type { User } from "./entities/User";
|
||||||
|
|
||||||
export const locationStore = z.object({
|
export const locationStore = z
|
||||||
lat: z.number().min(-90).max(90),
|
.object({
|
||||||
lng: z.number().min(-180).max(180),
|
lat: z.number().min(-90).max(90),
|
||||||
name: z.string().min(1).max(100)
|
lng: z.number().min(-180).max(180),
|
||||||
}).strict();
|
name: z.string().min(1).max(100),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const vehicleStore = z.object({
|
export const vehicleStore = z
|
||||||
name: z.string().min(1).max(100),
|
.object({
|
||||||
legalMaxSpeed: z.number().min(0).max(300),
|
name: z.string().min(1).max(100),
|
||||||
actualMaxSpeed: z.number().min(0).max(300),
|
legalMaxSpeed: z.number().min(0).max(300),
|
||||||
type: z.enum([
|
actualMaxSpeed: z.number().min(0).max(300),
|
||||||
"car", "truck", "motorcycle", "bicycle", "motor_scooter"
|
type: z.enum(["car", "truck", "motorcycle", "bicycle", "motor_scooter"]),
|
||||||
]),
|
weight: z.number().min(0).max(100000).optional(),
|
||||||
weight: z.number().min(0).max(100000).optional(),
|
width: z.number().min(0).max(10).optional(),
|
||||||
width: z.number().min(0).max(10).optional(),
|
axisLoad: z.number().min(0).max(100000).optional(),
|
||||||
axisLoad: z.number().min(0).max(100000).optional(),
|
height: z.number().min(0).max(20).optional(),
|
||||||
height: z.number().min(0).max(20).optional(),
|
length: z.number().min(0).max(100).optional(),
|
||||||
length: z.number().min(0).max(100).optional(),
|
emissionClass: z.string().min(1).max(50),
|
||||||
emissionClass: z.string().min(1).max(50),
|
fuelType: z.enum(["petrol", "diesel", "electric"]),
|
||||||
fuelType: z.enum(["petrol", "diesel", "electric"]),
|
preferredFuel: z.string().min(1).max(50),
|
||||||
preferredFuel: z.string().min(1).max(50)
|
})
|
||||||
}).strict();
|
.strict();
|
||||||
|
|
||||||
export const routeStore = z.object({
|
export const routeStore = z.object({
|
||||||
locations: z.array(z.object({
|
locations: z.array(
|
||||||
lat: z.number().min(-90).max(90),
|
z.object({
|
||||||
lon: z.number().min(-180).max(180)
|
lat: z.number().min(-90).max(90),
|
||||||
})),
|
lon: z.number().min(-180).max(180),
|
||||||
legs: z.array(z.object({
|
}),
|
||||||
shape: z.string(),
|
),
|
||||||
maneuvers: z.array(z.object({
|
legs: z.array(
|
||||||
type: z.number()
|
z.object({
|
||||||
}))
|
shape: z.string(),
|
||||||
}))
|
maneuvers: z.array(
|
||||||
})
|
z.object({
|
||||||
|
type: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
export const storeTypes: Record<StoreType, z.ZodSchema> = {
|
export const storeTypes: Record<StoreType, z.ZodSchema> = {
|
||||||
location: locationStore,
|
location: locationStore,
|
||||||
@ -45,25 +53,33 @@ export const storeTypes: Record<StoreType, z.ZodSchema> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SyncPayload = z.object({
|
export const SyncPayload = z.object({
|
||||||
changes: z.array(z.object({
|
changes: z.array(
|
||||||
id: z.uuid(),
|
z.object({
|
||||||
operation: z.enum(["create", "update", "delete"]),
|
id: z.uuid(),
|
||||||
data: z.string(),
|
operation: z.enum(["create", "update", "delete"]),
|
||||||
modified_at: z.string().refine(val => !isNaN(Date.parse(val)), { message: "Invalid date" }),
|
data: z.string(),
|
||||||
type: z.enum(["location", "vehicle", "route"]),
|
modified_at: z
|
||||||
name: z.string().min(1).max(100)
|
.string()
|
||||||
})),
|
.refine((val) => !isNaN(Date.parse(val)), { message: "Invalid date" }),
|
||||||
stores: z.array(z.object({
|
type: z.enum(["location", "vehicle", "route"]),
|
||||||
id: z.uuid(),
|
name: z.string().min(1).max(100),
|
||||||
modified_at: z.string().refine(val => !isNaN(Date.parse(val)), { message: "Invalid date" })
|
}),
|
||||||
}))
|
),
|
||||||
})
|
stores: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
modified_at: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => !isNaN(Date.parse(val)), { message: "Invalid date" }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
export type SyncPayload = z.infer<typeof SyncPayload>;
|
export type SyncPayload = z.infer<typeof SyncPayload>;
|
||||||
|
|
||||||
export function verifyStoreData(type: StoreType, data: string) {
|
export function verifyStoreData(type: StoreType, data: string) {
|
||||||
const schema = storeTypes[type];
|
const schema = storeTypes[type];
|
||||||
if(!schema) return false;
|
if (!schema) return false;
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(data);
|
const parsedData = JSON.parse(data);
|
||||||
schema.parse(parsedData);
|
schema.parse(parsedData);
|
||||||
@ -76,14 +92,17 @@ export function verifyStoreData(type: StoreType, data: string) {
|
|||||||
export async function sync(payload: SyncPayload, user: User) {
|
export async function sync(payload: SyncPayload, user: User) {
|
||||||
const changes: Store[] = [];
|
const changes: Store[] = [];
|
||||||
// Apply changes from client
|
// Apply changes from client
|
||||||
for(const change of payload.changes) {
|
for (const change of payload.changes) {
|
||||||
const store = await Store.findOne({ where: { id: change.id }, relations: { user: true } });
|
const store = await Store.findOne({
|
||||||
if(!verifyStoreData(change.type, change.data)) {
|
where: { id: change.id },
|
||||||
|
relations: { user: true },
|
||||||
|
});
|
||||||
|
if (!verifyStoreData(change.type, change.data)) {
|
||||||
// Invalid data
|
// Invalid data
|
||||||
if(store) changes.push(store); // Send back the store to the client to overwrite their changes
|
if (store) changes.push(store); // Send back the store to the client to overwrite their changes
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(!store) {
|
if (!store) {
|
||||||
// Store doesn't exist, create it
|
// Store doesn't exist, create it
|
||||||
const newStore = new Store();
|
const newStore = new Store();
|
||||||
newStore.id = change.id;
|
newStore.id = change.id;
|
||||||
@ -96,7 +115,7 @@ export async function sync(payload: SyncPayload, user: User) {
|
|||||||
await newStore.save();
|
await newStore.save();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(store.user.id !== user.id) {
|
if (store.user.id !== user.id) {
|
||||||
// Not the owner of this store
|
// Not the owner of this store
|
||||||
changes.push(store); // Send back the store to the client to overwrite their changes
|
changes.push(store); // Send back the store to the client to overwrite their changes
|
||||||
continue;
|
continue;
|
||||||
@ -108,9 +127,9 @@ export async function sync(payload: SyncPayload, user: User) {
|
|||||||
|
|
||||||
// Find stores that are out of date on the client
|
// Find stores that are out of date on the client
|
||||||
const allStores = await Store.findBy({ user: { id: user.id } }); // TODO: include friends' public stores, TODO: use SQL query to only get modified stores
|
const allStores = await Store.findBy({ user: { id: user.id } }); // TODO: include friends' public stores, TODO: use SQL query to only get modified stores
|
||||||
for(const store of allStores) {
|
for (const store of allStores) {
|
||||||
const clientStore = payload.stores.find(s => s.id === store.id);
|
const clientStore = payload.stores.find((s) => s.id === store.id);
|
||||||
if(!clientStore || new Date(clientStore.modified_at) < store.modified_at) {
|
if (!clientStore || new Date(clientStore.modified_at) < store.modified_at) {
|
||||||
changes.push(store); // Client doesn't have this store or it's out of date
|
changes.push(store); // Client doesn't have this store or it's out of date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user