feat: add hazards
This commit is contained in:
32
src/entities/Hazard.ts
Normal file
32
src/entities/Hazard.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import type { User } from "./User";
|
||||
|
||||
@Entity()
|
||||
export class Hazard extends BaseEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({
|
||||
type: "float"
|
||||
})
|
||||
latitude: number;
|
||||
|
||||
@Column({
|
||||
type: "float"
|
||||
})
|
||||
longitude: number;
|
||||
|
||||
@Column()
|
||||
type: string;
|
||||
|
||||
@Column()
|
||||
votes: number;
|
||||
|
||||
@Column({
|
||||
default: () => "NOW()",
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne("User", (u: User) => u.hazards)
|
||||
user: User;
|
||||
}
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from "typeorm";
|
||||
import { type Review } from "./Review";
|
||||
import { type Saved } from "./Saved";
|
||||
import type { Hazard } from "./Hazard";
|
||||
|
||||
@Entity()
|
||||
export class User extends BaseEntity {
|
||||
@ -22,6 +23,9 @@ export class User extends BaseEntity {
|
||||
@OneToMany("Saved", (s: Saved) => s.user)
|
||||
saved: Saved[];
|
||||
|
||||
@OneToMany("Hazards", (h: Hazard) => h.user)
|
||||
hazards: Hazard[];
|
||||
|
||||
@Column({
|
||||
default: () => "NOW()",
|
||||
})
|
||||
|
||||
82
src/main.ts
82
src/main.ts
@ -10,6 +10,7 @@ import { Review } from "./entities/Review";
|
||||
import { User } from "./entities/User";
|
||||
import { Saved } from "./entities/Saved";
|
||||
import { ILike } from "typeorm";
|
||||
import { Hazard } from "./entities/Hazard";
|
||||
|
||||
const app = new Hono();
|
||||
const { upgradeWebSocket, websocket } = createBunWebSocket<ServerWebSocket>();
|
||||
@ -165,6 +166,87 @@ if (process.env.REVIEWS_ENABLED) {
|
||||
});
|
||||
}
|
||||
|
||||
const VALID_HAZARD_TYPES = [
|
||||
"bumpy-road"
|
||||
];
|
||||
|
||||
if (process.env.HAZARDS_ENABLED) {
|
||||
app.get("/api/hazards", async (c) => {
|
||||
const { lat, lon, radius } = c.req.query();
|
||||
if (!lat || !lon || !radius) {
|
||||
return c.json({ error: "Latitude, longitude, and radius are required" }, 400);
|
||||
}
|
||||
// Remove unnecessary precision from lat/lon
|
||||
const nlat = Number(parseFloat(lat).toFixed(4));
|
||||
const nlon = Number(parseFloat(lon).toFixed(4));
|
||||
const nradius = Number(radius);
|
||||
if(isNaN(nradius) || nradius <= 0) {
|
||||
return c.json({ error: "Invalid radius" }, 400);
|
||||
}
|
||||
if(nradius > 1000) {
|
||||
return c.json({ error: "Radius too large, max is 1000 km" }, 400);
|
||||
}
|
||||
console.log(`Fetching hazards for lat: ${lat}, lon: ${lon}, radius: ${radius} km`);
|
||||
const hazards = await Hazard.createQueryBuilder("hazard") // Crazy math to get a rough bounding box for the radius in km
|
||||
.where("hazard.latitude BETWEEN :minLat AND :maxLat", { minLat: nlat - nradius / 110.574, maxLat: nlat + nradius / 110.574 })
|
||||
.andWhere("hazard.longitude BETWEEN :minLon AND :maxLon", { minLon: nlon - nradius / (111.320 * Math.cos(nlat * (Math.PI / 180))), maxLon: nlon + nradius / (111.320 * Math.cos(nlat * (Math.PI / 180))) })
|
||||
.getMany();
|
||||
return c.json(hazards);
|
||||
});
|
||||
|
||||
app.post("/api/hazards", async (c) => {
|
||||
const { type, lat, lon } = await c.req.json();
|
||||
if (!type || !lat || !lon) {
|
||||
return c.json(
|
||||
{ error: "Type, latitude, and longitude are required" },
|
||||
400,
|
||||
);
|
||||
}
|
||||
if(!VALID_HAZARD_TYPES.includes(type)) {
|
||||
return c.json(
|
||||
{ error: "Invalid hazard type" },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
const authHeader = c.req.header("Authorization");
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
const token = authHeader.split(" ")[1];
|
||||
if (!token) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
const isValid = await verifyToken(token);
|
||||
if (!isValid) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const uid = await getTokenUID(token);
|
||||
if (!uid) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const user = await User.findOneBy({ id: uid });
|
||||
if (!user) {
|
||||
return c.json({ error: "Invalid user ID" }, 400);
|
||||
}
|
||||
|
||||
// Remove unnecessary precision from lat/lon
|
||||
const nlat = Number(parseFloat(lat).toFixed(4));
|
||||
const nlon = Number(parseFloat(lon).toFixed(4));
|
||||
|
||||
const hazard = new Hazard();
|
||||
hazard.latitude = nlat;
|
||||
hazard.longitude = nlon;
|
||||
hazard.type = type;
|
||||
hazard.user = user;
|
||||
await hazard.save();
|
||||
|
||||
return c.json({ success: true });
|
||||
});
|
||||
}
|
||||
|
||||
app.get("/api/saved", async (c) => {
|
||||
const authHeader = c.req.header("Authorization");
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
|
||||
Reference in New Issue
Block a user