Keep track of ICE and police locations in your city. Very much a work-in-progress and not ready yet, stay tuned I guess?

allow for quickly seeding the database

create seeding script and create a temporary API endpoint to allow for
quick database seeding during runtime

aria.yuri.observer d71eea23 17fc25b8

verified
Changed files
+243 -9
apps
web
src
features
db
server
routes
api
reports
seed
+149
apps/web/src/features/db/server/seed/data.ts
··· 1 + import type { InferSelectModel } from 'drizzle-orm'; 2 + import type { report } from '$features/db/server/schema'; 3 + 4 + import { 5 + type InsertReportWithRelations, 6 + type InsertContextWithRelations, 7 + MediaType, 8 + ContextType, 9 + type InsertMedia, 10 + } from '$features/db/types'; 11 + 12 + export type SeedData = { 13 + reports: InsertReportWithRelations<InsertContextWithRelations>[]; 14 + }; 15 + 16 + const imageMedia = (media: Omit<InsertMedia, 'type'>) => { 17 + return { 18 + type: MediaType.image, 19 + ...media, 20 + }; 21 + }; 22 + 23 + export const seedData: SeedData = { 24 + reports: [ 25 + { 26 + context: [ 27 + { 28 + type: ContextType.root, 29 + text: 'ICE activity spotted in area', 30 + location: { 31 + latitude: '34.7724', 32 + longitude: '-84.9819', 33 + }, 34 + media: [ 35 + imageMedia({ 36 + url: 'https://plus.unsplash.com/premium_photo-1687157829884-fae305709c06?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=900', 37 + altText: 38 + 'A cop car with its lights on and a city building in the background, out of focus.', 39 + }), 40 + imageMedia({ 41 + url: 'https://plus.unsplash.com/premium_photo-1686695196013-b0e9aef9cdff?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTN8fHBvbGljZXxlbnwwfHwwfHx8MA%3D%3D&auto=format&fit=crop&q=60&w=900', 42 + altText: 43 + 'A cop with a stupidly smug face sitting on a motorcycle being useless and cruel.', 44 + }), 45 + imageMedia({ 46 + url: 'https://images.unsplash.com/photo-1652793806995-7bf3265e40b0?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1760', 47 + altText: 'Two Toronto police cars', 48 + }), 49 + imageMedia({ 50 + url: 'https://images.unsplash.com/photo-1590995891215-0336d27411de?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 51 + altText: 52 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 53 + }), 54 + imageMedia({ 55 + url: 'https://images.unsplash.com/photo-1591073214708-44d56a561981?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 56 + altText: 57 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 58 + }), 59 + imageMedia({ 60 + url: 'https://images.unsplash.com/photo-1520085401243-fa89fc9ff1b7?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1642', 61 + altText: 62 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 63 + }), 64 + imageMedia({ 65 + url: 'https://images.unsplash.com/photo-1758405282251-26903f4b7fcb?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 66 + altText: 67 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 68 + }), 69 + imageMedia({ 70 + url: 'https://images.unsplash.com/photo-1758405282247-86deca3ecc87?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 71 + altText: 72 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 73 + }), 74 + imageMedia({ 75 + url: 'https://images.unsplash.com/photo-1686153957738-fed649408234?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1762', 76 + altText: 77 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 78 + }), 79 + ], 80 + }, 81 + { 82 + type: ContextType.info, 83 + text: 'a protester was just kidnapped by ICE, please protect yourself and stay aware of your surroundings at all times in this area.', 84 + location: {}, 85 + media: [ 86 + imageMedia({ 87 + url: 'https://plus.unsplash.com/premium_photo-1683134562864-1670a96e3022?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 88 + altText: 'person being detained by masked ICE thugs', 89 + }), 90 + ], 91 + }, 92 + ], 93 + }, 94 + { 95 + context: [ 96 + { 97 + type: ContextType.root, 98 + location: { 99 + latitude: '34.76803247376194', 100 + longitude: '-84.97822846789504', 101 + }, 102 + media: [ 103 + imageMedia({ 104 + url: 'https://images.unsplash.com/photo-1590995891215-0336d27411de?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 105 + altText: 106 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 107 + }), 108 + imageMedia({ 109 + url: 'https://images.unsplash.com/photo-1591073214708-44d56a561981?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 110 + altText: 111 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 112 + }), 113 + imageMedia({ 114 + url: 'https://images.unsplash.com/photo-1520085401243-fa89fc9ff1b7?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1642', 115 + altText: 116 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 117 + }), 118 + imageMedia({ 119 + url: 'https://images.unsplash.com/photo-1758405282251-26903f4b7fcb?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 120 + altText: 121 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 122 + }), 123 + imageMedia({ 124 + url: 'https://images.unsplash.com/photo-1758405282247-86deca3ecc87?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 125 + altText: 126 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 127 + }), 128 + imageMedia({ 129 + url: 'https://images.unsplash.com/photo-1686153957738-fed649408234?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1762', 130 + altText: 131 + 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, consequuntur incidunt non, fugiat debitis quas atque porro quia necessitatibus facere ut molestiae amet, a nisi temporibus unde sequi. At, nam.', 132 + }), 133 + ], 134 + }, 135 + { 136 + type: ContextType.info, 137 + text: 'a protester was just kidnapped by ICE, please protect yourself and stay aware of your surroundings at all times in this area.', 138 + location: {}, 139 + media: [ 140 + imageMedia({ 141 + url: 'https://plus.unsplash.com/premium_photo-1683134562864-1670a96e3022?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740', 142 + altText: 'person being detained by masked ICE thugs', 143 + }), 144 + ], 145 + }, 146 + ], 147 + }, 148 + ], 149 + };
+61
apps/web/src/features/db/server/seed/index.ts
··· 1 + import { db } from '$features/db/server'; 2 + import type { SelectedLocation } from '$features/db/types'; 3 + import { 4 + context as contextTable, 5 + location as locationTable, 6 + report as reportTable, 7 + media as mediaTable, 8 + } from '../schema'; 9 + import { seedData } from './data'; 10 + 11 + interface Omit { 12 + <T extends object, K extends [...(keyof T)[]]>( 13 + obj: T, 14 + ...keys: K 15 + ): { 16 + [K2 in Exclude<keyof T, K[number]>]: T[K2]; 17 + }; 18 + } 19 + 20 + const omit: Omit = (obj, ...keys) => { 21 + const ret = {} as { 22 + [K in keyof typeof obj]: (typeof obj)[K]; 23 + }; 24 + let key: keyof typeof obj; 25 + for (key in obj) { 26 + if (!keys.includes(key)) { 27 + ret[key] = obj[key]; 28 + } 29 + } 30 + return ret; 31 + }; 32 + 33 + export const seedDatabase = async () => { 34 + await db.transaction(async (tx) => { 35 + for (const { context: contextItems, ...reportData } of seedData.reports) { 36 + const parentReport = await tx.insert(reportTable).values(reportData).returning(); 37 + 38 + for (const { media: mediaItems, location: locationData, ...contextData } of contextItems) { 39 + // const contextLocation = !!locationData?.latitude ? await tx.insert(locationTable).values() : undefined; 40 + let contextLocation: SelectedLocation[] | undefined = undefined; 41 + 42 + if (!!locationData?.latitude && !!locationData?.longitude) { 43 + contextLocation = await tx.insert(locationTable).values(locationData).returning(); 44 + } 45 + 46 + const parentContext = await tx 47 + .insert(contextTable) 48 + .values({ 49 + ...contextData, 50 + locationId: contextLocation?.[0].id, 51 + reportId: parentReport[0].id, 52 + }) 53 + .returning(); 54 + 55 + for (const mediaData of mediaItems) { 56 + await tx.insert(mediaTable).values({ ...mediaData, contextId: parentContext[0].id }); 57 + } 58 + } 59 + } 60 + }); 61 + };
-9
apps/web/src/routes/api/reports/+server.ts
··· 1 - import { json } from '@sveltejs/kit'; 2 - import { getReports } from '$lib/api/reports/reports.remote'; 3 - 4 - // getReports 5 - export const GET = async () => { 6 - const reports = await getReports(); 7 - 8 - return json(reports); 9 - };
+33
apps/web/src/routes/api/seed/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 2 + import { seedDatabase } from '$features/db/server/seed'; 3 + import { SEEDING_ENABLED } from '$env/static/private'; 4 + 5 + // getReports 6 + export const GET = async () => { 7 + if (!SEEDING_ENABLED) 8 + return json( 9 + { 10 + error: 'nope', 11 + }, 12 + { status: 403 } 13 + ); 14 + 15 + try { 16 + await seedDatabase(); 17 + return json( 18 + { 19 + success: true, 20 + }, 21 + { status: 200 } 22 + ); 23 + } catch (e) { 24 + if (e instanceof Error) { 25 + return json( 26 + { 27 + error: e?.message ?? 'Unable to seed data', 28 + }, 29 + { status: 500 } 30 + ); 31 + } 32 + } 33 + };