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 {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["interactiveTransactions"]
|
||||||
}
|
}
|
||||||
|
|
||||||
generator pothos {
|
generator pothos {
|
||||||
|
@ -25,15 +26,17 @@ model Dashboard {
|
||||||
|
|
||||||
model History {
|
model History {
|
||||||
id String
|
id String
|
||||||
|
idref String?
|
||||||
|
question Question? @relation(fields: [idref], references: [id], onDelete: SetNull, onUpdate: Restrict)
|
||||||
title String
|
title String
|
||||||
url String
|
url String
|
||||||
platform String
|
platform String
|
||||||
description String
|
description String
|
||||||
options Json
|
options Json
|
||||||
timestamp DateTime @db.Timestamp(6)
|
timestamp DateTime @db.Timestamp(6)
|
||||||
qualityindicators Json
|
qualityindicators Json
|
||||||
extra Json
|
extra Json
|
||||||
pk Int @id @default(autoincrement())
|
pk Int @id @default(autoincrement())
|
||||||
|
|
||||||
@@index([id])
|
@@index([id])
|
||||||
@@map("history")
|
@@map("history")
|
||||||
|
@ -75,6 +78,8 @@ model Question {
|
||||||
extra Json
|
extra Json
|
||||||
|
|
||||||
onFrontpage FrontpageId?
|
onFrontpage FrontpageId?
|
||||||
|
history History[]
|
||||||
|
|
||||||
@@map("questions")
|
@@map("questions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { prisma } from "../../database/prisma";
|
||||||
export async function updateHistory() {
|
export async function updateHistory() {
|
||||||
const questions = await prisma.question.findMany({});
|
const questions = await prisma.question.findMany({});
|
||||||
await prisma.history.createMany({
|
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`);
|
console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const results = await platform.fetcher();
|
const fetchedQuestions = await platform.fetcher();
|
||||||
if (results && results.length) {
|
if (!fetchedQuestions || !fetchedQuestions.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 {
|
|
||||||
console.log(`Platform ${platform.name} didn't return any results`);
|
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 {
|
export interface PlatformConfig {
|
||||||
|
|
|
@ -15,11 +15,15 @@ export const xrisk: Platform = {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
let results = JSON.parse(fileRaw);
|
let results = JSON.parse(fileRaw);
|
||||||
results = results.map((item) => ({
|
results = results.map((item) => {
|
||||||
...item,
|
item.extra = item.moreoriginsdata;
|
||||||
id: `${platformName}-${hash(item.title + " | " + item.url)}`, // some titles are non-unique, but title+url pair is always unique
|
delete item.moreoriginsdata;
|
||||||
platform: platformName,
|
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;
|
return results;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { History, Question } from "@prisma/client";
|
||||||
|
|
||||||
import { prisma } from "../../backend/database/prisma";
|
import { prisma } from "../../backend/database/prisma";
|
||||||
import { platforms, QualityIndicators } from "../../backend/platforms";
|
import { platforms, QualityIndicators } from "../../backend/platforms";
|
||||||
import { builder } from "../builder";
|
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", {
|
export const QuestionObj = builder.prismaObject("Question", {
|
||||||
findUnique: (question) => ({ id: question.id }),
|
findUnique: (question) => ({ id: question.id }),
|
||||||
|
interfaces: [QuestionShapeInterface],
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
id: t.exposeID("id", {
|
id: t.exposeID("id", {
|
||||||
description: "Unique string which identifies the question",
|
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({
|
visualization: t.string({
|
||||||
resolve: (parent) => (parent.extra as any)?.visualization, // used for guesstimate only, see searchGuesstimate.ts
|
resolve: (parent) => (parent.extra as any)?.visualization, // used for guesstimate only, see searchGuesstimate.ts
|
||||||
nullable: true,
|
nullable: true,
|
||||||
}),
|
}),
|
||||||
|
history: t.relation("history", {}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,8 +150,7 @@ builder.queryField("questions", (t) =>
|
||||||
type: "Question",
|
type: "Question",
|
||||||
cursor: "id",
|
cursor: "id",
|
||||||
maxSize: 1000,
|
maxSize: 1000,
|
||||||
resolve: (query, parent, args, context, info) =>
|
resolve: (query) => prisma.question.findMany({ ...query }),
|
||||||
prisma.question.findMany({ ...query }),
|
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -146,14 +146,16 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div>
|
||||||
<Link href={`/questions/${question.id}`} passHref>
|
{process.env.NEXT_PUBLIC_ENABLE_QUESTION_PAGES ? (
|
||||||
<a className="float-right block ml-2 mt-1.5">
|
<Link href={`/questions/${question.id}`} passHref>
|
||||||
<FaExpand
|
<a className="float-right block ml-2 mt-1.5">
|
||||||
size="18"
|
<FaExpand
|
||||||
className="text-gray-400 hover:text-gray-700"
|
size="18"
|
||||||
/>
|
className="text-gray-400 hover:text-gray-700"
|
||||||
</a>
|
/>
|
||||||
</Link>
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<a
|
<a
|
||||||
className="text-black no-underline"
|
className="text-black no-underline"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user