feat: insert questions without removing (slower, but fixes fks)
This commit is contained in:
		
							parent
							
								
									73a47d94c3
								
							
						
					
					
						commit
						185464c0ae
					
				|  | @ -0,0 +1,5 @@ | |||
| ALTER TABLE "history" ADD COLUMN     "idref" TEXT; | ||||
| 
 | ||||
| ALTER TABLE "history" ADD CONSTRAINT "history_idref_fkey" FOREIGN KEY ("idref") REFERENCES "questions"("id") ON DELETE SET NULL ON UPDATE RESTRICT; | ||||
| 
 | ||||
| UPDATE "history" SET idref = id WHERE id in (SELECT id FROM "questions"); | ||||
|  | @ -1,5 +1,6 @@ | |||
| generator client { | ||||
|   provider = "prisma-client-js" | ||||
|   provider        = "prisma-client-js" | ||||
|   previewFeatures = ["interactiveTransactions"] | ||||
| } | ||||
| 
 | ||||
| generator pothos { | ||||
|  | @ -25,15 +26,17 @@ model Dashboard { | |||
| 
 | ||||
| model History { | ||||
|   id                String | ||||
|   idref             String? | ||||
|   question          Question? @relation(fields: [idref], references: [id], onDelete: SetNull, onUpdate: Restrict) | ||||
|   title             String | ||||
|   url               String | ||||
|   platform          String | ||||
|   description       String | ||||
|   options           Json | ||||
|   timestamp         DateTime @db.Timestamp(6) | ||||
|   timestamp         DateTime  @db.Timestamp(6) | ||||
|   qualityindicators Json | ||||
|   extra             Json | ||||
|   pk                Int      @id @default(autoincrement()) | ||||
|   pk                Int       @id @default(autoincrement()) | ||||
| 
 | ||||
|   @@index([id]) | ||||
|   @@map("history") | ||||
|  | @ -75,6 +78,8 @@ model Question { | |||
|   extra             Json | ||||
| 
 | ||||
|   onFrontpage FrontpageId? | ||||
|   history     History[] | ||||
| 
 | ||||
|   @@map("questions") | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,9 @@ import { prisma } from "../../database/prisma"; | |||
| export async function updateHistory() { | ||||
|   const questions = await prisma.question.findMany({}); | ||||
|   await prisma.history.createMany({ | ||||
|     data: questions, | ||||
|     data: questions.map((q) => ({ | ||||
|       ...q, | ||||
|       idref: q.id, | ||||
|     })), | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -89,27 +89,69 @@ export const processPlatform = async (platform: Platform) => { | |||
|     console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`); | ||||
|     return; | ||||
|   } | ||||
|   const results = await platform.fetcher(); | ||||
|   if (results && results.length) { | ||||
|     await prisma.$transaction([ | ||||
|       prisma.question.deleteMany({ | ||||
|         where: { | ||||
|           platform: platform.name, | ||||
|         }, | ||||
|       }), | ||||
|       prisma.question.createMany({ | ||||
|         data: results.map((q) => ({ | ||||
|           extra: {}, | ||||
|           timestamp: new Date(), | ||||
|           ...q, | ||||
|           qualityindicators: q.qualityindicators as object, // fighting typescript
 | ||||
|         })), | ||||
|       }), | ||||
|     ]); | ||||
|     console.log("Done"); | ||||
|   } else { | ||||
|   const fetchedQuestions = await platform.fetcher(); | ||||
|   if (!fetchedQuestions || !fetchedQuestions.length) { | ||||
|     console.log(`Platform ${platform.name} didn't return any results`); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const prepareQuestion = (q: FetchedQuestion): Question => { | ||||
|     return { | ||||
|       extra: {}, | ||||
|       timestamp: new Date(), | ||||
|       ...q, | ||||
|       platform: platform.name, | ||||
|       qualityindicators: q.qualityindicators as object, // fighting typescript
 | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   const oldQuestions = await prisma.question.findMany({ | ||||
|     where: { | ||||
|       platform: platform.name, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const fetchedIds = fetchedQuestions.map((q) => q.id); | ||||
|   const oldIds = oldQuestions.map((q) => q.id); | ||||
| 
 | ||||
|   const fetchedIdsSet = new Set(fetchedIds); | ||||
|   const oldIdsSet = new Set(oldIds); | ||||
| 
 | ||||
|   const createdQuestions: Question[] = []; | ||||
|   const updatedQuestions: Question[] = []; | ||||
|   const deletedIds = oldIds.filter((id) => !fetchedIdsSet.has(id)); | ||||
| 
 | ||||
|   for (const q of fetchedQuestions.map((q) => prepareQuestion(q))) { | ||||
|     if (oldIdsSet.has(q.id)) { | ||||
|       updatedQuestions.push(q); | ||||
|     } else { | ||||
|       // TODO - check if question has changed for better performance
 | ||||
|       createdQuestions.push(q); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   await prisma.question.createMany({ | ||||
|     data: createdQuestions, | ||||
|   }); | ||||
| 
 | ||||
|   for (const q of updatedQuestions) { | ||||
|     await prisma.question.update({ | ||||
|       where: { id: q.id }, | ||||
|       data: q, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   await prisma.question.deleteMany({ | ||||
|     where: { | ||||
|       id: { | ||||
|         in: deletedIds, | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   console.log( | ||||
|     `Done, ${deletedIds.length} deleted, ${updatedQuestions.length} updated, ${createdQuestions.length} created` | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export interface PlatformConfig { | ||||
|  |  | |||
|  | @ -15,11 +15,15 @@ export const xrisk: Platform = { | |||
|       encoding: "utf-8", | ||||
|     }); | ||||
|     let results = JSON.parse(fileRaw); | ||||
|     results = results.map((item) => ({ | ||||
|       ...item, | ||||
|       id: `${platformName}-${hash(item.title + " | " + item.url)}`, // some titles are non-unique, but title+url pair is always unique
 | ||||
|       platform: platformName, | ||||
|     })); | ||||
|     results = results.map((item) => { | ||||
|       item.extra = item.moreoriginsdata; | ||||
|       delete item.moreoriginsdata; | ||||
|       return { | ||||
|         ...item, | ||||
|         id: `${platformName}-${hash(item.title + " | " + item.url)}`, // some titles are non-unique, but title+url pair is always unique
 | ||||
|         platform: platformName, | ||||
|       }; | ||||
|     }); | ||||
|     return results; | ||||
|   }, | ||||
| }; | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| import { History, Question } from "@prisma/client"; | ||||
| 
 | ||||
| import { prisma } from "../../backend/database/prisma"; | ||||
| import { platforms, QualityIndicators } from "../../backend/platforms"; | ||||
| import { builder } from "../builder"; | ||||
|  | @ -78,44 +80,67 @@ export const ProbabilityOptionObj = builder | |||
|     }), | ||||
|   }); | ||||
| 
 | ||||
| const QuestionShapeInterface = builder | ||||
|   .interfaceRef<Question | History>("QuestionShape") | ||||
|   .implement({ | ||||
|     fields: (t) => ({ | ||||
|       title: t.exposeString("title"), | ||||
|       description: t.exposeString("description"), | ||||
|       url: t.exposeString("url", { | ||||
|         description: | ||||
|           "Non-unique, a very small number of platforms have a page for more than one prediction", | ||||
|       }), | ||||
|       platform: t.field({ | ||||
|         type: PlatformObj, | ||||
|         resolve: (parent) => parent.platform, | ||||
|       }), | ||||
|       timestamp: t.field({ | ||||
|         type: "Date", | ||||
|         description: "Timestamp at which metaforecast fetched the question", | ||||
|         resolve: (parent) => parent.timestamp, | ||||
|       }), | ||||
|       qualityIndicators: t.field({ | ||||
|         type: QualityIndicatorsObj, | ||||
|         resolve: (parent) => | ||||
|           parent.qualityindicators as any as QualityIndicators, | ||||
|       }), | ||||
|       options: t.field({ | ||||
|         type: [ProbabilityOptionObj], | ||||
|         resolve: ({ options }) => { | ||||
|           if (!Array.isArray(options)) { | ||||
|             return []; | ||||
|           } | ||||
|           return options as any[]; | ||||
|         }, | ||||
|       }), | ||||
|     }), | ||||
|   }); | ||||
| 
 | ||||
| export const HistoryObj = builder.prismaObject("History", { | ||||
|   findUnique: (history) => ({ pk: history.pk }), | ||||
|   interfaces: [QuestionShapeInterface], | ||||
|   fields: (t) => ({ | ||||
|     id: t.exposeID("pk", { | ||||
|       description: "History items are identified by their integer ids", | ||||
|     }), | ||||
|     questionId: t.exposeID("id", { | ||||
|       description: "Unique string which identifies the question", | ||||
|     }), | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
| export const QuestionObj = builder.prismaObject("Question", { | ||||
|   findUnique: (question) => ({ id: question.id }), | ||||
|   interfaces: [QuestionShapeInterface], | ||||
|   fields: (t) => ({ | ||||
|     id: t.exposeID("id", { | ||||
|       description: "Unique string which identifies the question", | ||||
|     }), | ||||
|     title: t.exposeString("title"), | ||||
|     description: t.exposeString("description"), | ||||
|     url: t.exposeString("url", { | ||||
|       description: | ||||
|         "Non-unique, a very small number of platforms have a page for more than one prediction", | ||||
|     }), | ||||
|     timestamp: t.field({ | ||||
|       type: "Date", | ||||
|       description: "Timestamp at which metaforecast fetched the question", | ||||
|       resolve: (parent) => parent.timestamp, | ||||
|     }), | ||||
|     platform: t.field({ | ||||
|       type: PlatformObj, | ||||
|       resolve: (parent) => parent.platform, | ||||
|     }), | ||||
|     qualityIndicators: t.field({ | ||||
|       type: QualityIndicatorsObj, | ||||
|       resolve: (parent) => parent.qualityindicators as any as QualityIndicators, | ||||
|     }), | ||||
|     options: t.field({ | ||||
|       type: [ProbabilityOptionObj], | ||||
|       resolve: ({ options }) => { | ||||
|         if (!Array.isArray(options)) { | ||||
|           return []; | ||||
|         } | ||||
|         return options as any[]; | ||||
|       }, | ||||
|     }), | ||||
|     visualization: t.string({ | ||||
|       resolve: (parent) => (parent.extra as any)?.visualization, // used for guesstimate only, see searchGuesstimate.ts
 | ||||
|       nullable: true, | ||||
|     }), | ||||
|     history: t.relation("history", {}), | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
|  | @ -125,8 +150,7 @@ builder.queryField("questions", (t) => | |||
|       type: "Question", | ||||
|       cursor: "id", | ||||
|       maxSize: 1000, | ||||
|       resolve: (query, parent, args, context, info) => | ||||
|         prisma.question.findMany({ ...query }), | ||||
|       resolve: (query) => prisma.question.findMany({ ...query }), | ||||
|     }, | ||||
|     {}, | ||||
|     {} | ||||
|  |  | |||
|  | @ -146,14 +146,16 @@ export const DisplayQuestion: React.FC<Props> = ({ | |||
|             </div> | ||||
|           ) : null} | ||||
|           <div> | ||||
|             <Link href={`/questions/${question.id}`} passHref> | ||||
|               <a className="float-right block ml-2 mt-1.5"> | ||||
|                 <FaExpand | ||||
|                   size="18" | ||||
|                   className="text-gray-400 hover:text-gray-700" | ||||
|                 /> | ||||
|               </a> | ||||
|             </Link> | ||||
|             {process.env.NEXT_PUBLIC_ENABLE_QUESTION_PAGES ? ( | ||||
|               <Link href={`/questions/${question.id}`} passHref> | ||||
|                 <a className="float-right block ml-2 mt-1.5"> | ||||
|                   <FaExpand | ||||
|                     size="18" | ||||
|                     className="text-gray-400 hover:text-gray-700" | ||||
|                   /> | ||||
|                 </a> | ||||
|               </Link> | ||||
|             ) : null} | ||||
|             <Card.Title> | ||||
|               <a | ||||
|                 className="text-black no-underline" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user