1
0
mirror of https://github.com/barthuijgen/factorio-sites.git synced 2024-11-24 08:52:36 +02:00

update fbe-editor, migrate from sequelize to prisma

This commit is contained in:
Bart Huijgen 2021-03-04 23:07:34 +01:00
parent fe51aa2376
commit a42927f336
22 changed files with 2679 additions and 2684 deletions

4
.gitignore vendored
View File

@ -43,5 +43,5 @@ Thumbs.db
/credentials /credentials
.env.local .env.local
.local.env .local.env
/.yalc local.readme.md
local.readme.md .yalc

View File

@ -1,4 +1,7 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode",
} "[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
}

View File

@ -0,0 +1,50 @@
/*
Warnings:
- The migration will add a unique constraint covering the columns `[blueprint_hash]` on the table `blueprint_book`. If there are existing duplicate values, the migration will fail.
- The migration will add a unique constraint covering the columns `[factorioprints_id]` on the table `blueprint_page`. If there are existing duplicate values, the migration will fail.
- The migration will add a unique constraint covering the columns `[session_token]` on the table `session`. If there are existing duplicate values, the migration will fail.
*/
-- AlterTable
ALTER TABLE "blueprint" ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "blueprint_book" ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "blueprint_page" ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "session" ALTER COLUMN "last_used" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "user" ALTER COLUMN "password_reset_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "last_password_change" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "last_login_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "user_favorites" ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- CreateIndex
CREATE UNIQUE INDEX "blueprint_book.blueprint_hash_unique" ON "blueprint_book"("blueprint_hash");
-- CreateIndex
CREATE UNIQUE INDEX "blueprint_page.factorioprints_id_unique" ON "blueprint_page"("factorioprints_id");
-- CreateIndex
CREATE UNIQUE INDEX "session.session_token_unique" ON "session"("session_token");

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@ -0,0 +1,113 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum enum_user_role {
user
moderator
admin
}
model blueprint {
id String @id @default(uuid()) @db.Uuid
label String? @db.VarChar(255)
description String?
game_version String? @db.VarChar(255)
blueprint_hash String @unique @db.VarChar(40)
image_hash String @db.VarChar(40)
image_version Int @default(1)
tags String[] @db.VarChar(255)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
blueprint_page blueprint_page?
}
model blueprint_book {
id String @id @default(uuid()) @db.Uuid
label String? @db.VarChar(255)
description String?
child_tree Json @db.Json
blueprint_hash String @unique @db.VarChar(40)
is_modded Boolean
created_at DateTime @default(now())
updated_at DateTime @updatedAt
blueprint_page blueprint_page?
}
model blueprint_book_blueprints {
blueprint_book_id String @db.Uuid
blueprint_id String @db.Uuid
@@id([blueprint_book_id, blueprint_id])
}
model blueprint_book_books {
blueprint_book_1_id String @db.Uuid
blueprint_book_2_id String @db.Uuid
@@id([blueprint_book_1_id, blueprint_book_2_id])
}
model blueprint_page {
id String @id @default(uuid()) @db.Uuid
user_id String? @db.Uuid
blueprint_id String? @unique @db.Uuid
blueprint_book_id String? @unique @db.Uuid
title String @db.VarChar(255)
description_markdown String?
tags String[] @db.VarChar(255)
factorioprints_id String? @unique @db.VarChar(255)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
blueprint_book blueprint_book? @relation(fields: [blueprint_book_id], references: [id])
blueprint blueprint? @relation(fields: [blueprint_id], references: [id])
user_favorites user_favorites[]
}
model session {
id String @id @default(uuid()) @db.Uuid
user_id String @db.Uuid
session_token String @unique @db.VarChar(255)
useragent String @db.VarChar(255)
ip String @db.VarChar(255)
last_used DateTime
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user user @relation(fields: [user_id], references: [id])
}
model user {
id String @id @default(uuid()) @db.Uuid
email String? @unique @db.VarChar(255)
username String @unique @db.VarChar(255)
role enum_user_role? @default(user)
steam_id String? @unique @db.VarChar(255)
password String? @db.VarChar(255)
password_reset_token String? @db.Uuid
password_reset_at DateTime?
last_password_change DateTime?
last_login_at DateTime?
last_login_ip String @db.VarChar(255)
email_validated Boolean @default(false)
email_validate_token String? @db.Uuid
created_at DateTime @default(now())
updated_at DateTime @updatedAt
session session[]
user_favorites user_favorites[]
}
model user_favorites {
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user_id String @db.Uuid
blueprint_page_id String @db.Uuid
blueprint_page blueprint_page @relation(fields: [blueprint_page_id], references: [id])
user user @relation(fields: [user_id], references: [id])
@@id([user_id, blueprint_page_id])
}

View File

@ -0,0 +1,51 @@
import Link from "next/link";
import { css } from "@emotion/react";
import { BlueprintPage } from "@factorio-sites/database";
import { Box, Text } from "@chakra-ui/react";
import { MdFavorite } from "react-icons/md";
const linkStyles = css`
width: 100%;
margin: 5px 0;
a {
display: block;
padding: 5px;
color: #fff;
}
&:hover {
cursor: pointer;
background: #ccc;
}
`;
const formatDate = (datenum: number) => {
const date = new Date(datenum * 1000);
return date.toLocaleString();
};
interface BlueprintLinkProps {
blueprint: BlueprintPage;
editLink?: boolean;
}
export const BlueprintLink: React.FC<BlueprintLinkProps> = ({ blueprint, editLink }) => (
<div css={linkStyles}>
<Link
href={editLink ? `/user/blueprint/${blueprint.id}` : `/blueprint/${blueprint.id}`}
passHref
>
<a>
<Box css={{ display: "flex", justifyContent: "space-between" }}>
<Text>{blueprint.title}</Text>
<Box css={{ display: "flex" }}>
<Text css={{ display: "flex", alignItems: "center", marginRight: "2rem" }}>
<MdFavorite css={{ marginRight: "0.5rem" }} />
{blueprint.favorite_count}
</Text>
<Text>{formatDate(blueprint.updated_at)}</Text>
</Box>
</Box>
</a>
</Link>
</div>
);

View File

@ -5,10 +5,10 @@ const handler = apiHandler(async (_, res, { session }) => {
if (session) { if (session) {
return res.status(200).json({ return res.status(200).json({
auth: { auth: {
user_id: session.user.get("id"), user_id: session.user.id,
username: session.user.get("username"), username: session.user.username,
email: session.user.get("email"), email: session.user.email,
steam_id: session.user.get("steam_id"), steam_id: session.user.steam_id,
} as AuthContextProps, } as AuthContextProps,
}); });
} }

View File

@ -1,4 +1,4 @@
import { isBlueprintPageUserFavorite, createUserFavorite } from "@factorio-sites/database"; import { isBlueprintPageUserFavorite, createUserFavorite, prisma } from "@factorio-sites/database";
import { apiHandler } from "../../../utils/api-handler"; import { apiHandler } from "../../../utils/api-handler";
const handler = apiHandler(async (req, res, { session }) => { const handler = apiHandler(async (req, res, { session }) => {
@ -12,7 +12,14 @@ const handler = apiHandler(async (req, res, { session }) => {
const existing = await isBlueprintPageUserFavorite(user.id, blueprint_page_id); const existing = await isBlueprintPageUserFavorite(user.id, blueprint_page_id);
if (existing) { if (existing) {
await existing.destroy(); await prisma().user_favorites.delete({
where: {
user_id_blueprint_page_id: {
user_id: existing.user_id,
blueprint_page_id: existing.blueprint_page_id,
},
},
});
} else { } else {
await createUserFavorite(user.id, blueprint_page_id); await createUserFavorite(user.id, blueprint_page_id);
} }

View File

@ -1,52 +1,12 @@
import React from "react"; import React from "react";
import { NextPage, NextPageContext } from "next"; import { NextPage, NextPageContext } from "next";
import Link from "next/link";
import { css } from "@emotion/react";
import { BlueprintPage, getMostRecentBlueprintPages, init } from "@factorio-sites/database"; import { BlueprintPage, getMostRecentBlueprintPages, init } from "@factorio-sites/database";
import { SimpleGrid, Box, RadioGroup, Stack, Radio, Text } from "@chakra-ui/react"; import { SimpleGrid, Box, RadioGroup, Stack, Radio } from "@chakra-ui/react";
import { Panel } from "../components/Panel"; import { Panel } from "../components/Panel";
import { Pagination } from "../components/Pagination"; import { Pagination } from "../components/Pagination";
import { useRouterQueryToHref } from "../hooks/query.hook"; import { useRouterQueryToHref } from "../hooks/query.hook";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { MdFavorite } from "react-icons/md"; import { BlueprintLink } from "../components/BlueprintLink";
const linkStyles = css`
width: 100%;
margin: 5px 0;
a {
display: block;
padding: 5px;
color: #fff;
}
&:hover {
cursor: pointer;
background: #ccc;
}
`;
const formatDate = (datenum: number) => {
const date = new Date(datenum * 1000);
return date.toLocaleString();
};
const BlueprintComponent: React.FC<{ blueprint: BlueprintPage }> = ({ blueprint }) => (
<div css={linkStyles}>
<Link href={`/blueprint/${blueprint.id}`} passHref>
<a>
<Box css={{ display: "flex", justifyContent: "space-between" }}>
<Text>{blueprint.title}</Text>
<Box css={{ display: "flex" }}>
<Text css={{ display: "flex", alignItems: "center", marginRight: "2rem" }}>
<MdFavorite css={{ marginRight: "0.5rem" }} />
{blueprint.favorite_count}
</Text>
<Text>{formatDate(blueprint.updated_at)}</Text>
</Box>
</Box>
</a>
</Link>
</div>
);
interface IndexProps { interface IndexProps {
totalItems: number; totalItems: number;
@ -88,7 +48,7 @@ export const Index: NextPage<IndexProps> = ({
</Box> </Box>
<Box> <Box>
{blueprints.map((bp) => ( {blueprints.map((bp) => (
<BlueprintComponent key={bp.id} blueprint={bp} /> <BlueprintLink key={bp.id} blueprint={bp} />
))} ))}
<Pagination page={currentPage} totalPages={totalPages} totalItems={totalItems} /> <Pagination page={currentPage} totalPages={totalPages} totalItems={totalItems} />
</Box> </Box>

View File

@ -0,0 +1,229 @@
import React from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { Formik, Field } from "formik";
import { css } from "@emotion/react";
import {
FormControl,
FormLabel,
FormErrorMessage,
Input,
SimpleGrid,
Button,
Box,
Text,
Textarea,
} from "@chakra-ui/react";
import MultiSelect from "react-multi-select-component";
import { chakraResponsive } from "@factorio-sites/web-utils";
import {
Blueprint,
BlueprintBook,
BlueprintPage,
getBlueprintBookById,
getBlueprintById,
getBlueprintPageById,
getBlueprintStringByHash,
} from "@factorio-sites/database";
import { pageHandler } from "../../../utils/page-handler";
import { Panel } from "../../../components/Panel";
import { validateCreateBlueprintForm } from "../../../utils/validate";
import { useAuth } from "../../../providers/auth";
import { ImageEditor } from "../../../components/ImageEditor";
const FieldStyle = css`
margin-bottom: 1rem;
`;
const TAGS = [
{ value: "foo", label: "foo" },
{ value: "bar", label: "bar" },
{ value: "x", label: "x" },
{ value: "y", label: "y" },
];
type Selected =
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash">; string: string }
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash">; string: string };
interface UserBlueprintProps {
blueprintPage: BlueprintPage;
selected: Selected;
}
export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, selected }) => {
const auth = useAuth();
const router = useRouter();
if (!auth) {
router.push("/");
}
if (!blueprintPage) return null;
return (
<div css={{ margin: "0.7rem" }}>
<Formik
initialValues={{
title: blueprintPage.title,
description: blueprintPage.description_markdown,
string: selected.string,
tags: [],
}}
validate={validateCreateBlueprintForm}
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
setStatus("");
const result = await fetch("/api/blueprint/create", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(values),
}).then((res) => res.json());
if (result.status) {
setSubmitting(false);
setStatus(result.status);
} else if (result.errors) {
setSubmitting(false);
setErrors(result.errors);
} else if (result.success) {
router.push(`/blueprint/${result.id}`);
}
}}
>
{({ isSubmitting, handleSubmit, status, values, errors, setFieldValue }) => (
<SimpleGrid
columns={2}
gap={6}
templateColumns={chakraResponsive({ mobile: "1fr", desktop: "1fr 1fr" })}
>
<Panel title="Create new blueprint">
<form onSubmit={handleSubmit}>
<Field name="title">
{({ field, meta }: any) => (
<FormControl
id="title"
isRequired
isInvalid={meta.touched && meta.error}
css={FieldStyle}
>
<FormLabel>Title</FormLabel>
<Input type="text" {...field} />
<FormErrorMessage>{meta.error}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="description">
{({ field, meta }: any) => (
<FormControl
id="description"
isRequired
isInvalid={meta.touched && meta.error}
css={FieldStyle}
>
<FormLabel>Description</FormLabel>
<Textarea {...field} />
<FormErrorMessage>{meta.error}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="tags">
{({ field, meta }: any) => (
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
<FormLabel>Tags</FormLabel>
<MultiSelect
css={{ color: "black" }}
options={TAGS}
value={field.value}
onChange={(value: any) => setFieldValue("tags", value)}
labelledBy="Select"
hasSelectAll={false}
/>
<FormErrorMessage>{meta.error}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="string">
{({ field, meta }: any) => (
<FormControl
id="string"
isRequired
isInvalid={meta.touched && meta.error}
css={FieldStyle}
>
<FormLabel>Blueprint string</FormLabel>
<Input type="text" {...field} />
<FormErrorMessage>{meta.error}</FormErrorMessage>
</FormControl>
)}
</Field>
<Box css={{ display: "flex", alignItems: "center" }}>
<Button type="submit" colorScheme="green" disabled={isSubmitting}>
Submit
</Button>
{status && <Text css={{ marginLeft: "1rem", color: "red" }}>{status}</Text>}
</Box>
</form>
</Panel>
<Panel title="Preview">
<Box>
{values.string && !errors.string && (
<ImageEditor string={values.string}></ImageEditor>
)}
</Box>
</Panel>
</SimpleGrid>
)}
</Formik>
</div>
);
};
export const getServerSideProps = pageHandler(async (context, { session }) => {
const blueprintId = context.query.blueprintId ? (context.query.blueprintId as string) : null;
if (!session || !blueprintId) {
return { props: {} };
}
const blueprintPage = await getBlueprintPageById(blueprintId);
let selected!: UserBlueprintProps["selected"];
if (blueprintPage?.blueprint_id) {
const blueprint = await getBlueprintById(blueprintPage.blueprint_id);
if (!blueprint) return;
selected = {
type: "blueprint",
data: {
id: blueprintPage.blueprint_id,
blueprint_hash: blueprint.blueprint_hash,
},
string: (await getBlueprintStringByHash(blueprint.blueprint_hash)) as string,
};
} else if (blueprintPage?.blueprint_book_id) {
const blueprintBook = await getBlueprintBookById(blueprintPage.blueprint_book_id);
if (!blueprintBook) return;
selected = {
type: "blueprint_book",
data: {
id: blueprintPage.blueprint_book_id,
blueprint_hash: blueprintBook.blueprint_hash,
},
string: (await getBlueprintStringByHash(blueprintBook.blueprint_hash)) as string,
};
}
return {
props: {
blueprintPage: blueprintPage,
selected,
},
};
});
export default UserBlueprint;

View File

@ -3,22 +3,55 @@ import { NextPage } from "next";
import { Button, SimpleGrid, Box } from "@chakra-ui/react"; import { Button, SimpleGrid, Box } from "@chakra-ui/react";
import { Panel } from "../../components/Panel"; import { Panel } from "../../components/Panel";
import Link from "next/link"; import Link from "next/link";
import { pageHandler } from "../../utils/page-handler";
import { BlueprintPage, getBlueprintPageByUserId } from "@factorio-sites/database";
import { BlueprintLink } from "../../components/BlueprintLink";
export const UserBlueprints: NextPage = () => { interface UserBlueprintsProps {
blueprints: BlueprintPage[];
}
export const UserBlueprints: NextPage<UserBlueprintsProps> = ({ blueprints }) => {
return ( return (
<div css={{ margin: "0.7rem" }}> <div css={{ margin: "0.7rem" }}>
<SimpleGrid columns={1} margin="0 auto" maxWidth="800px"> <SimpleGrid columns={1} margin="0 auto" maxWidth="800px">
<Panel title="Blueprints"> <Panel title="Blueprints">
<Box>user blueprints</Box> <Box
<Link href="/user/blueprint-create"> css={{
<a> display: "flex",
<Button colorScheme="green">create blueprint</Button> borderBottom: "1px solid #b7b7b7",
</a> paddingBottom: "0.3rem",
</Link> }}
>
<Link href="/user/blueprint-create">
<a>
<Button colorScheme="green">create blueprint</Button>
</a>
</Link>
</Box>
<Box>
{blueprints.map((bp) => (
<BlueprintLink key={bp.id} blueprint={bp} editLink />
))}
</Box>
</Panel> </Panel>
</SimpleGrid> </SimpleGrid>
</div> </div>
); );
}; };
export const getServerSideProps = pageHandler(async (context, { session }) => {
if (!session) {
return { props: {} };
}
const blueprints = await getBlueprintPageByUserId(session.user.id);
return {
props: {
blueprints,
},
};
});
export default UserBlueprints; export default UserBlueprints;

Binary file not shown.

View File

@ -1,14 +1,13 @@
import { BlueprintData, getBlueprintContentForImageHash } from "@factorio-sites/common-utils"; import { BlueprintData, getBlueprintContentForImageHash } from "@factorio-sites/common-utils";
import { encodeBlueprint, hashString } from "@factorio-sites/node-utils"; import { encodeBlueprint, hashString } from "@factorio-sites/node-utils";
// import { getBlueprintImageRequestTopic } from "../gcp-pubsub"; import { blueprint as BlueprintModel } from "@prisma/client";
import { saveBlueprintString } from "../gcp-storage"; import { saveBlueprintString } from "../gcp-storage";
import { BlueprintModel } from "../postgres/database"; import { prisma } from "../postgres/database";
import { BlueprintInstance } from "../postgres/models/Blueprint";
import { Blueprint } from "../types"; import { Blueprint } from "../types";
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic(); // const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
const mapBlueprintInstanceToEntry = (entity: BlueprintInstance): Blueprint => ({ const mapBlueprintInstanceToEntry = (entity: BlueprintModel): Blueprint => ({
id: entity.id, id: entity.id,
blueprint_hash: entity.blueprint_hash, blueprint_hash: entity.blueprint_hash,
image_hash: entity.image_hash, image_hash: entity.image_hash,
@ -21,18 +20,12 @@ const mapBlueprintInstanceToEntry = (entity: BlueprintInstance): Blueprint => ({
}); });
export async function getBlueprintById(id: string): Promise<Blueprint | null> { export async function getBlueprintById(id: string): Promise<Blueprint | null> {
const result = await BlueprintModel() const result = await prisma().blueprint.findUnique({ where: { id } });
.findByPk(id)
.catch(() => null);
return result ? mapBlueprintInstanceToEntry(result) : null; return result ? mapBlueprintInstanceToEntry(result) : null;
} }
export async function getBlueprintByHash(hash: string): Promise<Blueprint | null> { export async function getBlueprintByHash(hash: string): Promise<Blueprint | null> {
const result = await BlueprintModel() const result = await prisma().blueprint.findUnique({ where: { blueprint_hash: hash } });
.findOne({
where: { blueprint_hash: hash },
})
.catch(() => null);
return result ? mapBlueprintInstanceToEntry(result) : null; return result ? mapBlueprintInstanceToEntry(result) : null;
} }
@ -57,16 +50,19 @@ export async function createBlueprint(
await saveBlueprintString(blueprint_hash, string); await saveBlueprintString(blueprint_hash, string);
// Write blueprint details to datastore // Write blueprint details to datastore
const result = await BlueprintModel().create({
label: blueprint.label, const result = await prisma().blueprint.create({
description: blueprint.description, data: {
blueprint_hash: blueprint_hash, label: blueprint.label,
image_hash: image_hash, description: blueprint.description,
tags: extraInfo.tags, blueprint_hash: blueprint_hash,
game_version: `${blueprint.version}`, image_hash: image_hash,
image_version: 1, tags: extraInfo.tags,
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : undefined, game_version: `${blueprint.version}`,
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : undefined, image_version: 1,
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
},
}); });
console.log(`Created Blueprint ${result.id}`); console.log(`Created Blueprint ${result.id}`);

View File

@ -1,35 +1,29 @@
import { BlueprintBookData } from "@factorio-sites/common-utils"; import { BlueprintBookData } from "@factorio-sites/common-utils";
import { encodeBlueprint, hashString } from "@factorio-sites/node-utils"; import { encodeBlueprint, hashString } from "@factorio-sites/node-utils";
import { blueprint_book } from "@prisma/client";
import { saveBlueprintString } from "../gcp-storage"; import { saveBlueprintString } from "../gcp-storage";
import { BlueprintBookModel } from "../postgres/database"; import { prisma } from "../postgres/database";
import { BlueprintBookInstance } from "../postgres/models/BlueprintBook";
import { BlueprintBook, ChildTree } from "../types"; import { BlueprintBook, ChildTree } from "../types";
import { createBlueprint } from "./blueprint"; import { createBlueprint } from "./blueprint";
const mapBlueprintBookEntityToObject = (entity: BlueprintBookInstance): BlueprintBook => ({ const mapBlueprintBookEntityToObject = (entity: blueprint_book): BlueprintBook => ({
id: entity.id, id: entity.id,
child_tree: entity.child_tree ? entity.child_tree : [], child_tree: entity.child_tree ? (entity.child_tree as any) : [],
blueprint_hash: entity.blueprint_hash, blueprint_hash: entity.blueprint_hash,
label: entity.label, label: entity.label || "",
description: entity.description, description: entity.description || "",
created_at: entity.created_at && entity.created_at.getTime() / 1000, created_at: entity.created_at && entity.created_at.getTime() / 1000,
updated_at: entity.updated_at && entity.updated_at.getTime() / 1000, updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
is_modded: entity.is_modded || false, is_modded: entity.is_modded || false,
}); });
export async function getBlueprintBookById(id: string): Promise<BlueprintBook | null> { export async function getBlueprintBookById(id: string): Promise<BlueprintBook | null> {
const result = await BlueprintBookModel() const result = await prisma().blueprint_book.findUnique({ where: { id } });
.findByPk(id)
.catch(() => null);
return result ? mapBlueprintBookEntityToObject(result) : null; return result ? mapBlueprintBookEntityToObject(result) : null;
} }
export async function getBlueprintBookByHash(hash: string): Promise<BlueprintBook | null> { export async function getBlueprintBookByHash(hash: string): Promise<BlueprintBook | null> {
const result = await BlueprintBookModel() const result = await prisma().blueprint_book.findUnique({ where: { blueprint_hash: hash } });
.findOne({
where: { blueprint_hash: hash },
})
.catch(() => null);
return result ? mapBlueprintBookEntityToObject(result) : null; return result ? mapBlueprintBookEntityToObject(result) : null;
} }
@ -81,14 +75,16 @@ export async function createBlueprintBook(
} }
} }
const result = await BlueprintBookModel().create({ const result = await prisma().blueprint_book.create({
label: blueprintBook.label, data: {
description: blueprintBook.description, label: blueprintBook.label,
blueprint_hash: blueprint_hash, description: blueprintBook.description,
is_modded: false, blueprint_hash: blueprint_hash,
child_tree, is_modded: false,
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : undefined, child_tree: child_tree as any,
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : undefined, updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
},
}); });
console.log(`Created Blueprint book ${result.id}`); console.log(`Created Blueprint book ${result.id}`);

View File

@ -1,15 +1,13 @@
import { Sequelize } from "sequelize"; import { blueprint_page } from "@prisma/client";
import { Op } from "sequelize"; import { prisma } from "../postgres/database";
import { BlueprintPageModel } from "../postgres/database";
import { BlueprintPageInstance } from "../postgres/models/BlueprintPage";
import { BlueprintPage } from "../types"; import { BlueprintPage } from "../types";
const mapBlueprintPageEntityToObject = (entity: BlueprintPageInstance): BlueprintPage => ({ const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage => ({
id: entity.id, id: entity.id,
blueprint_id: entity.blueprint_id ?? null, blueprint_id: entity.blueprint_id ?? null,
blueprint_book_id: entity.blueprint_book_id ?? null, blueprint_book_id: entity.blueprint_book_id ?? null,
title: entity.title, title: entity.title,
description_markdown: entity.description_markdown, description_markdown: entity.description_markdown || "",
created_at: entity.created_at && entity.created_at.getTime() / 1000, created_at: entity.created_at && entity.created_at.getTime() / 1000,
updated_at: entity.updated_at && entity.updated_at.getTime() / 1000, updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
factorioprints_id: entity.factorioprints_id ?? null, factorioprints_id: entity.factorioprints_id ?? null,
@ -17,20 +15,19 @@ const mapBlueprintPageEntityToObject = (entity: BlueprintPageInstance): Blueprin
}); });
export async function getBlueprintPageById(id: string): Promise<BlueprintPage | null> { export async function getBlueprintPageById(id: string): Promise<BlueprintPage | null> {
const result = await BlueprintPageModel() const result = await prisma().blueprint_page.findUnique({ where: { id } });
.findByPk(id)
.catch(() => null);
return result ? mapBlueprintPageEntityToObject(result) : null; return result ? mapBlueprintPageEntityToObject(result) : null;
} }
export async function getBlueprintPageByUserId(user_id: string): Promise<BlueprintPage[] | null> {
const results = await prisma().blueprint_page.findMany({ where: { user_id } });
return results ? results.map((result) => mapBlueprintPageEntityToObject(result)) : null;
}
export async function getBlueprintPageByFactorioprintsId( export async function getBlueprintPageByFactorioprintsId(
id: string id: string
): Promise<BlueprintPage | null> { ): Promise<BlueprintPage | null> {
const result = await BlueprintPageModel() const result = await prisma().blueprint_page.findFirst({ where: { factorioprints_id: id } });
.findOne({
where: { factorioprints_id: id },
})
.catch(() => null);
return result ? mapBlueprintPageEntityToObject(result) : null; return result ? mapBlueprintPageEntityToObject(result) : null;
} }
@ -45,37 +42,30 @@ export async function getMostRecentBlueprintPages({
query?: string; query?: string;
order: "date" | "favorites" | string; order: "date" | "favorites" | string;
}): Promise<{ count: number; rows: BlueprintPage[] }> { }): Promise<{ count: number; rows: BlueprintPage[] }> {
const orderMap: any = { const orderMap: Record<string, string> = {
date: { date: "updated_at",
order: [["updated_at", "DESC"]], favorites: "favorite_count",
},
favorites: {
order: [[Sequelize.literal(`"favorite_count"`), "DESC"]],
},
}; };
const orderArgs = orderMap[order] || orderMap.date; const result = (
const result = await BlueprintPageModel() await prisma().$queryRaw<(blueprint_page & { favorite_count: number })[]>(
.findAndCountAll({ `SELECT *, (SELECT COUNT(*) FROM user_favorites where user_favorites.blueprint_page_id = blueprint_page.id) AS favorite_count
where: query ? { title: { [Op.iLike]: `%${query}%` } } : undefined, FROM public.blueprint_page
limit: perPage, WHERE blueprint_page.title ILIKE $1
offset: (page - 1) * perPage, ORDER BY ${orderMap[order] || orderMap.date} DESC
raw: true, LIMIT $2 OFFSET $3`,
attributes: [ query ? `%${query}%` : "%",
"blueprint_page.*", perPage,
[ (page - 1) * perPage
Sequelize.literal( )
"(SELECT COUNT(*) FROM user_favorites WHERE user_favorites.blueprint_page_id = blueprint_page.id)" ).map((blueprintPage) => ({
), ...blueprintPage,
"favorite_count", created_at: new Date(blueprintPage.created_at),
], updated_at: new Date(blueprintPage.updated_at),
], }));
...orderArgs,
})
.catch(() => null);
return { return {
count: result?.count ?? 0, count: result.length,
rows: result?.rows.map(mapBlueprintPageEntityToObject) ?? [], rows: result.map(mapBlueprintPageEntityToObject),
}; };
} }
@ -92,16 +82,18 @@ export async function createBlueprintPage(
factorioprints_id?: string; factorioprints_id?: string;
} }
) { ) {
const page = await BlueprintPageModel().create({ const page = await prisma().blueprint_page.create({
user_id: extraInfo.user_id, data: {
title: extraInfo.title, user_id: extraInfo.user_id,
description_markdown: extraInfo.description_markdown, title: extraInfo.title,
factorioprints_id: extraInfo.factorioprints_id, description_markdown: extraInfo.description_markdown,
blueprint_id: type === "blueprint" ? targetId : undefined, factorioprints_id: extraInfo.factorioprints_id,
blueprint_book_id: type === "blueprint_book" ? targetId : undefined, blueprint_id: type === "blueprint" ? targetId : undefined,
tags: extraInfo.tags ? extraInfo.tags : [], blueprint_book_id: type === "blueprint_book" ? targetId : undefined,
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : undefined, tags: extraInfo.tags ? extraInfo.tags : [],
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : undefined, updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
},
}); });
console.log(`Created Blueprint Page`); console.log(`Created Blueprint Page`);

View File

@ -1,10 +1,10 @@
import * as bcrypt from "bcrypt"; import * as bcrypt from "bcrypt";
import { sequelize, SessionModel, UserModel } from "../postgres/database"; import { prisma } from "../postgres/database";
import { SessionInstance } from "../postgres/models/Session"; import { SessionInstance } from "../postgres/models/Session";
import { UserInstance } from "../postgres/models/User"; import { UserInstance } from "../postgres/models/User";
export const getUserById = async (id: string) => { export const getUserById = async (id: string) => {
const user = await UserModel().findByPk(id, { raw: true }); const user = await prisma().user.findUnique({ where: { id } });
return user ? user : null; return user ? user : null;
}; };
@ -15,20 +15,24 @@ export const createUserWithEmail = async (
ip: string ip: string
) => { ) => {
const hash = await bcrypt.hash(password, 10); const hash = await bcrypt.hash(password, 10);
return UserModel().create({ return prisma().user.create({
email, data: {
username, email,
password: hash, username,
last_login_ip: ip, password: hash,
last_password_change: new Date(), last_login_ip: ip,
last_password_change: new Date(),
},
}); });
}; };
export const createUserWithSteam = async (steam_id: string, username: string, ip: string) => { export const createUserWithSteam = async (steam_id: string, username: string, ip: string) => {
return UserModel().create({ return prisma().user.create({
steam_id, data: {
username, steam_id,
last_login_ip: ip, username,
last_login_ip: ip,
},
}); });
}; };
@ -43,7 +47,7 @@ export const loginUserWithEmail = async ({
useragent: string; useragent: string;
ip: string; ip: string;
}): Promise<(UserInstance & { session: SessionInstance }) | null> => { }): Promise<(UserInstance & { session: SessionInstance }) | null> => {
const user = await UserModel().findOne({ where: { email } }); const user = await prisma().user.findUnique({ where: { email } });
if (!user) { if (!user) {
return null; return null;
} }
@ -61,45 +65,50 @@ export const loginUserWithEmail = async ({
}; };
export const loginUserWithSteam = async (steam_id: string, ip: string) => { export const loginUserWithSteam = async (steam_id: string, ip: string) => {
const user = await UserModel().findOne({ where: { steam_id } }); const user = await prisma().user.findUnique({ where: { steam_id } });
if (!user) { if (!user) {
return false; return false;
} }
await user.update({ await prisma().user.update({
last_login_ip: ip, where: { steam_id },
last_login_at: new Date(), data: {
last_login_ip: ip,
last_login_at: new Date(),
},
}); });
return user; return user;
}; };
export const createSession = async (user: UserInstance, useragent: string, ip: string) => { export const createSession = async (user: UserInstance, useragent: string, ip: string) => {
return SessionModel().create({ return prisma().session.create({
user_id: user.id, datta: {
useragent, user_id: user.id,
ip, useragent,
ip,
},
}); });
}; };
export const getSessionByToken = async ( export const getSessionByToken = async (token: string) => {
token: string return await prisma().session.findUnique({
): Promise<(SessionInstance & { user: UserInstance }) | null> => {
return SessionModel().findOne<any>({
where: { session_token: token }, where: { session_token: token },
include: [UserModel()], include: {
user: true,
},
}); });
}; };
export const isBlueprintPageUserFavorite = async (user_id: string, blueprint_page_id: string) => { export const isBlueprintPageUserFavorite = async (user_id: string, blueprint_page_id: string) => {
const UserFavorites = sequelize().models.user_favorites; const { user_favorites } = prisma();
return UserFavorites.findOne({ return user_favorites.findFirst({ where: { user_id, blueprint_page_id } });
where: { user_id, blueprint_page_id },
});
}; };
export const createUserFavorite = async (user_id: string, blueprint_page_id: string) => { export const createUserFavorite = async (user_id: string, blueprint_page_id: string) => {
const UserFavorites = sequelize().models.user_favorites; const { user_favorites } = prisma();
return UserFavorites.create({ return user_favorites.create({
user_id, data: {
blueprint_page_id, user_id,
blueprint_page_id,
},
}); });
}; };

View File

@ -1,438 +0,0 @@
// import { Datastore } from "@google-cloud/datastore";
// import { encodeBlueprint, hashString, parseBlueprintString } from "@factorio-sites/node-utils";
// import {
// BlueprintStringData,
// BlueprintBookData,
// getBlueprintContentForImageHash,
// } from "@factorio-sites/common-utils";
// import { getBlueprintImageRequestTopic } from "./gcp-pubsub";
// import { saveBlueprintString } from "./gcp-storage";
// import { BlueprintBook, Blueprint, BlueprintPage } from "./types";
// const datastore = new Datastore();
// const BlueprintEntity = "Blueprint";
// const BlueprintBookEntity = "BlueprintBook";
// const BlueprintPageEntity = "BlueprintPage";
// const BlueprintStringEntity = "BlueprintString";
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
// class DatastoreExistsError extends Error {
// constructor(public existingId: string, message: string) {
// super(message);
// this.name = this.constructor.name;
// Error.captureStackTrace(this, this.constructor);
// }
// }
// /*
// * BlueprintBook
// */
// const mapBlueprintBookEntityToObject = (entity: any): BlueprintBook => ({
// id: entity[datastore.KEY].id,
// // blueprint_ids: entity.blueprint_ids.map((key: any) => key.id),
// // blueprint_book_ids: entity.blueprint_book_ids.map((key: any) => key.id),
// child_tree: entity.child_tree ? entity.child_tree : [],
// blueprint_hash: entity.blueprint_hash,
// label: entity.label,
// description: entity.description,
// created_at: entity.created_at && entity.created_at.getTime() / 1000,
// updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
// is_modded: entity.is_modded || false,
// factorioprints_id: entity.factorioprints_id || null,
// });
// export async function getBlueprintBookById(id: string): Promise<BlueprintBook | null> {
// const result = await datastore.get(datastore.key([BlueprintBookEntity, Number(id)]));
// return result[0] ? mapBlueprintBookEntityToObject(result[0]) : null;
// }
// export async function getBlueprintBookByHash(hash: string): Promise<BlueprintBook | null> {
// const query = datastore
// .createQuery(BlueprintBookEntity)
// .filter("blueprint_hash", "=", hash)
// .limit(1);
// const [result] = await datastore.runQuery(query);
// return result[0] ? mapBlueprintBookEntityToObject(result[0]) : null;
// }
// export async function createBlueprintBook(
// blueprintBook: BlueprintBookData,
// extraInfo: { tags: string[]; created_at: number; updated_at: number; factorioprints_id: string }
// ): Promise<{ insertedId: string; child_tree: BlueprintBook["child_tree"] }> {
// const string = await encodeBlueprint({ blueprint_book: blueprintBook });
// const blueprint_hash = hashString(string);
// const exists = await getBlueprintBookByHash(blueprint_hash);
// if (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
// await saveBlueprintString(blueprint_hash, string);
// const blueprint_ids = [];
// const blueprint_book_ids = [];
// const child_tree: BlueprintBook["child_tree"] = [];
// // Create the book's child objects
// for (let i = 0; i < blueprintBook.blueprints.length; i++) {
// const blueprint = blueprintBook.blueprints[i];
// if (blueprint.blueprint) {
// const result = await createBlueprint(blueprint.blueprint, extraInfo);
// child_tree.push({
// type: "blueprint",
// id: result.insertedId,
// name: blueprint.blueprint.label,
// });
// blueprint_ids.push(result.insertedId);
// } else if (blueprint.blueprint_book) {
// const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo);
// child_tree.push({
// type: "blueprint_book",
// id: result.insertedId,
// name: blueprint.blueprint_book.label,
// children: result.child_tree,
// });
// blueprint_book_ids.push(result.insertedId);
// }
// }
// // Write blueprint details to datastore
// const [result] = await datastore.insert({
// key: datastore.key([BlueprintBookEntity]),
// data: [
// {
// name: "blueprint_ids",
// value: blueprint_ids.map((id) => datastore.key([BlueprintEntity, datastore.int(id)])),
// },
// {
// name: "blueprint_book_ids",
// value: blueprint_book_ids.map((id) =>
// datastore.key([BlueprintBookEntity, datastore.int(id)])
// ),
// },
// { name: "child_tree", value: child_tree, excludeFromIndexes: true },
// { name: "label", value: blueprintBook.label },
// {
// name: "description",
// value: blueprintBook.description || null,
// excludeFromIndexes: true,
// },
// { name: "blueprint_hash", value: blueprint_hash },
// {
// name: "created_at",
// value: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : null,
// excludeFromIndexes: true,
// },
// {
// name: "updated_at",
// value: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : null,
// excludeFromIndexes: true,
// },
// { name: "factorioprints_id", value: extraInfo.factorioprints_id || null },
// ],
// });
// const insertedId = String(result.mutationResults?.[0]?.key?.path?.[0]?.id) as string;
// if (!insertedId) throw Error("Something went wrong inserting entity");
// console.log(`Created Blueprint book ${insertedId}`);
// return { insertedId, child_tree };
// }
// /*
// * Blueprint
// */
// const mapBlueprintEntityToObject = (entity: any): Blueprint => ({
// id: entity[datastore.KEY].id,
// blueprint_hash: entity.blueprint_hash,
// image_hash: entity.image_hash,
// label: entity.label,
// description: entity.description,
// tags: entity.tags,
// created_at: entity.created_at && entity.created_at.getTime() / 1000,
// updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
// factorioprints_id: entity.factorioprints_id || null,
// game_version: entity.game_version,
// });
// const mapBlueprintObjectToEntity = (object: Omit<Blueprint, "id"> & { id?: string }): any => ({
// key: datastore.key(object.id ? [BlueprintEntity, datastore.int(object.id)] : [BlueprintEntity]),
// data: [
// { name: "label", value: object.label },
// {
// name: "description",
// value: object.description || null,
// excludeFromIndexes: true,
// },
// { name: "blueprint_hash", value: object.blueprint_hash },
// { name: "image_hash", value: object.image_hash },
// { name: "tags", value: object.tags || [] },
// {
// name: "created_at",
// value: object.created_at ? new Date(object.created_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// {
// name: "updated_at",
// value: object.updated_at ? new Date(object.updated_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// { name: "game_version", value: object.game_version, excludeFromIndexes: true },
// { name: "factorioprints_id", value: object.factorioprints_id || null },
// ],
// });
// export async function getBlueprintById(id: string): Promise<Blueprint | null> {
// const result = await datastore.get(datastore.key([BlueprintEntity, datastore.int(id)]));
// return result[0] ? mapBlueprintEntityToObject(result[0]) : null;
// }
// export async function getBlueprintByHash(hash: string): Promise<Blueprint | null> {
// const query = datastore.createQuery(BlueprintEntity).filter("blueprint_hash", "=", hash).limit(1);
// const [result] = await datastore.runQuery(query);
// return result[0] ? mapBlueprintEntityToObject(result[0]) : null;
// }
// export async function getBlueprintByImageHash(hash: string): Promise<Blueprint | null> {
// const query = datastore.createQuery(BlueprintEntity).filter("image_hash", "=", hash).limit(1);
// const [result] = await datastore.runQuery(query);
// return result[0] ? mapBlueprintEntityToObject(result[0]) : null;
// }
// export async function createBlueprint(
// blueprint: BlueprintStringData,
// extraInfo: { tags: string[]; created_at: number; updated_at: number; factorioprints_id: string }
// ) {
// const string = await encodeBlueprint({ blueprint });
// const blueprint_hash = hashString(string);
// const image_hash = hashString(getBlueprintContentForImageHash(blueprint));
// const exists = await getBlueprintByHash(blueprint_hash);
// if (exists) {
// return { insertedId: exists.id };
// }
// // Write string to google storage
// await saveBlueprintString(blueprint_hash, string);
// // Write blueprint details to datastore
// const [result] = await datastore.insert({
// key: datastore.key([BlueprintEntity]),
// data: [
// { name: "label", value: blueprint.label },
// {
// name: "description",
// value: blueprint.description || null,
// excludeFromIndexes: true,
// },
// { name: "blueprint_hash", value: blueprint_hash },
// { name: "image_hash", value: image_hash },
// { name: "tags", value: extraInfo.tags ? extraInfo.tags : [] },
// {
// name: "created_at",
// value: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// {
// name: "updated_at",
// value: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// { name: "game_version", value: blueprint.version, excludeFromIndexes: true },
// { name: "factorioprints_id", value: extraInfo.factorioprints_id || null },
// ],
// });
// const insertedId = String(result.mutationResults?.[0]?.key?.path?.[0]?.id);
// if (!insertedId) throw Error("Something went wrong inserting entity");
// console.log(`Created Blueprint ${insertedId}`);
// blueprintImageRequestTopic.publishJSON({
// blueprintId: insertedId,
// });
// await datastore.insert({
// key: datastore.key([BlueprintEntity, datastore.int(insertedId), BlueprintStringEntity]),
// data: [
// { name: "blueprint_hash", value: blueprint_hash },
// { name: "image_hash", value: image_hash },
// { name: "version", value: 1 },
// { name: "changes_markdown", value: null },
// {
// name: "created_at",
// value: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// ],
// });
// return { insertedId };
// }
// export async function updateBlueprint(blueprint: Blueprint) {
// datastore.save(mapBlueprintObjectToEntity(blueprint));
// }
// /*
// * BlueprintPage
// */
// const mapBlueprintPageEntityToObject = (entity: any): BlueprintPage => ({
// id: entity[datastore.KEY].id,
// blueprint_id: entity.blueprint_id ? entity.blueprint_id.id : null,
// blueprint_book_id: entity.blueprint_book_id ? entity.blueprint_book_id.id : null,
// title: entity.title,
// description_markdown: entity.description_markdown,
// created_at: entity.created_at && entity.created_at.getTime() / 1000,
// updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
// factorioprints_id: entity.factorioprints_id || null,
// });
// export async function getBlueprintPageById(id: string): Promise<BlueprintPage | null> {
// const result = await datastore.get(datastore.key([BlueprintPageEntity, datastore.int(id)]));
// return result[0] ? mapBlueprintPageEntityToObject(result[0]) : null;
// }
// export async function getBlueprintPageByFactorioprintsId(
// id: string
// ): Promise<BlueprintPage | null> {
// const query = datastore
// .createQuery(BlueprintPageEntity)
// .filter("factorioprints_id", "=", id)
// .limit(1);
// const [result] = await datastore.runQuery(query);
// return result[0] ? mapBlueprintPageEntityToObject(result[0]) : null;
// }
// async function createBlueprintPage(
// type: "blueprint" | "blueprint_book",
// targetId: string,
// extraInfo: {
// title: string;
// description_markdown: string;
// created_at: number;
// updated_at: number;
// factorioprints_id?: string;
// }
// ) {
// const insertData: any = [
// { name: "title", value: extraInfo.title },
// {
// name: "description_markdown",
// value: extraInfo.description_markdown,
// excludeFromIndexes: true,
// },
// {
// name: "created_at",
// value: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
// excludeFromIndexes: true,
// },
// {
// name: "updated_at",
// value: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
// },
// { name: "factorioprints_id", value: extraInfo.factorioprints_id || null },
// ];
// if (type === "blueprint") {
// insertData.push({
// name: "blueprint_id",
// value: datastore.key([BlueprintEntity, datastore.int(targetId)]),
// });
// } else if (type === "blueprint_book") {
// insertData.push({
// name: "blueprint_book_id",
// value: datastore.key([BlueprintBookEntity, datastore.int(targetId)]),
// });
// } else {
// throw Error("Invalid type given");
// }
// await datastore.insert({
// key: datastore.key([BlueprintPageEntity]),
// data: insertData,
// });
// console.log(`Created Blueprint Page`);
// }
// /*
// * Other
// */
// interface BlueprintDataFromFactorioprints {
// description_markdown: string;
// title: string;
// updated_at: number;
// created_at: number;
// tags: string[];
// factorioprints_id: string;
// }
// export async function saveBlueprintFromFactorioprints(
// factorioprintData: BlueprintDataFromFactorioprints,
// blueprintString: string
// ) {
// const parsed = await parseBlueprintString(blueprintString);
// // not needed for inserting, just printing
// // const { blueprints, books } = flattenBlueprintData(parsed.data);
// // console.log(`string has ${books.length} books with ${blueprints.length} blueprints`);
// const extraInfo = {
// created_at: factorioprintData.created_at,
// updated_at: factorioprintData.updated_at,
// tags: factorioprintData.tags,
// factorioprints_id: factorioprintData.factorioprints_id,
// };
// const extraInfoPage = {
// title: factorioprintData.title,
// description_markdown: factorioprintData.description_markdown,
// created_at: factorioprintData.created_at,
// updated_at: factorioprintData.updated_at,
// factorioprints_id: factorioprintData.factorioprints_id,
// };
// if (parsed.data.blueprint) {
// console.log("string has one blueprint...");
// const { insertedId } = await createBlueprint(parsed.data.blueprint, extraInfo).catch(
// (error) => {
// if (error instanceof DatastoreExistsError) {
// console.log(`Blueprint already exists with id ${error.existingId}`);
// return { insertedId: error.existingId };
// } else throw error;
// }
// );
// await createBlueprintPage("blueprint", insertedId, extraInfoPage);
// } else if (parsed.data.blueprint_book) {
// console.log("string has a blueprint book...");
// const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, extraInfo);
// await createBlueprintPage("blueprint_book", insertedId, extraInfoPage);
// }
// return true;
// }
// export async function getMostRecentBlueprintPages(page = 1): Promise<BlueprintPage[]> {
// const perPage = 10;
// const query = datastore
// .createQuery(BlueprintPageEntity)
// .limit(perPage)
// .offset((page - 1) * perPage);
// const [result] = await datastore.runQuery(query);
// return result.map(mapBlueprintPageEntityToObject);
// }
// export async function getPaginatedBlueprints(page = 1, perPage = 10): Promise<Blueprint[]> {
// const query = datastore
// .createQuery(BlueprintEntity)
// .limit(perPage)
// .offset((page - 1) * perPage);
// const [result] = await datastore.runQuery(query);
// return result.map(mapBlueprintEntityToObject);
// }

View File

@ -1,123 +1,40 @@
import { Sequelize } from "sequelize"; import { PrismaClient } from "@prisma/client";
import { getBlueprintModel } from "./models/Blueprint"; import { getEnvOrThrow, getSecretOrThrow } from "../env.util";
import { getBlueprintBookModel } from "./models/BlueprintBook";
import { getBlueprintPageModel } from "./models/BlueprintPage";
import { getUsertModel } from "./models/User";
import { getSessionModel } from "./models/Session";
import { getEnv, getEnvOrThrow, getSecretOrThrow } from "../env.util";
const _init = async () => { const _init = async () => {
if (_prisma) return _prisma;
const databasePassword = getEnvOrThrow("POSTGRES_PASSWORD"); const databasePassword = getEnvOrThrow("POSTGRES_PASSWORD");
const sequelize = new Sequelize({ const prisma = new PrismaClient({
dialect: "postgres", log: ["query", "info", "warn", "error"],
host: getEnvOrThrow("POSTGRES_HOST"), datasources: {
port: 5432, db: {
database: getEnvOrThrow("POSTGRES_DB"), url: `postgresql://${getEnvOrThrow("POSTGRES_USER")}:${await getSecretOrThrow(
username: getEnvOrThrow("POSTGRES_USER"), databasePassword
password: databasePassword.startsWith("projects/") )}@${getEnvOrThrow("POSTGRES_HOST")}:5432/${getEnvOrThrow("POSTGRES_DB")}`,
? await getSecretOrThrow(databasePassword) },
: databasePassword,
define: {
underscored: true,
freezeTableName: true,
updatedAt: "updated_at",
createdAt: "created_at",
}, },
}); });
const UserModel = getUsertModel(sequelize); await prisma.$connect();
const SessionModel = getSessionModel(sequelize);
const BlueprintModel = getBlueprintModel(sequelize);
const BlueprintBookModel = getBlueprintBookModel(sequelize);
const BlueprintPageModel = getBlueprintPageModel(sequelize);
UserModel.hasMany(SessionModel, { foreignKey: "user_id" }); return prisma;
SessionModel.belongsTo(UserModel, { foreignKey: "user_id" });
BlueprintModel.hasMany(BlueprintPageModel, { foreignKey: "blueprint_id" });
BlueprintPageModel.belongsTo(BlueprintModel, { foreignKey: "blueprint_id" });
BlueprintPageModel.belongsTo(BlueprintBookModel, { foreignKey: "blueprint_book_id" });
BlueprintBookModel.hasMany(BlueprintPageModel, { foreignKey: "blueprint_book_id" });
BlueprintBookModel.belongsToMany(BlueprintBookModel, {
through: "blueprint_book_books",
as: "blueprint_books",
foreignKey: "blueprint_book_1_id",
otherKey: "blueprint_book_2_id",
timestamps: false,
});
BlueprintBookModel.belongsToMany(BlueprintModel, {
through: "blueprint_book_blueprints",
as: "blueprints",
foreignKey: "blueprint_book_id",
otherKey: "blueprint_id",
timestamps: false,
});
UserModel.belongsToMany(BlueprintPageModel, {
through: "user_favorites",
as: "favorites",
foreignKey: "user_id",
otherKey: "blueprint_page_id",
});
// Test database connection, if it fails the method will throw
if (process.env.NODE_ENV === "development") {
console.log(`[DB] connecting to database ${sequelize.config.database}`);
await sequelize.authenticate();
}
if (getEnv("SYNCHRONIZE_DB")) {
const sync_tables = getEnv("SYNCHRONIZE_DB")?.split(",") || [];
console.log(`[SYNCHRONIZE_DB] syncing database`, { sync_tables });
for (const key in sequelize.models) {
if (sync_tables.includes(key) || sync_tables.includes("*")) {
await sequelize.models[key].sync({ force: true });
}
}
}
return {
sequelize,
UserModel,
SessionModel,
BlueprintModel,
BlueprintBookModel,
BlueprintPageModel,
};
}; };
const promise = _init() const promise = _init()
.then((result) => { .then((result) => {
_sequelize = result.sequelize; _prisma = result;
_UserModel = result.UserModel;
_SessionModel = result.SessionModel;
_BlueprintModel = result.BlueprintModel;
_BlueprintBookModel = result.BlueprintBookModel;
_BlueprintPageModel = result.BlueprintPageModel;
return result; return result;
}) })
.catch((reason) => { .catch((reason) => {
console.log("Database failed to init!", reason); console.log("Database failed to init!", reason);
}); });
let _sequelize: Sequelize; let _prisma: PrismaClient;
let _UserModel: ReturnType<typeof getUsertModel>;
let _SessionModel: ReturnType<typeof getSessionModel>;
let _BlueprintModel: ReturnType<typeof getBlueprintModel>;
let _BlueprintBookModel: ReturnType<typeof getBlueprintBookModel>;
let _BlueprintPageModel: ReturnType<typeof getBlueprintPageModel>;
export const init = async () => { export const init = async () => {
return promise; return promise;
}; };
export const sequelize = () => _sequelize; export const prisma = () => _prisma;
export const UserModel = () => _UserModel;
export const SessionModel = () => _SessionModel;
export const BlueprintModel = () => _BlueprintModel;
export const BlueprintBookModel = () => _BlueprintBookModel;
export const BlueprintPageModel = () => _BlueprintPageModel;

View File

@ -1,10 +0,0 @@
import { QueryInterface, Sequelize } from "sequelize";
module.exports = {
async up(queryInterface: QueryInterface, Sequelize: Sequelize) {
queryInterface.createTable("user", {});
},
async down(queryInterface: QueryInterface, Sequelize: Sequelize) {
//
},
};

View File

@ -24,76 +24,79 @@
"update": "nx migrate latest", "update": "nx migrate latest",
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help" "help": "nx help",
"db-gen": "npx prisma generate --schema=./apps/blueprints/prisma/schema.prisma",
"preinstall": "npx yalc add @fbe/editor"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react": "1.1.2", "@chakra-ui/react": "1.3.3",
"@emotion/react": "11.1.4", "@emotion/react": "11.1.5",
"@emotion/server": "11.0.0", "@emotion/server": "11.0.0",
"@emotion/styled": "11.0.0", "@emotion/styled": "11.1.5",
"@fbe/editor": "./fbe-editor-v1.0.0.tgz", "@fbe/editor": "file:.yalc/@fbe/editor",
"@google-cloud/datastore": "6.3.1", "@google-cloud/datastore": "6.3.1",
"@google-cloud/pubsub": "2.7.0", "@google-cloud/pubsub": "2.9.0",
"@google-cloud/secret-manager": "3.2.3", "@google-cloud/secret-manager": "3.4.0",
"@google-cloud/storage": "5.7.0", "@google-cloud/storage": "5.7.4",
"@hookstate/core": "3.0.3", "@hookstate/core": "3.0.5",
"@prisma/client": "2.18.0",
"bbcode-to-react": "0.2.9", "bbcode-to-react": "0.2.9",
"bcrypt": "5.0.0", "bcrypt": "5.0.0",
"cookie": "0.4.1", "cookie": "0.4.1",
"document-register-element": "1.14.10", "document-register-element": "1.14.10",
"formik": "2.2.6", "formik": "2.2.6",
"framer-motion": "3.1.4", "framer-motion": "3.3.0",
"next": "10.0.5", "next": "10.0.7",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"openid": "2.0.7", "openid": "2.0.7",
"pako": "1.0.11", "pako": "1.0.11",
"pg": "8.5.1", "pg": "8.5.1",
"phin": "3.5.1", "phin": "3.5.1",
"puppeteer": "5.5.0", "puppeteer": "7.1.0",
"react": "17.0.1", "react": "17.0.1",
"react-dom": "17.0.1", "react-dom": "17.0.1",
"react-icons": "4.1.0", "react-icons": "4.2.0",
"react-map-interaction": "2.0.0", "react-map-interaction": "2.0.0",
"react-markdown": "5.0.3", "react-markdown": "5.0.3",
"sequelize": "6.3.5", "react-multi-select-component": "3.1.3",
"sharp": "0.27.0", "sharp": "0.27.1",
"ws": "7.4.2" "ws": "7.4.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.12.10", "@babel/core": "7.12.16",
"@babel/preset-env": "7.12.11", "@babel/preset-env": "7.12.16",
"@babel/preset-react": "7.12.10", "@babel/preset-react": "7.12.13",
"@babel/preset-typescript": "7.12.7", "@babel/preset-typescript": "7.12.16",
"@nrwl/cli": "11.0.20", "@nrwl/cli": "11.2.12",
"@nrwl/cypress": "11.0.20", "@nrwl/cypress": "11.2.12",
"@nrwl/eslint-plugin-nx": "11.0.20", "@nrwl/eslint-plugin-nx": "11.2.12",
"@nrwl/jest": "11.0.20", "@nrwl/jest": "11.2.12",
"@nrwl/next": "11.0.20", "@nrwl/next": "11.2.12",
"@nrwl/node": "11.0.20", "@nrwl/node": "11.2.12",
"@nrwl/react": "11.0.20", "@nrwl/react": "11.2.12",
"@nrwl/web": "11.0.20", "@nrwl/web": "11.2.12",
"@nrwl/workspace": "11.0.20", "@nrwl/workspace": "11.2.12",
"@testing-library/react": "11.2.2", "@testing-library/react": "11.2.5",
"@types/bbcode-to-react": "0.2.0", "@types/bbcode-to-react": "0.2.0",
"@types/bcrypt": "3.0.0", "@types/bcrypt": "3.0.0",
"@types/cookie": "0.4.0", "@types/cookie": "0.4.0",
"@types/jest": "26.0.20", "@types/jest": "26.0.20",
"@types/node": "14.14.14", "@types/node": "14.14.28",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/openid": "2.0.1", "@types/openid": "2.0.1",
"@types/pako": "1.0.1", "@types/pako": "1.0.1",
"@types/puppeteer": "5.4.2", "@types/puppeteer": "5.4.3",
"@types/react": "17.0.0", "@types/react": "17.0.2",
"@types/react-dom": "17.0.0", "@types/react-dom": "17.0.1",
"@types/sharp": "0.27.1", "@types/sharp": "0.27.1",
"@types/ws": "7.4.0", "@types/ws": "7.4.0",
"@typescript-eslint/eslint-plugin": "4.12.0", "@typescript-eslint/eslint-plugin": "4.15.1",
"@typescript-eslint/parser": "4.12.0", "@typescript-eslint/parser": "4.15.1",
"babel-jest": "26.6.3", "babel-jest": "26.6.3",
"cypress": "6.2.1", "cypress": "6.4.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"eslint": "7.17.0", "eslint": "7.20.0",
"eslint-config-prettier": "7.1.0", "eslint-config-prettier": "7.2.0",
"eslint-plugin-cypress": "2.11.2", "eslint-plugin-cypress": "2.11.2",
"eslint-plugin-import": "2.22.1", "eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1", "eslint-plugin-jsx-a11y": "6.4.1",
@ -101,10 +104,11 @@
"eslint-plugin-react-hooks": "4.2.0", "eslint-plugin-react-hooks": "4.2.0",
"jest": "26.6.3", "jest": "26.6.3",
"prettier": "2.2.1", "prettier": "2.2.1",
"ts-jest": "26.4.4", "prisma": "2.18.0",
"ts-jest": "26.5.1",
"ts-node": "9.1.1", "ts-node": "9.1.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"typescript": "4.1.3", "typescript": "4.1.5",
"wasm-loader": "1.3.0" "wasm-loader": "1.3.0"
} }
} }

View File

@ -2,8 +2,9 @@
"version": "v1", "version": "v1",
"packages": { "packages": {
"@fbe/editor": { "@fbe/editor": {
"signature": "fcd9eadabd31e2c2000a5ea3e4b000f8", "signature": "4394b199f59f3db8ce4aa6b1c9801472",
"file": true "file": true,
"replaced": "./fbe-editor-v1.0.0.tgz"
} }
} }
} }

3883
yarn.lock

File diff suppressed because it is too large Load Diff