mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2024-11-28 09:33:51 +02:00
Upgrade packages, refactoring db access
This commit is contained in:
parent
dc4fe41f38
commit
3f1bc9924a
1
.gitignore
vendored
1
.gitignore
vendored
@ -41,3 +41,4 @@ Thumbs.db
|
|||||||
# custom
|
# custom
|
||||||
/.cache
|
/.cache
|
||||||
/credentials
|
/credentials
|
||||||
|
.env.local
|
@ -7,10 +7,14 @@ Make sure `prod.package.json` is up to date on packages from the root `package.j
|
|||||||
`docker build -t eu.gcr.io/factorio-sites/blueprints --file blueprints.Dockerfile .`
|
`docker build -t eu.gcr.io/factorio-sites/blueprints --file blueprints.Dockerfile .`
|
||||||
`docker push eu.gcr.io/factorio-sites/blueprints`
|
`docker push eu.gcr.io/factorio-sites/blueprints`
|
||||||
|
|
||||||
### Testing deployment locally
|
## Testing deployment locally
|
||||||
|
|
||||||
`docker run --rm -p 3000:3000 eu.gcr.io/factorio-sites/blueprints`
|
`docker run --rm -p 3000:3000 eu.gcr.io/factorio-sites/blueprints`
|
||||||
|
|
||||||
### windows env
|
### windows env
|
||||||
|
|
||||||
`$env:GOOGLE_APPLICATION_CREDENTIALS="D:\git\factorio-sites\credentials\factorio-sites.json"`
|
create a `.env.local` with
|
||||||
|
|
||||||
|
```
|
||||||
|
GOOGLE_APPLICATION_CREDENTIALS="D:\git\factorio-sites\credentials\factorio-sites.json"
|
||||||
|
```
|
||||||
|
@ -1 +1,4 @@
|
|||||||
module.exports = {};
|
module.exports = {
|
||||||
|
poweredByHeader: false,
|
||||||
|
reactStrictMode: true,
|
||||||
|
};
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
import React, { ReactNode, useEffect, useState } from "react";
|
import React, { ReactNode, useEffect, useState } from "react";
|
||||||
import { jsx, css } from "@emotion/core";
|
import { jsx, css } from "@emotion/react";
|
||||||
import { NextPage, NextPageContext } from "next";
|
import { NextPage, NextPageContext } from "next";
|
||||||
import BBCode from "bbcode-to-react";
|
import BBCode from "bbcode-to-react";
|
||||||
import { Button, Grid, Image } from "@chakra-ui/core";
|
import { Button, Grid, Image } from "@chakra-ui/react";
|
||||||
import {
|
import {
|
||||||
BlueprintBookEntry,
|
BlueprintBookEntry,
|
||||||
BlueprintEntry,
|
BlueprintEntry,
|
||||||
@ -67,7 +67,8 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setData(null);
|
setData(null);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch((reason) => console.error(reason));
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedHash]);
|
}, [selectedHash]);
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variantColor="green"
|
colorScheme="green"
|
||||||
css={{ position: "absolute", right: "65px" }}
|
css={{ position: "absolute", right: "65px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowJson(false);
|
setShowJson(false);
|
||||||
@ -231,7 +232,7 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
variantColor="green"
|
colorScheme="green"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowJson(true);
|
setShowJson(true);
|
||||||
if (selected.type === "blueprint_book") {
|
if (selected.type === "blueprint_book") {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
import { jsx, css, Global } from "@emotion/core";
|
import { AppProps, NextWebVitalsMetric } from "next/app";
|
||||||
import { AppProps } from "next/app";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Router from "next/router";
|
import Router from "next/router";
|
||||||
import { CSSReset, ITheme, theme } from "@chakra-ui/core";
|
import { jsx, css, Global } from "@emotion/react";
|
||||||
import { ThemeProvider } from "emotion-theming";
|
import { ChakraProvider } from "@chakra-ui/react";
|
||||||
import NProgress from "nprogress";
|
import NProgress from "nprogress";
|
||||||
import { Header } from "../src/Header";
|
import { Header } from "../src/Header";
|
||||||
|
|
||||||
@ -39,26 +38,10 @@ if (typeof window !== "undefined") {
|
|||||||
Router.events.on("routeChangeError", () => NProgress.done());
|
Router.events.on("routeChangeError", () => NProgress.done());
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = (theme: ITheme) => ({
|
const BlueprintsApp = ({ Component, pageProps }: AppProps) => {
|
||||||
light: {
|
|
||||||
color: theme.colors.gray[800],
|
|
||||||
bg: theme.colors.gray[300],
|
|
||||||
borderColor: theme.colors.gray[200],
|
|
||||||
placeholderColor: theme.colors.gray[500],
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
color: theme.colors.whiteAlpha[900],
|
|
||||||
bg: theme.colors.gray[800],
|
|
||||||
borderColor: theme.colors.whiteAlpha[300],
|
|
||||||
placeholderColor: theme.colors.whiteAlpha[400],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const CustomApp = ({ Component, pageProps }: AppProps) => {
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ChakraProvider>
|
||||||
<Global styles={globalStyles} />
|
<Global styles={globalStyles} />
|
||||||
<CSSReset config={config} />
|
|
||||||
<Head>
|
<Head>
|
||||||
<title>Welcome to blueprints!</title>
|
<title>Welcome to blueprints!</title>
|
||||||
<link
|
<link
|
||||||
@ -72,8 +55,12 @@ const CustomApp = ({ Component, pageProps }: AppProps) => {
|
|||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ChakraProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomApp;
|
export function reportWebVitals(metric: NextWebVitalsMetric) {
|
||||||
|
console.log(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlueprintsApp;
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { NextPage, NextPageContext } from "next";
|
import { NextPage, NextPageContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { jsx, css } from "@emotion/core";
|
import { jsx, css } from "@emotion/react";
|
||||||
import { BlueprintPageEntry, getMostRecentBlueprintPages } from "@factorio-sites/database";
|
import { BlueprintPageEntry, getMostRecentBlueprintPages } from "@factorio-sites/database";
|
||||||
import { Panel } from "../src/Panel";
|
import { Panel } from "../src/Panel";
|
||||||
import { SimpleGrid } from "@chakra-ui/core";
|
import { SimpleGrid } from "@chakra-ui/react";
|
||||||
import { Pagination } from "../src/Pagination";
|
import { Pagination } from "../src/Pagination";
|
||||||
|
|
||||||
const linkStyles = css`
|
const linkStyles = css`
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/core": "0.8.0",
|
"@chakra-ui/react": "1.0.0",
|
||||||
"@emotion/core": "10.0.35",
|
"@emotion/react": "11.1.3",
|
||||||
"@emotion/styled": "10.0.27",
|
"@emotion/styled": "10.0.27",
|
||||||
"@google-cloud/datastore": "6.2.0",
|
"@google-cloud/datastore": "6.2.0",
|
||||||
"@google-cloud/pubsub": "2.6.0",
|
"@google-cloud/pubsub": "2.6.0",
|
||||||
"@google-cloud/storage": "5.3.0",
|
"@google-cloud/storage": "5.3.0",
|
||||||
"bbcode-to-react": "0.2.9",
|
"bbcode-to-react": "0.2.9",
|
||||||
"document-register-element": "1.14.10",
|
"document-register-element": "1.14.10",
|
||||||
"emotion-server": "10.0.27",
|
|
||||||
"emotion-theming": "10.0.27",
|
|
||||||
"next": "9.5.5",
|
"next": "9.5.5",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
import { jsx, css } from "@emotion/core";
|
import { jsx, css } from "@emotion/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import BBCode from "bbcode-to-react";
|
import BBCode from "bbcode-to-react";
|
||||||
import { BlueprintBookEntry } from "@factorio-sites/database";
|
import { BlueprintBookEntry } from "@factorio-sites/database";
|
||||||
|
@ -1,31 +1,49 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { Button, ButtonProps } from "@chakra-ui/core";
|
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||||
|
import { MdCheck, MdClose } from "react-icons/md";
|
||||||
|
|
||||||
|
const SUCCESS_ICON_DURATION = 2000;
|
||||||
|
|
||||||
export const CopyButton: React.FC<Omit<ButtonProps, "children"> & { content: string }> = ({
|
export const CopyButton: React.FC<Omit<ButtonProps, "children"> & { content: string }> = ({
|
||||||
content,
|
content,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [icon, setIcon] = useState<"check" | "small-close" | null>(null);
|
const [icon, setIcon] = useState<"red" | "green" | null>(null);
|
||||||
|
|
||||||
|
const iconProps = useMemo(() => {
|
||||||
|
if (icon === "green") {
|
||||||
|
return {
|
||||||
|
colorScheme: "green",
|
||||||
|
leftIcon: <MdCheck />,
|
||||||
|
};
|
||||||
|
} else if (icon === "red") {
|
||||||
|
return {
|
||||||
|
colorScheme: "red",
|
||||||
|
leftIcon: <MdClose />,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { colorScheme: "green" };
|
||||||
|
}
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
leftIcon={icon ?? undefined}
|
{...iconProps}
|
||||||
variantColor={icon === "small-close" ? "red" : "green"}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(content)
|
.writeText(content)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIcon("check");
|
setIcon("green");
|
||||||
setTimeout(() => setIcon(null), 2500);
|
setTimeout(() => setIcon(null), SUCCESS_ICON_DURATION);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIcon("small-close");
|
setIcon("red");
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { jsx, css } from "@emotion/core";
|
import { jsx, css } from "@emotion/react";
|
||||||
import { MapInteractionCSS } from "react-map-interaction";
|
import { MapInteractionCSS } from "react-map-interaction";
|
||||||
|
|
||||||
const elementStyle = css`
|
const elementStyle = css`
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Box, Heading, Flex } from "@chakra-ui/core";
|
import { Box, Heading, Flex } from "@chakra-ui/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
// const MenuItems = ({ children }) => (
|
// const MenuItems = ({ children }) => (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
import { jsx, css } from "@emotion/core";
|
import { jsx, css } from "@emotion/react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
const markdownStyle = css`
|
const markdownStyle = css`
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
import { jsx } from "@emotion/core";
|
import { jsx } from "@emotion/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { Box, BoxProps, Button } from "@chakra-ui/core";
|
import { Box, BoxProps, Button } from "@chakra-ui/react";
|
||||||
|
|
||||||
interface PaginationProps {
|
interface PaginationProps {
|
||||||
page: number;
|
page: number;
|
||||||
@ -13,7 +13,7 @@ const PaginationLink: React.FC<PaginationProps> = ({ page }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const query: Record<string, string> = { ...router.query, page: page.toString() };
|
const query: Record<string, string> = { ...router.query, page: page.toString() };
|
||||||
const href =
|
const href =
|
||||||
"?" +
|
"/?" +
|
||||||
Object.keys(query)
|
Object.keys(query)
|
||||||
.map((key) => `${key}=${query[key]}`)
|
.map((key) => `${key}=${query[key]}`)
|
||||||
.join("&");
|
.join("&");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
import { jsx, css, SerializedStyles } from "@emotion/core";
|
import { jsx, css, SerializedStyles } from "@emotion/react";
|
||||||
import { Box, BoxProps } from "@chakra-ui/core";
|
import { Box, BoxProps } from "@chakra-ui/react";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
const panelStyles = css`
|
const panelStyles = css`
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
export * from "./lib/database";
|
export * from "./lib/gcp-datastore";
|
||||||
export * from "./lib/pubsub";
|
export * from "./lib/gcp-pubsub";
|
||||||
|
export * from "./lib/gcp-storage";
|
||||||
|
export * from "./lib/types";
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { database } from "./database";
|
|
||||||
|
|
||||||
describe("database", () => {
|
|
||||||
it("should work", () => {
|
|
||||||
expect(database()).toEqual("database");
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +1,3 @@
|
|||||||
import { Storage } from "@google-cloud/storage";
|
|
||||||
import { Datastore } from "@google-cloud/datastore";
|
import { Datastore } from "@google-cloud/datastore";
|
||||||
import { encodeBlueprint, hashString, parseBlueprintString } from "@factorio-sites/node-utils";
|
import { encodeBlueprint, hashString, parseBlueprintString } from "@factorio-sites/node-utils";
|
||||||
import {
|
import {
|
||||||
@ -6,89 +5,17 @@ import {
|
|||||||
BlueprintBook,
|
BlueprintBook,
|
||||||
getBlueprintContentForImageHash,
|
getBlueprintContentForImageHash,
|
||||||
} from "@factorio-sites/common-utils";
|
} from "@factorio-sites/common-utils";
|
||||||
import { getBlueprintImageRequestTopic } from "./pubsub";
|
import { getBlueprintImageRequestTopic } from "./gcp-pubsub";
|
||||||
|
import { saveBlueprintString } from "./gcp-storage";
|
||||||
|
import { BlueprintBookEntry, BlueprintEntry, BlueprintPageEntry } from "./types";
|
||||||
|
|
||||||
// to dev on windows run: $env:GOOGLE_APPLICATION_CREDENTIALS="FULL_PATH"
|
|
||||||
const storage = new Storage();
|
|
||||||
const datastore = new Datastore();
|
const datastore = new Datastore();
|
||||||
|
|
||||||
const BLUEPRINT_BUCKET = storage.bucket("blueprint-strings");
|
|
||||||
const IMAGE_BUCKET = storage.bucket("blueprint-images");
|
|
||||||
const BlueprintEntity = "Blueprint";
|
const BlueprintEntity = "Blueprint";
|
||||||
const BlueprintBookEntity = "BlueprintBook";
|
const BlueprintBookEntity = "BlueprintBook";
|
||||||
const BlueprintPageEntity = "BlueprintPage";
|
const BlueprintPageEntity = "BlueprintPage";
|
||||||
const BlueprintStringEntity = "BlueprintString";
|
const BlueprintStringEntity = "BlueprintString";
|
||||||
const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||||
|
|
||||||
interface BlueprintChild {
|
|
||||||
type: "blueprint";
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
interface BlueprintBookChild {
|
|
||||||
type: "blueprint_book";
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
children: ChildTree;
|
|
||||||
}
|
|
||||||
type ChildTree = Array<BlueprintChild | BlueprintBookChild>;
|
|
||||||
|
|
||||||
export interface BlueprintEntry {
|
|
||||||
id: string;
|
|
||||||
label: string; // from source
|
|
||||||
description: string | null; // from source
|
|
||||||
game_version: number; // from source
|
|
||||||
blueprint_hash: string;
|
|
||||||
image_hash: string;
|
|
||||||
created_at: number;
|
|
||||||
updated_at: number;
|
|
||||||
tags: string[];
|
|
||||||
factorioprints_id?: string;
|
|
||||||
// BlueprintEntry->BlueprintString 1:m
|
|
||||||
// BlueprintEntry->BlueprintPageEntry n:m
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlueprintBookEntry {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
description?: string;
|
|
||||||
/** strings as keys of BlueprintEntry */
|
|
||||||
blueprint_ids: string[];
|
|
||||||
/** strings as keys of BlueprintBookEntry (currently unsupported) */
|
|
||||||
blueprint_book_ids: string[];
|
|
||||||
child_tree: ChildTree;
|
|
||||||
blueprint_hash: string;
|
|
||||||
created_at: number;
|
|
||||||
updated_at: number;
|
|
||||||
is_modded: boolean;
|
|
||||||
factorioprints_id?: string;
|
|
||||||
// BlueprintBook:BlueprintBook n:m
|
|
||||||
// BlueprintBook:BlueprintEntry 1:m
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlueprintPageEntry {
|
|
||||||
id: string;
|
|
||||||
blueprint_id?: string;
|
|
||||||
blueprint_book_id?: string;
|
|
||||||
title: string;
|
|
||||||
description_markdown: string;
|
|
||||||
created_at: number;
|
|
||||||
updated_at: number;
|
|
||||||
factorioprints_id?: string;
|
|
||||||
// BlueprintPageEntry->BlueprintEntry 1:m
|
|
||||||
// BlueprintPageEntry->BlueprintBook 1:m
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlueprintStringEntry {
|
|
||||||
blueprint_id: string;
|
|
||||||
blueprint_hash: string;
|
|
||||||
image_hash: string;
|
|
||||||
version: number;
|
|
||||||
changes_markdown: string;
|
|
||||||
created_at: Date;
|
|
||||||
// BlueprintString->BlueprintEntry m:1
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatastoreExistsError extends Error {
|
class DatastoreExistsError extends Error {
|
||||||
constructor(public existingId: string, message: string) {
|
constructor(public existingId: string, message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
@ -129,35 +56,32 @@ export async function getBlueprintBookByHash(hash: string): Promise<BlueprintBoo
|
|||||||
return result[0] ? mapBlueprintBookEntityToObject(result[0]) : null;
|
return result[0] ? mapBlueprintBookEntityToObject(result[0]) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createBlueprintBook(
|
export async function createBlueprintBook(
|
||||||
blueprintBook: BlueprintBook,
|
blueprintBook: BlueprintBook,
|
||||||
extraInfo: { tags: string[]; created_at: number; updated_at: number; factorioprints_id: string }
|
extraInfo: { tags: string[]; created_at: number; updated_at: number; factorioprints_id: string }
|
||||||
): Promise<{ insertedId: string; child_tree: ChildTree }> {
|
): Promise<{ insertedId: string; child_tree: BlueprintBookEntry["child_tree"] }> {
|
||||||
const string = await encodeBlueprint({ blueprint_book: blueprintBook });
|
const string = await encodeBlueprint({ blueprint_book: blueprintBook });
|
||||||
const blueprint_hash = hashString(string);
|
const blueprint_hash = hashString(string);
|
||||||
|
|
||||||
const exists = await getBlueprintBookByHash(blueprint_hash);
|
const exists = await getBlueprintBookByHash(blueprint_hash);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
throw new DatastoreExistsError(exists.id, "Blueprint book already exists");
|
const book = await getBlueprintBookById(exists.id);
|
||||||
|
if (!book) throw Error("this is impossible, just pleasing typescript");
|
||||||
|
return { insertedId: exists.id, child_tree: book.child_tree };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write string to google storage
|
// Write string to google storage
|
||||||
await BLUEPRINT_BUCKET.file(blueprint_hash).save(string);
|
await saveBlueprintString(blueprint_hash, string);
|
||||||
|
|
||||||
const blueprint_ids = [];
|
const blueprint_ids = [];
|
||||||
const blueprint_book_ids = [];
|
const blueprint_book_ids = [];
|
||||||
const child_tree: ChildTree = [];
|
const child_tree: BlueprintBookEntry["child_tree"] = [];
|
||||||
|
|
||||||
// Create the book's child objects
|
// Create the book's child objects
|
||||||
for (let i = 0; i < blueprintBook.blueprints.length; i++) {
|
for (let i = 0; i < blueprintBook.blueprints.length; i++) {
|
||||||
const blueprint = blueprintBook.blueprints[i];
|
const blueprint = blueprintBook.blueprints[i];
|
||||||
if (blueprint.blueprint) {
|
if (blueprint.blueprint) {
|
||||||
const result = await createBlueprint(blueprint.blueprint, extraInfo).catch((error) => {
|
const result = await createBlueprint(blueprint.blueprint, extraInfo);
|
||||||
if (error instanceof DatastoreExistsError) {
|
|
||||||
console.log(`Blueprint already exists with id ${error.existingId}`);
|
|
||||||
return { insertedId: error.existingId };
|
|
||||||
} else throw error;
|
|
||||||
});
|
|
||||||
child_tree.push({
|
child_tree.push({
|
||||||
type: "blueprint",
|
type: "blueprint",
|
||||||
id: result.insertedId,
|
id: result.insertedId,
|
||||||
@ -165,15 +89,7 @@ async function createBlueprintBook(
|
|||||||
});
|
});
|
||||||
blueprint_ids.push(result.insertedId);
|
blueprint_ids.push(result.insertedId);
|
||||||
} else if (blueprint.blueprint_book) {
|
} else if (blueprint.blueprint_book) {
|
||||||
const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo).catch(
|
const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo);
|
||||||
(error) => {
|
|
||||||
if (error instanceof DatastoreExistsError) {
|
|
||||||
console.log(`Blueprint book already exists with id ${error.existingId}`);
|
|
||||||
// TODO: query blueprint book to get child_tree
|
|
||||||
return { insertedId: error.existingId, child_tree: [] as ChildTree };
|
|
||||||
} else throw error;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
child_tree.push({
|
child_tree.push({
|
||||||
type: "blueprint_book",
|
type: "blueprint_book",
|
||||||
id: result.insertedId,
|
id: result.insertedId,
|
||||||
@ -299,11 +215,11 @@ export async function createBlueprint(
|
|||||||
|
|
||||||
const exists = await getBlueprintByHash(blueprint_hash);
|
const exists = await getBlueprintByHash(blueprint_hash);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
throw new DatastoreExistsError(exists.id, "Blueprint already exists");
|
return { insertedId: exists.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write string to google storage
|
// Write string to google storage
|
||||||
await BLUEPRINT_BUCKET.file(blueprint_hash).save(string);
|
await saveBlueprintString(blueprint_hash, string);
|
||||||
|
|
||||||
// Write blueprint details to datastore
|
// Write blueprint details to datastore
|
||||||
const [result] = await datastore.insert({
|
const [result] = await datastore.insert({
|
||||||
@ -364,15 +280,6 @@ export async function updateBlueprint(blueprint: BlueprintEntry) {
|
|||||||
datastore.save(mapBlueprintObjectToEntity(blueprint));
|
datastore.save(mapBlueprintObjectToEntity(blueprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* BlueprintString
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function getBlueprintStringByHash(hash: string): Promise<string | null> {
|
|
||||||
const [buffer] = await BLUEPRINT_BUCKET.file(hash).download();
|
|
||||||
return buffer ? buffer.toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BlueprintPage
|
* BlueprintPage
|
||||||
*/
|
*/
|
||||||
@ -455,21 +362,6 @@ async function createBlueprintPage(
|
|||||||
console.log(`Created Blueprint Page`);
|
console.log(`Created Blueprint Page`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* BlueprintImage
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function saveBlueprintImage(hash: string, image: Buffer): Promise<void> {
|
|
||||||
return IMAGE_BUCKET.file(`${hash}.webp`).save(image, {
|
|
||||||
contentType: "image/webp",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function hasBlueprintImage(hash: string): Promise<boolean> {
|
|
||||||
const [result] = await IMAGE_BUCKET.file(`${hash}.webp`).exists();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Other
|
* Other
|
||||||
*/
|
*/
|
34
libs/database/src/lib/gcp-storage.ts
Normal file
34
libs/database/src/lib/gcp-storage.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Storage } from "@google-cloud/storage";
|
||||||
|
|
||||||
|
const storage = new Storage();
|
||||||
|
|
||||||
|
const BLUEPRINT_BUCKET = storage.bucket("blueprint-strings");
|
||||||
|
const IMAGE_BUCKET = storage.bucket("blueprint-images");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BlueprintString
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getBlueprintStringByHash(hash: string): Promise<string | null> {
|
||||||
|
const [buffer] = await BLUEPRINT_BUCKET.file(hash).download();
|
||||||
|
return buffer ? buffer.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveBlueprintString(hash: string, content: string) {
|
||||||
|
await BLUEPRINT_BUCKET.file(hash).save(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BlueprintImage
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function saveBlueprintImage(hash: string, image: Buffer): Promise<void> {
|
||||||
|
return IMAGE_BUCKET.file(`${hash}.webp`).save(image, {
|
||||||
|
contentType: "image/webp",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hasBlueprintImage(hash: string): Promise<boolean> {
|
||||||
|
const [result] = await IMAGE_BUCKET.file(`${hash}.webp`).exists();
|
||||||
|
return result;
|
||||||
|
}
|
16
libs/database/src/lib/postgres/connection.ts
Normal file
16
libs/database/src/lib/postgres/connection.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createConnection } from "typeorm";
|
||||||
|
import { Blueprint } from "./entities";
|
||||||
|
|
||||||
|
export async function init() {
|
||||||
|
await createConnection({
|
||||||
|
type: "postgres",
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 5432,
|
||||||
|
username: "postgres",
|
||||||
|
password: "local",
|
||||||
|
database: "factorio-blueprints",
|
||||||
|
entities: [Blueprint],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
37
libs/database/src/lib/postgres/entities.ts
Normal file
37
libs/database/src/lib/postgres/entities.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Blueprint {
|
||||||
|
@PrimaryGeneratedColumn("uuid")
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@Column("text")
|
||||||
|
label?: string;
|
||||||
|
|
||||||
|
@Column("text", { nullable: true })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@Column("text")
|
||||||
|
game_version?: string;
|
||||||
|
|
||||||
|
@Column("text", { unique: true })
|
||||||
|
blueprint_hash?: string;
|
||||||
|
|
||||||
|
@Column("varchar", { length: 40 })
|
||||||
|
image_hash?: string;
|
||||||
|
|
||||||
|
@Column("text")
|
||||||
|
image_version?: string;
|
||||||
|
|
||||||
|
@Column("time without time zone")
|
||||||
|
created_at?: number;
|
||||||
|
|
||||||
|
@Column("time without time zone")
|
||||||
|
updated_at?: number;
|
||||||
|
|
||||||
|
@Column("text", { array: true })
|
||||||
|
tags?: string;
|
||||||
|
|
||||||
|
@Column("text", { nullable: true })
|
||||||
|
factorioprints_id?: number;
|
||||||
|
}
|
68
libs/database/src/lib/types.ts
Normal file
68
libs/database/src/lib/types.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
interface BlueprintChild {
|
||||||
|
type: "blueprint";
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
interface BlueprintBookChild {
|
||||||
|
type: "blueprint_book";
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children: ChildTree;
|
||||||
|
}
|
||||||
|
type ChildTree = Array<BlueprintChild | BlueprintBookChild>;
|
||||||
|
|
||||||
|
export interface BlueprintEntry {
|
||||||
|
id: string;
|
||||||
|
label: string; // from source
|
||||||
|
description: string | null; // from source
|
||||||
|
game_version: number; // from source
|
||||||
|
blueprint_hash: string;
|
||||||
|
image_hash: string;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
tags: string[];
|
||||||
|
factorioprints_id?: string;
|
||||||
|
// BlueprintEntry->BlueprintString 1:m
|
||||||
|
// BlueprintEntry->BlueprintPageEntry n:m
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlueprintBookEntry {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
/** strings as keys of BlueprintEntry */
|
||||||
|
blueprint_ids: string[];
|
||||||
|
/** strings as keys of BlueprintBookEntry (currently unsupported) */
|
||||||
|
blueprint_book_ids: string[];
|
||||||
|
child_tree: ChildTree;
|
||||||
|
blueprint_hash: string;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
is_modded: boolean;
|
||||||
|
factorioprints_id?: string;
|
||||||
|
// BlueprintBook:BlueprintBook n:m
|
||||||
|
// BlueprintBook:BlueprintEntry 1:m
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlueprintPageEntry {
|
||||||
|
id: string;
|
||||||
|
blueprint_id?: string;
|
||||||
|
blueprint_book_id?: string;
|
||||||
|
title: string;
|
||||||
|
description_markdown: string;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
factorioprints_id?: string;
|
||||||
|
// BlueprintPageEntry->BlueprintEntry 1:m
|
||||||
|
// BlueprintPageEntry->BlueprintBook 1:m
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlueprintStringEntry {
|
||||||
|
blueprint_id: string;
|
||||||
|
blueprint_hash: string;
|
||||||
|
image_hash: string;
|
||||||
|
version: number;
|
||||||
|
changes_markdown: string;
|
||||||
|
created_at: Date;
|
||||||
|
// BlueprintString->BlueprintEntry m:1
|
||||||
|
}
|
45
package.json
45
package.json
@ -2,6 +2,7 @@
|
|||||||
"name": "factorio-sites",
|
"name": "factorio-sites",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nx": "nx",
|
"nx": "nx",
|
||||||
"start": "nx serve",
|
"start": "nx serve",
|
||||||
@ -25,28 +26,30 @@
|
|||||||
"dep-graph": "nx dep-graph",
|
"dep-graph": "nx dep-graph",
|
||||||
"help": "nx help"
|
"help": "nx help"
|
||||||
},
|
},
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/core": "0.8.0",
|
"@chakra-ui/react": "1.0.4",
|
||||||
"@emotion/core": "10.0.35",
|
"@emotion/react": "11.1.3",
|
||||||
"@emotion/styled": "10.0.27",
|
"@emotion/server": "11.0.0",
|
||||||
|
"@emotion/styled": "11.0.0",
|
||||||
"@google-cloud/datastore": "6.2.0",
|
"@google-cloud/datastore": "6.2.0",
|
||||||
"@google-cloud/pubsub": "2.6.0",
|
"@google-cloud/pubsub": "2.6.0",
|
||||||
"@google-cloud/storage": "5.3.0",
|
"@google-cloud/storage": "5.3.0",
|
||||||
"bbcode-to-react": "0.2.9",
|
"bbcode-to-react": "0.2.9",
|
||||||
"document-register-element": "1.14.10",
|
"document-register-element": "1.14.10",
|
||||||
"emotion-server": "10.0.27",
|
"framer-motion": "3.1.1",
|
||||||
"emotion-theming": "10.0.27",
|
"next": "10.0.3",
|
||||||
"next": "9.5.5",
|
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
|
"pg": "8.4.1",
|
||||||
"phin": "3.5.0",
|
"phin": "3.5.0",
|
||||||
"puppeteer": "5.3.1",
|
"puppeteer": "5.3.1",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
|
"react-icons": "4.1.0",
|
||||||
"react-map-interaction": "2.0.0",
|
"react-map-interaction": "2.0.0",
|
||||||
"react-markdown": "5.0.1",
|
"react-markdown": "5.0.1",
|
||||||
"sharp": "0.26.2",
|
"sharp": "0.26.2",
|
||||||
|
"typeorm": "0.2.28",
|
||||||
"ws": "7.3.1"
|
"ws": "7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -54,22 +57,20 @@
|
|||||||
"@babel/preset-env": "7.9.6",
|
"@babel/preset-env": "7.9.6",
|
||||||
"@babel/preset-react": "7.9.4",
|
"@babel/preset-react": "7.9.4",
|
||||||
"@babel/preset-typescript": "7.9.0",
|
"@babel/preset-typescript": "7.9.0",
|
||||||
"@emotion/babel-preset-css-prop": "10.0.27",
|
"@emotion/babel-preset-css-prop": "11.0.0",
|
||||||
"@nrwl/cli": "10.3.1",
|
"@nrwl/cli": "10.3.1",
|
||||||
"@nrwl/cypress": "10.3.1",
|
"@nrwl/cypress": "11.0.16",
|
||||||
"@nrwl/eslint-plugin-nx": "10.3.1",
|
"@nrwl/eslint-plugin-nx": "11.0.16",
|
||||||
"@nrwl/jest": "10.3.1",
|
"@nrwl/jest": "11.0.16",
|
||||||
"@nrwl/next": "10.3.1",
|
"@nrwl/next": "11.0.16",
|
||||||
"@nrwl/node": "10.3.1",
|
"@nrwl/node": "11.0.16",
|
||||||
"@nrwl/react": "10.3.1",
|
"@nrwl/react": "11.0.16",
|
||||||
"@nrwl/web": "10.3.1",
|
"@nrwl/web": "11.0.16",
|
||||||
"@nrwl/workspace": "10.3.1",
|
"@nrwl/workspace": "11.0.16",
|
||||||
"@testing-library/react": "10.4.1",
|
"@testing-library/react": "10.4.1",
|
||||||
"@types/bbcode-to-react": "0.2.0",
|
"@types/bbcode-to-react": "0.2.0",
|
||||||
"@types/imagemin": "7.0.0",
|
|
||||||
"@types/imagemin-webp": "5.1.1",
|
|
||||||
"@types/jest": "26.0.8",
|
"@types/jest": "26.0.8",
|
||||||
"@types/node": "14.14.1",
|
"@types/node": "14.14.14",
|
||||||
"@types/nprogress": "0.2.0",
|
"@types/nprogress": "0.2.0",
|
||||||
"@types/pako": "1.0.1",
|
"@types/pako": "1.0.1",
|
||||||
"@types/puppeteer": "3.0.2",
|
"@types/puppeteer": "3.0.2",
|
||||||
@ -92,8 +93,8 @@
|
|||||||
"jest": "26.2.2",
|
"jest": "26.2.2",
|
||||||
"prettier": "2.1.2",
|
"prettier": "2.1.2",
|
||||||
"ts-jest": "26.4.0",
|
"ts-jest": "26.4.0",
|
||||||
"ts-node": "~7.0.0",
|
"ts-node": "9.1.1",
|
||||||
"tslint": "~6.0.0",
|
"tslint": "6.1.3",
|
||||||
"typescript": "~4.0.3"
|
"typescript": "4.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user