feat: back to frontpage-in-db approach
This commit is contained in:
parent
80ee4c4055
commit
42c0f0967b
|
@ -17,7 +17,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/QURIresearch/metaforecasts#readme",
|
"homepage": "https://github.com/QURIresearch/metaforecasts#readme",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cli": "ts-node src/backend/index.js",
|
"cli": "ts-node src/backend/index.ts",
|
||||||
"reload": "heroku run:detached node src/backend/utils/doEverythingForScheduler.js",
|
"reload": "heroku run:detached node src/backend/utils/doEverythingForScheduler.js",
|
||||||
"setCookies": "./src/backend/utils/setCookies.sh",
|
"setCookies": "./src/backend/utils/setCookies.sh",
|
||||||
"next-dev": "next dev",
|
"next-dev": "next dev",
|
||||||
|
|
|
@ -302,11 +302,34 @@ export async function pgInitializeHistories() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pgInitializeFrontpage() {
|
||||||
|
let YOLO = false;
|
||||||
|
if (YOLO) {
|
||||||
|
await runPgCommand({
|
||||||
|
command: dropTable("latest", "frontpage"),
|
||||||
|
pool: readWritePool,
|
||||||
|
});
|
||||||
|
await runPgCommand({
|
||||||
|
command: `CREATE TABLE latest.frontpage (
|
||||||
|
id serial primary key,
|
||||||
|
frontpage_full jsonb,
|
||||||
|
frontpage_sliced jsonb
|
||||||
|
);`,
|
||||||
|
pool: readWritePool,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"pgInitializeFrontpage: This command is dangerous, set YOLO to true in the code to invoke it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function pgInitialize() {
|
export async function pgInitialize() {
|
||||||
await pgInitializeScaffolding();
|
await pgInitializeScaffolding();
|
||||||
await pgInitializeLatest();
|
await pgInitializeLatest();
|
||||||
await pgInitializeHistories();
|
await pgInitializeHistories();
|
||||||
await pgInitializeDashboards();
|
await pgInitializeDashboards();
|
||||||
|
await pgInitializeFrontpage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read
|
// Read
|
||||||
|
|
|
@ -2,23 +2,22 @@ import { pgRead, readWritePool } from './database/pg-wrapper';
|
||||||
|
|
||||||
export async function getFrontpageRaw() {
|
export async function getFrontpageRaw() {
|
||||||
const client = await readWritePool.connect();
|
const client = await readWritePool.connect();
|
||||||
const res = await client.query(`
|
const res = await client.query(
|
||||||
SELECT * FROM latest.combined
|
"SELECT frontpage_sliced FROM latest.frontpage ORDER BY id DESC LIMIT 1"
|
||||||
WHERE
|
);
|
||||||
(qualityindicators->>'stars')::int >= 3
|
if (!res.rows.length) return [];
|
||||||
AND description != ''
|
console.log(res.rows[0].frontpage_sliced);
|
||||||
AND JSON_ARRAY_LENGTH(options) > 0
|
return res.rows[0].frontpage_sliced;
|
||||||
ORDER BY RANDOM() LIMIT 50
|
|
||||||
`);
|
|
||||||
|
|
||||||
return res.rows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFrontpageFullRaw() {
|
export async function getFrontpageFullRaw() {
|
||||||
return await pgRead({
|
const client = await readWritePool.connect();
|
||||||
schema: "latest",
|
const res = await client.query(
|
||||||
tableName: "combined",
|
"SELECT frontpage_full FROM latest.frontpage ORDER BY id DESC LIMIT 1"
|
||||||
});
|
);
|
||||||
|
if (!res.rows.length) return [];
|
||||||
|
console.log(res.rows[0]);
|
||||||
|
return res.rows[0].frontpage_full;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFrontpage() {
|
export async function getFrontpage() {
|
||||||
|
@ -36,3 +35,34 @@ export async function getFrontpage() {
|
||||||
return frontPageForecastsCompatibleWithFuse;
|
return frontPageForecastsCompatibleWithFuse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function rebuildFrontpage() {
|
||||||
|
const frontpageFull = await pgRead({
|
||||||
|
schema: "latest",
|
||||||
|
tableName: "combined",
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = await readWritePool.connect();
|
||||||
|
const frontpageSliced = (
|
||||||
|
await client.query(`
|
||||||
|
SELECT * FROM latest.combined
|
||||||
|
WHERE
|
||||||
|
(qualityindicators->>'stars')::int >= 3
|
||||||
|
AND description != ''
|
||||||
|
AND JSON_ARRAY_LENGTH(options) > 0
|
||||||
|
ORDER BY RANDOM() LIMIT 50
|
||||||
|
`)
|
||||||
|
).rows;
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
await client.query(
|
||||||
|
"INSERT INTO latest.frontpage(frontpage_full, frontpage_sliced) VALUES($1, $2)",
|
||||||
|
[JSON.stringify(frontpageFull), JSON.stringify(frontpageSliced)]
|
||||||
|
);
|
||||||
|
|
||||||
|
const end = Date.now();
|
||||||
|
const difference = end - start;
|
||||||
|
console.log(
|
||||||
|
`Took ${difference / 1000} seconds, or ${difference / (1000 * 60)} minutes.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import "dotenv/config";
|
import 'dotenv/config';
|
||||||
import readline from "readline";
|
|
||||||
import { pgInitialize } from "./database/pg-wrapper.js";
|
import readline from 'readline';
|
||||||
import { doEverything, tryCatchTryAgain } from "./flow/doEverything.js";
|
|
||||||
import { updateHistory } from "./flow/history/updateHistory.js";
|
import { pgInitialize } from './database/pg-wrapper.js';
|
||||||
import { mergeEverything } from "./flow/mergeEverything.js";
|
import { doEverything, tryCatchTryAgain } from './flow/doEverything.js';
|
||||||
import { rebuildNetlifySiteWithNewData } from "./flow/rebuildNetliftySiteWithNewData.js";
|
import { updateHistory } from './flow/history/updateHistory.js';
|
||||||
import { platformFetchers } from "./platforms/all-platforms.js";
|
import { mergeEverything } from './flow/mergeEverything.js';
|
||||||
import { rebuildAlgoliaDatabase } from "./utils/algolia.js";
|
import { rebuildNetlifySiteWithNewData } from './flow/rebuildNetliftySiteWithNewData.js';
|
||||||
|
import { rebuildFrontpage } from './frontpage';
|
||||||
|
import { platformFetchers } from './platforms/all-platforms.js';
|
||||||
|
import { rebuildAlgoliaDatabase } from './utils/algolia.js';
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
let functions = [
|
let functions = [
|
||||||
|
@ -18,6 +21,7 @@ let functions = [
|
||||||
rebuildNetlifySiteWithNewData,
|
rebuildNetlifySiteWithNewData,
|
||||||
doEverything,
|
doEverything,
|
||||||
pgInitialize,
|
pgInitialize,
|
||||||
|
rebuildFrontpage,
|
||||||
];
|
];
|
||||||
let functionNames = functions.map((fun) => fun.name);
|
let functionNames = functions.map((fun) => fun.name);
|
||||||
|
|
||||||
|
@ -34,6 +38,7 @@ let generateWhatToDoMessage = () => {
|
||||||
// `\n[${functionNames.length-1}]: Add to history` +
|
// `\n[${functionNames.length-1}]: Add to history` +
|
||||||
`All of the above`,
|
`All of the above`,
|
||||||
`Initialize postgres database`,
|
`Initialize postgres database`,
|
||||||
|
"Rebuild frontpage",
|
||||||
];
|
];
|
||||||
let otherMessagesWithNums = otherMessages.map(
|
let otherMessagesWithNums = otherMessages.map(
|
||||||
(message, i) => `[${i + l}]: ${message}`
|
(message, i) => `[${i + l}]: ${message}`
|
|
@ -1,41 +1,24 @@
|
||||||
import { GetStaticProps, NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getFrontpage } from '../backend/frontpage';
|
|
||||||
import CommonDisplay from '../web/display/commonDisplay';
|
|
||||||
import { displayForecastsWrapperForCapture } from '../web/display/displayForecastsWrappers';
|
import { displayForecastsWrapperForCapture } from '../web/display/displayForecastsWrappers';
|
||||||
|
import { Props } from '../web/search/anySearchPage';
|
||||||
|
import CommonDisplay from '../web/search/commonDisplay';
|
||||||
import Layout from './layout';
|
import Layout from './layout';
|
||||||
|
|
||||||
/* get Props */
|
export { getServerSideProps } from "../web/search/anySearchPage";
|
||||||
|
|
||||||
interface Props {
|
const CapturePage: NextPage<Props> = ({
|
||||||
defaultResults: any;
|
defaultResults,
|
||||||
}
|
initialResults,
|
||||||
|
initialQueryParameters,
|
||||||
export const getStaticProps: GetStaticProps<Props> = async (context) => {
|
}) => {
|
||||||
let frontPageForecasts = await getFrontpage();
|
|
||||||
frontPageForecasts = frontPageForecasts.map((forecast) => ({
|
|
||||||
...forecast,
|
|
||||||
item: {
|
|
||||||
...forecast.item,
|
|
||||||
timestamp: forecast.item.timestamp.toJSON(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
defaultResults: frontPageForecasts,
|
|
||||||
},
|
|
||||||
revalidate: 3600 * 6,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
const CapturePage: NextPage<Props> = ({ defaultResults }) => {
|
|
||||||
return (
|
return (
|
||||||
<Layout page={"capture"}>
|
<Layout page={"capture"}>
|
||||||
<CommonDisplay
|
<CommonDisplay
|
||||||
defaultResults={defaultResults}
|
defaultResults={defaultResults}
|
||||||
|
initialResults={initialResults}
|
||||||
|
initialQueryParameters={initialQueryParameters}
|
||||||
hasSearchbar={true}
|
hasSearchbar={true}
|
||||||
hasCapture={true}
|
hasCapture={true}
|
||||||
hasAdvancedOptions={false}
|
hasAdvancedOptions={false}
|
||||||
|
|
|
@ -1,41 +1,24 @@
|
||||||
import { GetStaticProps, NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getFrontpage } from '../backend/frontpage';
|
|
||||||
import CommonDisplay from '../web/display/commonDisplay';
|
|
||||||
import { displayForecastsWrapperForSearch } from '../web/display/displayForecastsWrappers';
|
import { displayForecastsWrapperForSearch } from '../web/display/displayForecastsWrappers';
|
||||||
|
import { Props } from '../web/search/anySearchPage';
|
||||||
|
import CommonDisplay from '../web/search/commonDisplay';
|
||||||
import Layout from './layout';
|
import Layout from './layout';
|
||||||
|
|
||||||
/* get Props */
|
export { getServerSideProps } from "../web/search/anySearchPage";
|
||||||
|
|
||||||
interface Props {
|
const IndexPage: NextPage<Props> = ({
|
||||||
defaultResults: any;
|
defaultResults,
|
||||||
}
|
initialResults,
|
||||||
|
initialQueryParameters,
|
||||||
export const getStaticProps: GetStaticProps<Props> = async (context) => {
|
}) => {
|
||||||
let frontPageForecasts = await getFrontpage();
|
|
||||||
frontPageForecasts = frontPageForecasts.map((forecast) => ({
|
|
||||||
...forecast,
|
|
||||||
item: {
|
|
||||||
...forecast.item,
|
|
||||||
timestamp: forecast.item.timestamp.toJSON(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
defaultResults: frontPageForecasts,
|
|
||||||
},
|
|
||||||
revalidate: 3600 * 6,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
const IndexPage: NextPage<Props> = ({ defaultResults }) => {
|
|
||||||
return (
|
return (
|
||||||
<Layout page={"search"}>
|
<Layout page={"search"}>
|
||||||
<CommonDisplay
|
<CommonDisplay
|
||||||
defaultResults={defaultResults}
|
defaultResults={defaultResults}
|
||||||
|
initialResults={initialResults}
|
||||||
|
initialQueryParameters={initialQueryParameters}
|
||||||
hasSearchbar={true}
|
hasSearchbar={true}
|
||||||
hasCapture={false}
|
hasCapture={false}
|
||||||
hasAdvancedOptions={true}
|
hasAdvancedOptions={true}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, { Fragment, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import ButtonsForStars from '../display/buttonsForStars';
|
||||||
|
import Form from '../display/form';
|
||||||
|
import MultiSelectPlatform from '../display/multiSelectPlatforms';
|
||||||
|
import { SliderElement } from '../display/slider';
|
||||||
import { platformsWithLabels, PlatformWithLabel } from '../platforms';
|
import { platformsWithLabels, PlatformWithLabel } from '../platforms';
|
||||||
import searchAccordingToQueryData from '../worker/searchAccordingToQueryData';
|
import searchAccordingToQueryData from '../worker/searchAccordingToQueryData';
|
||||||
import ButtonsForStars from './buttonsForStars';
|
|
||||||
import Form from './form';
|
|
||||||
import MultiSelectPlatform from './multiSelectPlatforms';
|
|
||||||
import { SliderElement } from './slider';
|
|
||||||
|
|
||||||
interface QueryParametersWithoutNum {
|
interface QueryParametersWithoutNum {
|
||||||
query: string;
|
query: string;
|
||||||
|
@ -21,6 +21,8 @@ export interface QueryParameters extends QueryParametersWithoutNum {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
defaultResults: any;
|
defaultResults: any;
|
||||||
|
initialResults: any;
|
||||||
|
initialQueryParameters: QueryParameters;
|
||||||
hasSearchbar: boolean;
|
hasSearchbar: boolean;
|
||||||
hasCapture: boolean;
|
hasCapture: boolean;
|
||||||
hasAdvancedOptions: boolean;
|
hasAdvancedOptions: boolean;
|
||||||
|
@ -34,17 +36,19 @@ interface Props {
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultQueryParameters: QueryParametersWithoutNum = {
|
export const defaultQueryParameters: QueryParametersWithoutNum = {
|
||||||
query: "",
|
query: "",
|
||||||
starsThreshold: 2,
|
starsThreshold: 2,
|
||||||
forecastsThreshold: 0,
|
forecastsThreshold: 0,
|
||||||
forecastingPlatforms: platformsWithLabels, // weird key value format,
|
forecastingPlatforms: platformsWithLabels, // weird key value format,
|
||||||
};
|
};
|
||||||
const defaultNumDisplay = 21;
|
export const defaultNumDisplay = 21;
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
const CommonDisplay: React.FC<Props> = ({
|
const CommonDisplay: React.FC<Props> = ({
|
||||||
defaultResults,
|
defaultResults,
|
||||||
|
initialResults,
|
||||||
|
initialQueryParameters,
|
||||||
hasSearchbar,
|
hasSearchbar,
|
||||||
hasCapture,
|
hasCapture,
|
||||||
hasAdvancedOptions,
|
hasAdvancedOptions,
|
||||||
|
@ -56,36 +60,21 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
/* States */
|
/* States */
|
||||||
|
|
||||||
const [queryParameters, setQueryParameters] =
|
const [queryParameters, setQueryParameters] =
|
||||||
useState<QueryParametersWithoutNum>(defaultQueryParameters);
|
useState<QueryParametersWithoutNum>(initialQueryParameters);
|
||||||
|
|
||||||
const [numDisplay, setNumDisplay] = useState(0);
|
const [numDisplay, setNumDisplay] = useState(
|
||||||
|
initialQueryParameters.numDisplay ?? defaultNumDisplay
|
||||||
const [ready, setReady] = useState(false);
|
);
|
||||||
|
|
||||||
// used to distinguish numDisplay updates which force search and don't force search, see effects below
|
// used to distinguish numDisplay updates which force search and don't force search, see effects below
|
||||||
const [forceSearch, setForceSearch] = useState(0);
|
const [forceSearch, setForceSearch] = useState(0);
|
||||||
|
|
||||||
const [results, setResults] = useState([]);
|
const [results, setResults] = useState(initialResults);
|
||||||
const [advancedOptions, showAdvancedOptions] = useState(false);
|
const [advancedOptions, showAdvancedOptions] = useState(false);
|
||||||
const [whichResultToDisplayAndCapture, setWhichResultToDisplayAndCapture] =
|
const [whichResultToDisplayAndCapture, setWhichResultToDisplayAndCapture] =
|
||||||
useState(0);
|
useState(0);
|
||||||
const [showIdToggle, setShowIdToggle] = useState(false);
|
const [showIdToggle, setShowIdToggle] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!router.isReady) return;
|
|
||||||
|
|
||||||
setQueryParameters({
|
|
||||||
...defaultQueryParameters,
|
|
||||||
...router.query,
|
|
||||||
});
|
|
||||||
setNumDisplay(
|
|
||||||
typeof router.query.numDisplay === "string"
|
|
||||||
? parseInt(router.query.numDisplay)
|
|
||||||
: defaultNumDisplay
|
|
||||||
);
|
|
||||||
setReady(true);
|
|
||||||
}, [router.isReady]);
|
|
||||||
|
|
||||||
/* Functions which I want to have access to the Home namespace */
|
/* Functions which I want to have access to the Home namespace */
|
||||||
// I don't want to create an "defaultResults" object for each search.
|
// I don't want to create an "defaultResults" object for each search.
|
||||||
async function executeSearchOrAnswerWithDefaultResults() {
|
async function executeSearchOrAnswerWithDefaultResults() {
|
||||||
|
@ -173,7 +162,6 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
useEffect(updateRoute, [numDisplay]);
|
useEffect(updateRoute, [numDisplay]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ready) return;
|
|
||||||
setResults([]);
|
setResults([]);
|
||||||
let newTimeoutId = setTimeout(() => {
|
let newTimeoutId = setTimeout(() => {
|
||||||
updateRoute();
|
updateRoute();
|
||||||
|
@ -184,7 +172,7 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(newTimeoutId);
|
clearTimeout(newTimeoutId);
|
||||||
};
|
};
|
||||||
}, [ready, queryParameters, forceSearch]);
|
}, [queryParameters, forceSearch]);
|
||||||
|
|
||||||
/* State controllers */
|
/* State controllers */
|
||||||
|
|
42
src/web/search/anySearchPage.tsx
Normal file
42
src/web/search/anySearchPage.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { GetServerSideProps } from 'next';
|
||||||
|
|
||||||
|
import { getFrontpage } from '../../backend/frontpage';
|
||||||
|
import searchAccordingToQueryData from '../worker/searchAccordingToQueryData';
|
||||||
|
import { defaultNumDisplay, defaultQueryParameters, QueryParameters } from './commonDisplay';
|
||||||
|
|
||||||
|
/* Common code for / and /capture */
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
defaultResults: any;
|
||||||
|
initialResults: any;
|
||||||
|
initialQueryParameters: QueryParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
let urlQuery = context.query;
|
||||||
|
|
||||||
|
let initialQueryParameters: QueryParameters = {
|
||||||
|
...defaultQueryParameters,
|
||||||
|
numDisplay: defaultNumDisplay,
|
||||||
|
...urlQuery, // FIXME - parse numerical fields
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultResults = await getFrontpage();
|
||||||
|
|
||||||
|
const initialResults =
|
||||||
|
!!initialQueryParameters &&
|
||||||
|
initialQueryParameters.query != "" &&
|
||||||
|
initialQueryParameters.query != undefined
|
||||||
|
? await searchAccordingToQueryData(initialQueryParameters)
|
||||||
|
: defaultResults;
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
initialQueryParameters,
|
||||||
|
initialResults,
|
||||||
|
defaultResults,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user