diff --git a/seed-data/fake-articles.json b/seed-data/fake-articles.json deleted file mode 100644 index ef83b34..0000000 --- a/seed-data/fake-articles.json +++ /dev/null @@ -1,302 +0,0 @@ -[ - { - "title": "Account Executive" - }, - { - "title": "Engineer II" - }, - { - "title": "Data Coordinator" - }, - { - "title": "Senior Editor" - }, - { - "title": "Senior Quality Engineer" - }, - { - "title": "Statistician III" - }, - { - "title": "Programmer I" - }, - { - "title": "Office Assistant II" - }, - { - "title": "VP Marketing" - }, - { - "title": "Senior Quality Engineer" - }, - { - "title": "Business Systems Development Analyst" - }, - { - "title": "Chemical Engineer" - }, - { - "title": "Director of Sales" - }, - { - "title": "Chief Design Engineer" - }, - { - "title": "Editor" - }, - { - "title": "Speech Pathologist" - }, - { - "title": "Pharmacist" - }, - { - "title": "Operator" - }, - { - "title": "Human Resources Assistant III" - }, - { - "title": "Computer Systems Analyst II" - }, - { - "title": "Sales Associate" - }, - { - "title": "Desktop Support Technician" - }, - { - "title": "Executive Secretary" - }, - { - "title": "Quality Control Specialist" - }, - { - "title": "Research Associate" - }, - { - "title": "Software Consultant" - }, - { - "title": "Staff Scientist" - }, - { - "title": "Senior Sales Associate" - }, - { - "title": "Business Systems Development Analyst" - }, - { - "title": "VP Sales" - }, - { - "title": "Mechanical Systems Engineer" - }, - { - "title": "Information Systems Manager" - }, - { - "title": "Internal Auditor" - }, - { - "title": "Product Engineer" - }, - { - "title": "Legal Assistant" - }, - { - "title": "GIS Technical Architect" - }, - { - "title": "Software Consultant" - }, - { - "title": "Paralegal" - }, - { - "title": "Nurse" - }, - { - "title": "Biostatistician II" - }, - { - "title": "Web Designer I" - }, - { - "title": "Financial Analyst" - }, - { - "title": "Administrative Officer" - }, - { - "title": "VP Accounting" - }, - { - "title": "Biostatistician IV" - }, - { - "title": "Data Coordinator" - }, - { - "title": "Occupational Therapist" - }, - { - "title": "Web Developer IV" - }, - { - "title": "Quality Control Specialist" - }, - { - "title": "General Manager" - }, - { - "title": "Assistant Manager" - }, - { - "title": "Sales Associate" - }, - { - "title": "VP Marketing" - }, - { - "title": "Graphic Designer" - }, - { - "title": "Operator" - }, - { - "title": "Senior Financial Analyst" - }, - { - "title": "Information Systems Manager" - }, - { - "title": "Tax Accountant" - }, - { - "title": "Research Assistant II" - }, - { - "title": "Quality Engineer" - }, - { - "title": "Staff Scientist" - }, - { - "title": "Account Representative I" - }, - { - "title": "Clinical Specialist" - }, - { - "title": "Web Developer II" - }, - { - "title": "Desktop Support Technician" - }, - { - "title": "Electrical Engineer" - }, - { - "title": "Registered Nurse" - }, - { - "title": "Paralegal" - }, - { - "title": "Financial Advisor" - }, - { - "title": "Senior Cost Accountant" - }, - { - "title": "Senior Financial Analyst" - }, - { - "title": "Safety Technician III" - }, - { - "title": "Recruiting Manager" - }, - { - "title": "Engineer III" - }, - { - "title": "Social Worker" - }, - { - "title": "Assistant Manager" - }, - { - "title": "Financial Analyst" - }, - { - "title": "Health Coach II" - }, - { - "title": "Database Administrator III" - }, - { - "title": "Senior Editor" - }, - { - "title": "Research Nurse" - }, - { - "title": "Graphic Designer" - }, - { - "title": "Quality Engineer" - }, - { - "title": "Media Manager II" - }, - { - "title": "Payment Adjustment Coordinator" - }, - { - "title": "Desktop Support Technician" - }, - { - "title": "Legal Assistant" - }, - { - "title": "Research Associate" - }, - { - "title": "Operator" - }, - { - "title": "Speech Pathologist" - }, - { - "title": "Senior Editor" - }, - { - "title": "Financial Analyst" - }, - { - "title": "Professor" - }, - { - "title": "Registered Nurse" - }, - { - "title": "Electrical Engineer" - }, - { - "title": "Actuary" - }, - { - "title": "Nuclear Power Engineer" - }, - { - "title": "Social Worker" - }, - { - "title": "Safety Technician IV" - }, - { - "title": "Web Developer I" - } -] diff --git a/seed-data/fake-categories.json b/seed-data/fake-categories.json deleted file mode 100644 index 91a572a..0000000 --- a/seed-data/fake-categories.json +++ /dev/null @@ -1,302 +0,0 @@ -[ - { - "name": "Green Sotol" - }, - { - "name": "Mexican Prairie Clover" - }, - { - "name": "Stipulate Leaf-flower" - }, - { - "name": "Kawelu" - }, - { - "name": "Spiked Crested Coralroot" - }, - { - "name": "Texas Windmill Grass" - }, - { - "name": "Mountain Alder" - }, - { - "name": "Macdougal Verbena" - }, - { - "name": "European Aspen" - }, - { - "name": "Torrey's Willowherb" - }, - { - "name": "Ailanthus" - }, - { - "name": "Shortstalk Stinkweed" - }, - { - "name": "Black Damar" - }, - { - "name": "Canelillo" - }, - { - "name": "Veatch's Island Broom" - }, - { - "name": "Lewton's Milkwort" - }, - { - "name": "Hungarian Milkvetch" - }, - { - "name": "Palmer Evening Primrose" - }, - { - "name": "Smooth Chastetree" - }, - { - "name": "Jaeger's Joshua Tree" - }, - { - "name": "Roughhairy Maiden Fern" - }, - { - "name": "Florida Orchid" - }, - { - "name": "Belonia Lichen" - }, - { - "name": "Bigfruit Evening Primrose" - }, - { - "name": "Fitch's Tarweed" - }, - { - "name": "Coastal Plain Dawnflower" - }, - { - "name": "Clokey's Gilia" - }, - { - "name": "Indian Jointvetch" - }, - { - "name": "Wreath Lichen" - }, - { - "name": "Cumberland Xanthoparmelia Lichen" - }, - { - "name": "Spectacular Flatsedge" - }, - { - "name": "Pride Of California" - }, - { - "name": "Feverfew" - }, - { - "name": "Comb Wash Buckwheat" - }, - { - "name": "Sweet Woodreed" - }, - { - "name": "Delicate Violet Orchid" - }, - { - "name": "Canadian Blacksnakeroot" - }, - { - "name": "Wax Currant" - }, - { - "name": "Western Mountain Ash" - }, - { - "name": "Rhodomyrtus" - }, - { - "name": "Johnston's Knotweed" - }, - { - "name": "Kauai Bur Cucumber" - }, - { - "name": "Cain's Reedgrass" - }, - { - "name": "San Diego Pitchersage" - }, - { - "name": "Rock Goldenrod" - }, - { - "name": "Itchgrass" - }, - { - "name": "Threadleaf Horsebrush" - }, - { - "name": "Red Hills Vervain" - }, - { - "name": "Louisiana Bluestar" - }, - { - "name": "Utah Sweetvetch" - }, - { - "name": "Kauila" - }, - { - "name": "Sea Hibiscus" - }, - { - "name": "Derris" - }, - { - "name": "Florida Tasselflower" - }, - { - "name": "Glossy Hawthorn" - }, - { - "name": "Ahlner's Microcalicium Lichen" - }, - { - "name": "Aster" - }, - { - "name": "Elegant Hawthorn" - }, - { - "name": "Pricklypear" - }, - { - "name": "Parry's Sage" - }, - { - "name": "Redberry Buckthorn" - }, - { - "name": "Baden's Bluegrass" - }, - { - "name": "Utah Columbine" - }, - { - "name": "Obscure Shield Lichen" - }, - { - "name": "Showy Orchid" - }, - { - "name": "Silverleafed Princess Flower" - }, - { - "name": "Oahu Stenogyne" - }, - { - "name": "Hammond's Claytonia" - }, - { - "name": "Owyhee River Stickseed" - }, - { - "name": "Southwestern Cosmos" - }, - { - "name": "Toothed Flatsedge" - }, - { - "name": "Vegetable Fern" - }, - { - "name": "Rose" - }, - { - "name": "Desert Wishbone-bush" - }, - { - "name": "Rocky Mountain Woodsia" - }, - { - "name": "East Indian Lemongrass" - }, - { - "name": "Coville's Erigeron" - }, - { - "name": "Spiral Flag" - }, - { - "name": "Nevada Milkvetch" - }, - { - "name": "Douglas's Catchfly" - }, - { - "name": "Silverleaf Phacelia" - }, - { - "name": "Canadian Ricegrass" - }, - { - "name": "Barrier Range Wattle" - }, - { - "name": "Brooks' Alsophila" - }, - { - "name": "Calder's Bladderpod" - }, - { - "name": "Desert Brickellbush" - }, - { - "name": "Echeveria" - }, - { - "name": "Caruzo" - }, - { - "name": "American Black Nightshade" - }, - { - "name": "Whiteflower Goldenbush" - }, - { - "name": "Littleleaf Milkwort" - }, - { - "name": "Fir Mistletoe" - }, - { - "name": "Disc Lichen" - }, - { - "name": "Flagstaff Rockcress" - }, - { - "name": "Golden Spiderflower" - }, - { - "name": "Yellow Fumewort" - }, - { - "name": "Dot Lichen" - }, - { - "name": "Ross' Avens" - }, - { - "name": "Sierra Bluecup" - }, - { - "name": "Sausage Tree" - } -] diff --git a/seed-data/fake-users.json b/seed-data/fake-users.json deleted file mode 100644 index 3e2dbf5..0000000 --- a/seed-data/fake-users.json +++ /dev/null @@ -1,417 +0,0 @@ -[ - { - "name": "Léandre", - "email": "amant0@hc360.com", - "image": "https://robohash.org/autnihilquasi.png?size=50x50&set=set1" - }, - { - "name": "Céline", - "email": "aedsall1@zdnet.com", - "image": "https://robohash.org/temporibusfacerevoluptatem.png?size=50x50&set=set1" - }, - { - "name": "Vénus", - "email": "bcovington2@dion.ne.jp", - "image": "https://robohash.org/architectoconsecteturconsequatur.png?size=50x50&set=set1" - }, - { - "name": "Loïca", - "email": "msteinhammer3@columbia.edu", - "image": "https://robohash.org/pariaturullamaut.png?size=50x50&set=set1" - }, - { - "name": "Táng", - "email": "fmichurin4@is.gd", - "image": "https://robohash.org/eaquevoluptasfugit.png?size=50x50&set=set1" - }, - { - "name": "Andréa", - "email": "kbygrave5@pen.io", - "image": "https://robohash.org/eligendinatusa.png?size=50x50&set=set1" - }, - { - "name": "Léandre", - "email": "bbargery6@ocn.ne.jp", - "image": "https://robohash.org/estidquod.png?size=50x50&set=set1" - }, - { - "name": "Chloé", - "email": "mharnor7@ameblo.jp", - "image": "https://robohash.org/facilisducimuset.png?size=50x50&set=set1" - }, - { - "name": "Cunégonde", - "email": "lgiovanardi8@liveinternet.ru", - "image": "https://robohash.org/perspiciatisaliquidsit.png?size=50x50&set=set1" - }, - { - "name": "Dafnée", - "email": "ebleiman9@reuters.com", - "image": "https://robohash.org/quiquiaquaerat.png?size=50x50&set=set1" - }, - { - "name": "Laurène", - "email": "ibostona@tuttocitta.it", - "image": "https://robohash.org/quossolutaenim.png?size=50x50&set=set1" - }, - { - "name": "Pénélope", - "email": "kpedrollob@nyu.edu", - "image": "https://robohash.org/molestiasnullaomnis.png?size=50x50&set=set1" - }, - { - "name": "Laïla", - "email": "noransc@symantec.com", - "image": "https://robohash.org/rerumharumut.png?size=50x50&set=set1" - }, - { - "name": "Yáo", - "email": "hsived@oaic.gov.au", - "image": "https://robohash.org/etiuremolestias.png?size=50x50&set=set1" - }, - { - "name": "Judicaël", - "email": "nfockee@wordpress.org", - "image": "https://robohash.org/quisquamestaccusamus.png?size=50x50&set=set1" - }, - { - "name": "Léane", - "email": "nalwenf@hexun.com", - "image": "https://robohash.org/quasimagnamab.png?size=50x50&set=set1" - }, - { - "name": "Publicité", - "email": "bgeorgeg@howstuffworks.com", - "image": "https://robohash.org/repellendusquiculpa.png?size=50x50&set=set1" - }, - { - "name": "Kuí", - "email": "celdonh@blinklist.com", - "image": "https://robohash.org/doloremundequo.png?size=50x50&set=set1" - }, - { - "name": "Clémence", - "email": "mcommussoi@scribd.com", - "image": "https://robohash.org/quidoloresat.png?size=50x50&set=set1" - }, - { - "name": "Agnès", - "email": "nzambonj@nymag.com", - "image": "https://robohash.org/etnemoconsequatur.png?size=50x50&set=set1" - }, - { - "name": "Nadège", - "email": "mwhithamk@bbb.org", - "image": "https://robohash.org/accusamushicest.png?size=50x50&set=set1" - }, - { - "name": "Miléna", - "email": "glisettl@merriam-webster.com", - "image": "https://robohash.org/veniamplaceatadipisci.png?size=50x50&set=set1" - }, - { - "name": "Séréna", - "email": "wpoundsm@yellowpages.com", - "image": "https://robohash.org/nihiladea.png?size=50x50&set=set1" - }, - { - "name": "Chloé", - "email": "sescalen@imgur.com", - "image": "https://robohash.org/optiovoluptatibusaut.png?size=50x50&set=set1" - }, - { - "name": "Maëlyss", - "email": "nyoskowitzo@omniture.com", - "image": "https://robohash.org/expeditanobismaxime.png?size=50x50&set=set1" - }, - { - "name": "Cunégonde", - "email": "awellenp@de.vu", - "image": "https://robohash.org/commodiutest.png?size=50x50&set=set1" - }, - { - "name": "Valérie", - "email": "zduchatelq@imageshack.us", - "image": "https://robohash.org/omnisdoloremtemporibus.png?size=50x50&set=set1" - }, - { - "name": "Aloïs", - "email": "foxburyr@github.io", - "image": "https://robohash.org/cumnemoquia.png?size=50x50&set=set1" - }, - { - "name": "Eliès", - "email": "bmactimpanys@moonfruit.com", - "image": "https://robohash.org/eosvitaeveritatis.png?size=50x50&set=set1" - }, - { - "name": "Maëline", - "email": "abaget@gnu.org", - "image": "https://robohash.org/magnivoluptatibussoluta.png?size=50x50&set=set1" - }, - { - "name": "Pål", - "email": "ssunocku@discuz.net", - "image": "https://robohash.org/accusamusquaedelectus.png?size=50x50&set=set1" - }, - { - "name": "Irène", - "email": "reneasv@sphinn.com", - "image": "https://robohash.org/assumendahicperferendis.png?size=50x50&set=set1" - }, - { - "name": "Renée", - "email": "dmclarenw@skyrock.com", - "image": "https://robohash.org/voluptatemrecusandaeest.png?size=50x50&set=set1" - }, - { - "name": "Léonore", - "email": "breimsx@tmall.com", - "image": "https://robohash.org/natusporropariatur.png?size=50x50&set=set1" - }, - { - "name": "Táng", - "email": "tcolty@usnews.com", - "image": "https://robohash.org/autenimlabore.png?size=50x50&set=set1" - }, - { - "name": "Ruì", - "email": "bblazaz@mac.com", - "image": "https://robohash.org/quosiuremaiores.png?size=50x50&set=set1" - }, - { - "name": "Anaëlle", - "email": "vsimon10@npr.org", - "image": "https://robohash.org/velmolestiasullam.png?size=50x50&set=set1" - }, - { - "name": "Clémence", - "email": "bedmonds11@economist.com", - "image": "https://robohash.org/consequaturimpeditmollitia.png?size=50x50&set=set1" - }, - { - "name": "Eloïse", - "email": "jmaster12@guardian.co.uk", - "image": "https://robohash.org/autdolormolestiae.png?size=50x50&set=set1" - }, - { - "name": "Adélaïde", - "email": "mchazier13@slate.com", - "image": "https://robohash.org/laborereprehenderitsequi.png?size=50x50&set=set1" - }, - { - "name": "Séverine", - "email": "dgisborne14@rediff.com", - "image": "https://robohash.org/quiavoluptatemet.png?size=50x50&set=set1" - }, - { - "name": "Publicité", - "email": "gjobson15@vk.com", - "image": "https://robohash.org/eteaet.png?size=50x50&set=set1" - }, - { - "name": "Miléna", - "email": "bskarin16@buzzfeed.com", - "image": "https://robohash.org/facerenecessitatibussuscipit.png?size=50x50&set=set1" - }, - { - "name": "Méryl", - "email": "jcurrington17@dropbox.com", - "image": "https://robohash.org/molestiasasperioresmollitia.png?size=50x50&set=set1" - }, - { - "name": "Dù", - "email": "aandroli18@infoseek.co.jp", - "image": "https://robohash.org/namillumquo.png?size=50x50&set=set1" - }, - { - "name": "Liè", - "email": "hcornwall19@mozilla.com", - "image": "https://robohash.org/dignissimosconsequaturanimi.png?size=50x50&set=set1" - }, - { - "name": "Estève", - "email": "mshoutt1a@amazonaws.com", - "image": "https://robohash.org/velitsolutatotam.png?size=50x50&set=set1" - }, - { - "name": "Kuí", - "email": "cdrysdall1b@51.la", - "image": "https://robohash.org/utquodeos.png?size=50x50&set=set1" - }, - { - "name": "Rébecca", - "email": "ldavall1c@vimeo.com", - "image": "https://robohash.org/delenitiquasiid.png?size=50x50&set=set1" - }, - { - "name": "Eléonore", - "email": "brickhuss1d@ustream.tv", - "image": "https://robohash.org/consecteturcommodiiure.png?size=50x50&set=set1" - }, - { - "name": "Crééz", - "email": "jaggott1e@is.gd", - "image": "https://robohash.org/minimanisidelectus.png?size=50x50&set=set1" - }, - { - "name": "Célia", - "email": "kspuffard1f@ca.gov", - "image": "https://robohash.org/ipsumaliquidcumque.png?size=50x50&set=set1" - }, - { - "name": "Marie-thérèse", - "email": "cmaharg1g@psu.edu", - "image": "https://robohash.org/liberoquaerataut.png?size=50x50&set=set1" - }, - { - "name": "Publicité", - "email": "ineeds1h@example.com", - "image": "https://robohash.org/aliquidrepudiandaeeum.png?size=50x50&set=set1" - }, - { - "name": "Maïlis", - "email": "lalliston1i@miibeian.gov.cn", - "image": "https://robohash.org/estoptioquia.png?size=50x50&set=set1" - }, - { - "name": "Bérengère", - "email": "tcullingford1j@squidoo.com", - "image": "https://robohash.org/saepeetvel.png?size=50x50&set=set1" - }, - { - "name": "Styrbjörn", - "email": "skalewe1k@archive.org", - "image": "https://robohash.org/sednequevoluptatem.png?size=50x50&set=set1" - }, - { - "name": "Bérénice", - "email": "kgoudard1l@dell.com", - "image": "https://robohash.org/necessitatibusvelrerum.png?size=50x50&set=set1" - }, - { - "name": "Nuó", - "email": "lpenticost1m@angelfire.com", - "image": "https://robohash.org/autemsitsaepe.png?size=50x50&set=set1" - }, - { - "name": "Cloé", - "email": "idomnin1n@bbc.co.uk", - "image": "https://robohash.org/utetut.png?size=50x50&set=set1" - }, - { - "name": "Irène", - "email": "ddutchburn1o@npr.org", - "image": "https://robohash.org/etculpased.png?size=50x50&set=set1" - }, - { - "name": "Stéphanie", - "email": "achafney1p@dagondesign.com", - "image": "https://robohash.org/laudantiumilloconsequatur.png?size=50x50&set=set1" - }, - { - "name": "Eloïse", - "email": "gholttom1q@salon.com", - "image": "https://robohash.org/voluptatemestaut.png?size=50x50&set=set1" - }, - { - "name": "Märta", - "email": "agierek1r@rediff.com", - "image": "https://robohash.org/sintcommodiid.png?size=50x50&set=set1" - }, - { - "name": "Mélinda", - "email": "dkeoghan1s@java.com", - "image": "https://robohash.org/erroripsumdoloribus.png?size=50x50&set=set1" - }, - { - "name": "Adélie", - "email": "bsenior1t@ca.gov", - "image": "https://robohash.org/possimusistelabore.png?size=50x50&set=set1" - }, - { - "name": "Célia", - "email": "lstenner1u@drupal.org", - "image": "https://robohash.org/nemonihilquas.png?size=50x50&set=set1" - }, - { - "name": "Géraldine", - "email": "mstephens1v@csmonitor.com", - "image": "https://robohash.org/providentillumlibero.png?size=50x50&set=set1" - }, - { - "name": "Inès", - "email": "mskacel1w@nationalgeographic.com", - "image": "https://robohash.org/quiutvoluptatem.png?size=50x50&set=set1" - }, - { - "name": "Táng", - "email": "swilber1x@reverbnation.com", - "image": "https://robohash.org/rerumrepellatdolor.png?size=50x50&set=set1" - }, - { - "name": "Maëlyss", - "email": "ncamings1y@rediff.com", - "image": "https://robohash.org/quamminimanon.png?size=50x50&set=set1" - }, - { - "name": "Jú", - "email": "jcard1z@instagram.com", - "image": "https://robohash.org/voluptasinciduntrerum.png?size=50x50&set=set1" - }, - { - "name": "Lauréna", - "email": "lstruttman20@msu.edu", - "image": "https://robohash.org/exercitationemquiea.png?size=50x50&set=set1" - }, - { - "name": "Nadège", - "email": "mcreber21@accuweather.com", - "image": "https://robohash.org/officiisdelenitia.png?size=50x50&set=set1" - }, - { - "name": "Aloïs", - "email": "lsquires22@va.gov", - "image": "https://robohash.org/quiquasdolorum.png?size=50x50&set=set1" - }, - { - "name": "Loïs", - "email": "btempest23@taobao.com", - "image": "https://robohash.org/quisminimaaccusantium.png?size=50x50&set=set1" - }, - { - "name": "Åke", - "email": "mmullan24@forbes.com", - "image": "https://robohash.org/molestiaerepellenduseos.png?size=50x50&set=set1" - }, - { - "name": "Mélia", - "email": "afarnhill25@163.com", - "image": "https://robohash.org/quasdoloresquam.png?size=50x50&set=set1" - }, - { - "name": "Maëlyss", - "email": "wpache26@sakura.ne.jp", - "image": "https://robohash.org/doloresintrecusandae.png?size=50x50&set=set1" - }, - { - "name": "Loïca", - "email": "tsoal27@taobao.com", - "image": "https://robohash.org/accusamusilloaut.png?size=50x50&set=set1" - }, - { - "name": "Léana", - "email": "gparkisson28@jiathis.com", - "image": "https://robohash.org/omnisarchitectoaut.png?size=50x50&set=set1" - }, - { - "name": "Anaël", - "email": "nslegg29@canalblog.com", - "image": "https://robohash.org/quiaidadipisci.png?size=50x50&set=set1" - }, - { - "name": "Marie-josée", - "email": "salford2a@hubpages.com", - "image": "https://robohash.org/nihilestillo.png?size=50x50&set=set1" - } -] diff --git a/seed-data/index.ts b/seed-data/index.ts new file mode 100644 index 0000000..e5ece4c --- /dev/null +++ b/seed-data/index.ts @@ -0,0 +1,50 @@ +import "dotenv/config"; +import { db } from "../src/server/db"; +import { articles, categories, users } from "../src/server/db/schema"; + +async function seed() { + const usersData = Array.from({ length: 100 }).map((_, i) => ({ + name: `User ${i + 1}`, + email: `user${i + 1}@example.com`, + createdAt: new Date(Date.now() + i), + })); + const u = await db + .insert(users) + .values(usersData) + .returning({ id: users.id }); + console.log("Seeded " + u.length + " users"); + + const categoriesData = Array.from({ length: 250 }).map((_, i) => ({ + name: `Kategorie ${i + 1}`, + slug: `Kategorie-${i + 1}`, + createdAt: new Date(Date.now() + i), + })); + const c = await db.insert(categories).values(categoriesData).returning({ + id: categories.id, + }); + console.log("Seeded " + c.length + " categories"); + + const articlesData = Array.from({ length: 500 }).map((_, i) => ({ + title: `Artikel ${i + 1}`, + slug: `Artikel-${i + 1}`, + createdAt: new Date(Date.now() + i * 5), + published: true, + })); + const a = await db + .insert(articles) + .values(articlesData) + .returning({ id: articles.id }); + console.log("Seeded " + a.length + " articles"); +} + +async function init() { + try { + await seed(); + } catch (error) { + console.error(error); + } finally { + process.exit(); + } +} + +init(); diff --git a/seed-data/seed.ts b/seed-data/seed.ts deleted file mode 100644 index 1135ace..0000000 --- a/seed-data/seed.ts +++ /dev/null @@ -1,42 +0,0 @@ -import "dotenv/config"; -import { db, DBType } from "../src/server/db"; -import { articles, categories, users } from "../src/server/db/schema"; -import fakeArticles from "./fake-articles.json"; -import fakeUsers from "./fake-users.json"; -import fakeCategories from "./fake-categories.json"; -import { generateSlug } from "@/lib/utils"; -import { createId } from "@paralleldrive/cuid2"; - -async function seed() { - const u = await db - .insert(users) - .values(fakeUsers) - .returning({ id: users.id }); - console.log("Seeded " + u.length + " users"); - - const c = await db - .insert(categories) - .values( - fakeCategories.map(({ name }) => ({ name, slug: generateSlug(name) })), - ) - .returning({ - id: categories.id, - }); - console.log("Seeded " + c.length + " categories"); - - const a = await db - .insert(articles) - .values( - fakeArticles.map(({ title }) => ({ - title, - slug: createId(), - published: true, - })), - ) - .returning({ id: articles.id }); - console.log("Seeded " + a.length + " articles"); -} - -seed() - .catch(console.error) - .finally(() => process.exit()); diff --git a/src/app/(PAGES)/_components/main-page.tsx b/src/app/(PAGES)/_components/main-page.tsx index 08dee67..81ffdd6 100644 --- a/src/app/(PAGES)/_components/main-page.tsx +++ b/src/app/(PAGES)/_components/main-page.tsx @@ -1,8 +1,8 @@ "use client"; import React from "react"; -import CategoriesGrid, { - CategoriesGridSkeleton, -} from "@/components/category/categories-grid"; +import CategoryGrid, { + CategoryGridSkeleton, +} from "@/components/category/grid/category-grid"; import ArticleGrid, { ArticleGridSkeleton, } from "@/components/article/grid/article-grid"; diff --git a/src/app/(PAGES)/kategorie/page.tsx b/src/app/(PAGES)/kategorie/page.tsx index c1899ea..371df52 100644 --- a/src/app/(PAGES)/kategorie/page.tsx +++ b/src/app/(PAGES)/kategorie/page.tsx @@ -1,13 +1,11 @@ -import CategoriesGrid from "@/components/category/categories-grid"; +import InfiniteCategoryGrid from "@/components/category/grid/infinite-category-grid"; import { api } from "@/trpc/server"; import React from "react"; async function Page() { - const categories = await api.category.getAll(); return ( <> -

Kategorien

- + ); } diff --git a/src/components/article/article-filter-bar.tsx b/src/components/article/article-filter-bar.tsx index 2090c4f..3497a3f 100644 --- a/src/components/article/article-filter-bar.tsx +++ b/src/components/article/article-filter-bar.tsx @@ -117,6 +117,4 @@ const sortItems: Array<{ { Icon: CalendarArrowDown, value: "oldest", label: "Älteste" }, { Icon: ArrowDownAZ, value: "abc", label: "Alphabetisch A-Z" }, { Icon: ArrowUpAz, value: "cba", label: "Alphabetisch Z-A" }, - // { Icon: Eye, value: "popular", label: "Beliebteste" }, - // { Icon: MessageSquare, value: "commented", label: "Meistkommentiert" }, ]; diff --git a/src/components/category/category-filter-bar.tsx b/src/components/category/category-filter-bar.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/category/categories-grid.tsx b/src/components/category/grid/category-grid.tsx similarity index 57% rename from src/components/category/categories-grid.tsx rename to src/components/category/grid/category-grid.tsx index f4085f4..c1ddb7e 100644 --- a/src/components/category/categories-grid.tsx +++ b/src/components/category/grid/category-grid.tsx @@ -1,13 +1,14 @@ import CategoryCard from "@/components/category/category-card"; import { Category } from "@/server/db/schema"; import React from "react"; -import { Skeleton } from "../ui/skeleton"; +import { Skeleton } from "@/components/ui/skeleton"; -const GRID_CLASS = "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"; +export const CATEGORY_GRID_CLASS = + "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"; -function CategoriesGrid({ categories }: { categories: Category[] }) { +function CategoryGrid({ categories }: { categories: Category[] }) { return ( - + {categories.map((category) => (
  • @@ -17,12 +18,12 @@ function CategoriesGrid({ categories }: { categories: Category[] }) { ); } -export default CategoriesGrid; +export default CategoryGrid; -export function CategoriesGridSkeleton() { +export function CategoryGridSkeleton() { const range = Array.from(new Array(6).keys()); return ( -
      +
        {range.map((i) => (
      • diff --git a/src/components/category/grid/infinite-category-grid.tsx b/src/components/category/grid/infinite-category-grid.tsx new file mode 100644 index 0000000..1f92bc6 --- /dev/null +++ b/src/components/category/grid/infinite-category-grid.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { api } from "@/trpc/react"; +import React from "react"; + +import CategoryCard from "../category-card"; +import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook"; +import { Skeleton } from "@/components/ui/skeleton"; +import { CATEGORY_GRID_CLASS } from "./category-grid"; +// import ArticleFilterBar, { ArticleFilter } from "../article-filter-bar"; + +export default function InfiniteCategoryGrid() { + // const [filter, setFilter] = React.useState( + // undefined, + // ); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + api.category.getByCursor.useInfiniteQuery( + { + // filter, + }, + { + getNextPageParam: (lastPage) => lastPage.nextCursor, + }, + ); + // Calculate all visible items across all loaded pages + const allItems = React.useMemo(() => { + return data?.pages.flatMap((page) => page.items) || []; + }, [data]); + + // Ref for bottom observation + const bottomObserverRef = React.useRef(null); + + useInfiniteItemsObserver({ + bottomObserverRef, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + }); + + return ( +
        + {/* */} + + {data?.pages?.length + ? allItems.map((category, idx) => ( +
      • + +
      • + )) + : null} + + {/* Loading indicator */} + {(isLoading || isFetchingNextPage) && + Array.from(new Array(isLoading ? 16 : 4).keys()).map((idx) => ( +
      • + +
      • + ))} + + {/* Bottom observer element */} + {hasNextPage && ( +
      • + )} +
      • +
        + ); +} diff --git a/src/lib/validation/zod/category.ts b/src/lib/validation/zod/category.ts index 666529f..4eeb688 100644 --- a/src/lib/validation/zod/category.ts +++ b/src/lib/validation/zod/category.ts @@ -5,3 +5,8 @@ export const categorySchema = z.object({ description: z.string().optional(), image: z.string().optional(), }); + +export const categoryFilterSchema = z.object({ + query: z.string().optional(), + sort: z.string().optional(), +}); diff --git a/src/server/api/routers/article.ts b/src/server/api/routers/article.ts index c92cec4..5c6e36e 100644 --- a/src/server/api/routers/article.ts +++ b/src/server/api/routers/article.ts @@ -11,22 +11,50 @@ import { articleSchema, createArticleSchema, } from "@/lib/validation/zod/article"; -import { and, asc, count, desc, eq, gt, ilike, like, sql } from "drizzle-orm"; +import { + and, + asc, + count, + desc, + eq, + gte, + ilike, + like, + lte, + sql, +} from "drizzle-orm"; import { hasPermission, Role } from "@/lib/validation/permissions"; import { generateSlug } from "@/lib/utils"; +import SuperJSON from "superjson"; + +type ArticleCursor = Pick; +const getArticleSorting = (sort: string, cursor?: ArticleCursor) => { + // Default to newest + const baseCase = { + orderBy: [desc(articles.createdAt), asc(articles.slug)], + cursor: cursor ? lte(articles.createdAt, cursor.createdAt) : undefined, + }; -const getArticleSorting = (sort: string) => { switch (sort) { case "newest": - return desc(articles.createdAt); + return baseCase; case "oldest": - return asc(articles.createdAt); + return { + orderBy: [asc(articles.createdAt), asc(articles.slug)], + cursor: cursor ? gte(articles.createdAt, cursor.createdAt) : undefined, + }; case "abc": - return asc(articles.title); + return { + orderBy: [asc(articles.title), asc(articles.slug)], + cursor: cursor ? gte(articles.title, cursor.title) : undefined, + }; case "cba": - return desc(articles.title); + return { + orderBy: [desc(articles.title), asc(articles.slug)], + cursor: cursor ? lte(articles.title, cursor.title) : undefined, + }; default: - return desc(articles.createdAt); // Default to newest + return baseCase; } }; @@ -59,15 +87,32 @@ export const articleRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const { cursor } = input!; const limit = input?.limit ?? 50; - const cursorArg = cursor ? gt(articles.slug, cursor) : undefined; + + // Decode cursor if using it + let cursorObj: ArticleCursor | undefined; + if (cursor) { + try { + cursorObj = SuperJSON.parse(Buffer.from(cursor, "base64").toString()); + } catch (e) { + // Handle invalid cursor + cursorObj = undefined; + } + } const queryFilterArg = input?.filter?.query?.length ? ilike(articles.title, "%" + input.filter.query + "%") : undefined; + const categoryArg = input?.filter?.category ? eq(articles.categoryId, input.filter.category) : undefined; - const orderBy = getArticleSorting(input?.filter?.sort ?? "newest"); + + const sortConfig = input?.filter?.sort ?? "newest"; + const { orderBy, cursor: cursorArg } = getArticleSorting( + sortConfig, + cursorObj, + ); + const items = await ctx.db.query.articles.findMany({ where: and( cursorArg, @@ -83,15 +128,26 @@ export const articleRouter = createTRPCRouter({ createdAt: true, }, }); - let nextCursor: typeof cursor | undefined = undefined; + + let nextCursor: string | undefined = undefined; if (items.length > limit) { - const nextItem = items.pop(); - nextCursor = nextItem!.slug; + console.log("Configure next cursor"); + const cursorItem = items.pop(); + // Create a cursor object with the relevant fields for sorting + const cursorData: ArticleCursor = { + slug: cursorItem!.slug, + createdAt: cursorItem!.createdAt, + title: cursorItem!.title, + }; + // Encode the cursor as base64 + nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString( + "base64", + ); } + return { items, nextCursor, - previousCursor: cursor, }; }), getAll: publicProcedure @@ -160,10 +216,6 @@ export const articleRouter = createTRPCRouter({ if (!isEditor) { throw new Error("You are not allowed to update articles"); } - console.log( - "Content before save", - JSON.stringify(input.article.content), - ); return await ctx.db .update(articles) diff --git a/src/server/api/routers/category.ts b/src/server/api/routers/category.ts index af83b4b..ce5f003 100644 --- a/src/server/api/routers/category.ts +++ b/src/server/api/routers/category.ts @@ -6,10 +6,58 @@ import { } from "@/server/api/trpc"; import { categories, Category } from "@/server/db/schema"; -import { count, eq, like } from "drizzle-orm"; +import { + and, + asc, + count, + desc, + eq, + gt, + gte, + ilike, + like, + lte, +} from "drizzle-orm"; import { hasPermission, Role } from "@/lib/validation/permissions"; -import { categorySchema } from "@/lib/validation/zod/category"; +import { + categoryFilterSchema, + categorySchema, +} from "@/lib/validation/zod/category"; import { generateSlug } from "@/lib/utils"; +import SuperJSON from "superjson"; + +type CategoryCursor = Pick; +const getCategorySorting = (sort: string, cursor?: CategoryCursor) => { + // Default to newest + const baseCase = { + orderBy: [desc(categories.createdAt), asc(categories.slug)], + cursor: cursor ? lte(categories.createdAt, cursor.createdAt) : undefined, + }; + + switch (sort) { + case "newest": + return baseCase; + case "oldest": + return { + orderBy: [asc(categories.createdAt), asc(categories.slug)], + cursor: cursor + ? gte(categories.createdAt, cursor.createdAt) + : undefined, + }; + case "abc": + return { + orderBy: [asc(categories.name), asc(categories.slug)], + cursor: cursor ? gte(categories.name, cursor.name) : undefined, + }; + case "cba": + return { + orderBy: [desc(categories.name), asc(categories.slug)], + cursor: cursor ? lte(categories.name, cursor.name) : undefined, + }; + default: + return baseCase; + } +}; export const categoryRouter = createTRPCRouter({ search: publicProcedure @@ -42,6 +90,71 @@ export const categoryRouter = createTRPCRouter({ }); }), + getByCursor: publicProcedure + .input( + z.object({ + limit: z.number().optional(), + cursor: z.string().optional(), + filter: categoryFilterSchema.optional(), + }), + ) + .query(async ({ ctx, input }) => { + const { cursor } = input!; + const limit = input?.limit ?? 50; + + // Decode cursor if using it + let cursorObj: CategoryCursor | undefined; + if (cursor) { + try { + cursorObj = SuperJSON.parse(Buffer.from(cursor, "base64").toString()); + } catch (e) { + // Handle invalid cursor + cursorObj = undefined; + } + } + const queryFilterArg = input?.filter?.query?.length + ? ilike(categories.name, "%" + input.filter.query + "%") + : undefined; + + const sortConfig = input?.filter?.sort ?? "newest"; + const { orderBy, cursor: cursorArg } = getCategorySorting( + sortConfig, + cursorObj, + ); + + const items = (await ctx.db.query.categories.findMany({ + where: and(cursorArg, queryFilterArg), + limit: limit + 1, + orderBy, + columns: { + name: true, + slug: true, + createdAt: true, + }, + })) as Category[]; + + let nextCursor: string | undefined = undefined; + if (items.length > limit) { + console.log("Configure next cursor"); + const cursorItem = items.pop(); + // Create a cursor object with the relevant fields for sorting + const cursorData: CategoryCursor = { + slug: cursorItem!.slug, + createdAt: cursorItem!.createdAt, + name: cursorItem!.name, + }; + // Encode the cursor as base64 + nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString( + "base64", + ); + } + + return { + items, + nextCursor, + }; + }), + getCount: publicProcedure.query(async ({ ctx }) => { return (await ctx.db.select({ count: count() }).from(categories))[0]?.count; }), diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 2b2d996..1a369ab 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -29,7 +29,7 @@ export const articles = createTable( .primaryKey() .$defaultFn(() => createId()) .notNull(), - title: varchar("title", { length: 256 }), + title: varchar("title", { length: 256 }).notNull(), slug: varchar("slug", { length: 256 }).unique().notNull(), authorId: varchar("author_id", { length: 255 }), content: jsonb("content").$type(), @@ -68,7 +68,7 @@ export const categories = createTable( .primaryKey() .$defaultFn(() => createId()) .notNull(), - name: varchar("name", { length: 256 }), + name: varchar("name", { length: 256 }).notNull(), description: text("description"), slug: varchar("slug", { length: 256 }).unique().notNull(),