mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2024-11-20 18:15:37 +02:00
blueprint managent updates and a lot more
This commit is contained in:
parent
00358228dc
commit
512b66300d
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@ -1,6 +1,3 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||
}
|
||||
|
20
apps/blueprint-image-function/function.package.json
Normal file
20
apps/blueprint-image-function/function.package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "blueprint-image-function",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"postinstall": "yarn prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"puppeteer": "7.1.0",
|
||||
"tslib": "2.1.0",
|
||||
"sharp": "0.27.2",
|
||||
"prisma": "2.18.0",
|
||||
"@prisma/client": "2.18.0",
|
||||
"@google-cloud/pubsub": "2.9.0",
|
||||
"@google-cloud/secret-manager": "3.4.0",
|
||||
"@google-cloud/storage": "5.7.4",
|
||||
"cookie": "0.4.1",
|
||||
"pako": "1.0.11",
|
||||
"bcrypt": "5.0.0"
|
||||
}
|
||||
}
|
136
apps/blueprint-image-function/src/function-handler.ts
Normal file
136
apps/blueprint-image-function/src/function-handler.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import {
|
||||
getBlueprintById,
|
||||
getBlueprintStringByHash,
|
||||
hasBlueprintImage,
|
||||
saveBlueprintImage,
|
||||
init,
|
||||
} from "@factorio-sites/database";
|
||||
import { jsonReplaceErrors } from "@factorio-sites/node-utils";
|
||||
import { optimise } from "./image-optimiser";
|
||||
import { renderImage } from "./image-renderer";
|
||||
|
||||
// {"blueprintId":"ee9b98eb-313a-4401-8aee-d6e970b76aad"}
|
||||
// ^ image_hash: 6f78c0a93c20fe99076e8defe4e396923f42753b
|
||||
|
||||
/** express req interface for http-triggered function */
|
||||
interface Req {
|
||||
query: Record<string, string>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
body: any;
|
||||
}
|
||||
/** express res interface for http-triggered function */
|
||||
interface Res {
|
||||
status(status: number): Res;
|
||||
send(body: string): void;
|
||||
}
|
||||
/** message body for pubsub triggered function */
|
||||
type Message = Record<string, any>;
|
||||
/** context for pubsub triggered function */
|
||||
interface Context {
|
||||
eventId: string;
|
||||
eventType: string;
|
||||
timestamp: string;
|
||||
resource: { service: string; name: string };
|
||||
}
|
||||
type Handler = (req: Req, res: Res) => void;
|
||||
type PubSubHandler = (
|
||||
message: Message,
|
||||
context: Context,
|
||||
callback: (error?: string) => void
|
||||
) => void;
|
||||
|
||||
export const functionHttpHandler: Handler = async (req, res) => {
|
||||
try {
|
||||
const blueprintId = req.body.blueprintId;
|
||||
|
||||
if (!blueprintId) {
|
||||
return res.status(400).send("No blueprintId in body");
|
||||
}
|
||||
|
||||
console.log("generating image for", blueprintId);
|
||||
|
||||
await init();
|
||||
const blueprint = await getBlueprintById(blueprintId);
|
||||
|
||||
if (!blueprint) {
|
||||
return res.status(400).send("Blueprint not found");
|
||||
}
|
||||
|
||||
if (await hasBlueprintImage(blueprint.image_hash, "300")) {
|
||||
return res.status(200).send("Image already exists");
|
||||
}
|
||||
|
||||
const blueprint_string = await getBlueprintStringByHash(blueprint.blueprint_hash);
|
||||
if (!blueprint_string) {
|
||||
return res.status(400).send("Blueprint string not found");
|
||||
}
|
||||
|
||||
const image = await renderImage(blueprint_string);
|
||||
console.log("Image generated");
|
||||
|
||||
// Make thumbnail, max size 300px
|
||||
const min_image = await optimise(image, 300);
|
||||
|
||||
await saveBlueprintImage(blueprint.image_hash, min_image, "300");
|
||||
|
||||
res.status(200).send("Done");
|
||||
} catch (reason) {
|
||||
res.status(500).send(`Error rendering image ${reason.stack || reason}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const functionPubSubHandler: PubSubHandler = async (message, _context, callback) => {
|
||||
const error = (message: string, data: Record<string, unknown> = {}) => {
|
||||
console.error(JSON.stringify({ message, ...data }, jsonReplaceErrors));
|
||||
callback(message);
|
||||
};
|
||||
const log = (message: string, data: Record<string, unknown> = {}) => {
|
||||
console.log(JSON.stringify({ message, ...data }));
|
||||
};
|
||||
|
||||
try {
|
||||
const data = message.data
|
||||
? JSON.parse(Buffer.from(message.data, "base64").toString())
|
||||
: message;
|
||||
|
||||
const blueprintId = data.blueprintId;
|
||||
|
||||
if (!blueprintId) {
|
||||
return error("No blueprintId in body");
|
||||
}
|
||||
|
||||
log(`generating image for ${blueprintId}`);
|
||||
|
||||
await init();
|
||||
const blueprint = await getBlueprintById(blueprintId);
|
||||
|
||||
if (!blueprint) {
|
||||
return error("Blueprint not found");
|
||||
}
|
||||
|
||||
if (await hasBlueprintImage(blueprint.image_hash, "300")) {
|
||||
log("Image already exists");
|
||||
return callback();
|
||||
}
|
||||
|
||||
const blueprint_string = await getBlueprintStringByHash(blueprint.blueprint_hash);
|
||||
if (!blueprint_string) {
|
||||
return error("Blueprint string not found");
|
||||
}
|
||||
|
||||
const image = await renderImage(blueprint_string);
|
||||
log("Image generated");
|
||||
|
||||
// Make thumbnail, max size 300px
|
||||
const min_image = await optimise(image, 300);
|
||||
|
||||
await saveBlueprintImage(blueprint.image_hash, min_image, "300");
|
||||
log(`Saved image with image hash ${blueprint.image_hash}`);
|
||||
|
||||
await saveBlueprintImage(blueprint.image_hash, image, "original");
|
||||
|
||||
callback();
|
||||
} catch (reason) {
|
||||
error(`Error rendering image ${reason}`, { error: reason });
|
||||
}
|
||||
};
|
@ -1,50 +1,27 @@
|
||||
import * as sharp from "sharp";
|
||||
|
||||
const RESIZE_ENABLED = false;
|
||||
|
||||
// const calculateImageSizeMod = (pixels: number) =>
|
||||
// Math.min(Math.max((-pixels + 500) / 20500 + 1, 0.3), 1);
|
||||
// const calculateImageSizeMod = (pixels: number) =>
|
||||
// Math.min(Math.max((-pixels + 3000) / 33000 + 1, 0.3), 1);
|
||||
// const mod = calculateImageSizeMod(Math.max(meta.width, meta.height));
|
||||
|
||||
const calculateImageSizeMod = (pixels: number) =>
|
||||
Math.min(Math.max((-pixels + 3000) / 33000 + 1, 0.3), 1);
|
||||
export const optimise = async (image: Buffer, max_dimention = 5000): Promise<Buffer> => {
|
||||
const sharp_image = await sharp(image)
|
||||
.resize({
|
||||
width: max_dimention,
|
||||
height: max_dimention,
|
||||
fit: sharp.fit.inside,
|
||||
})
|
||||
.webp({ lossless: true })
|
||||
.toBuffer();
|
||||
|
||||
export const optimise = async (image: Buffer): Promise<Buffer> => {
|
||||
let sharp_image = sharp(image);
|
||||
if (RESIZE_ENABLED) {
|
||||
const MAX_IMAGE_DIMENTION = 5000;
|
||||
sharp_image = await sharp_image
|
||||
.metadata()
|
||||
.then((meta) => {
|
||||
if (
|
||||
meta.width &&
|
||||
meta.height &&
|
||||
(meta.width > MAX_IMAGE_DIMENTION || meta.height > MAX_IMAGE_DIMENTION)
|
||||
) {
|
||||
const mod = calculateImageSizeMod(Math.max(meta.width, meta.height));
|
||||
console.log({
|
||||
width: meta.width,
|
||||
height: meta.height,
|
||||
mod,
|
||||
size_mb: image.byteLength / 1024_000,
|
||||
});
|
||||
return sharp_image.resize({
|
||||
width: Math.round(meta.width * mod),
|
||||
height: Math.round(meta.height * mod),
|
||||
});
|
||||
}
|
||||
return sharp_image;
|
||||
})
|
||||
.then((image) => image.webp({ lossless: true }));
|
||||
} else {
|
||||
sharp_image = sharp_image.webp({ lossless: true });
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
input_size: `${Math.round(image.byteLength / 1024) / 1000}mb`,
|
||||
output_size: `${Math.round(sharp_image.byteLength / 1024) / 1000}mb`,
|
||||
})
|
||||
);
|
||||
|
||||
const min_image = await sharp_image.toBuffer();
|
||||
|
||||
console.log({
|
||||
input_size_mb: image.byteLength / 1024_000,
|
||||
output_size_mb: min_image.byteLength / 1024_000,
|
||||
});
|
||||
|
||||
return min_image;
|
||||
return sharp_image;
|
||||
};
|
||||
|
@ -4,11 +4,11 @@ import * as Puppeteer from "puppeteer";
|
||||
let BROWSER: Puppeteer.Browser;
|
||||
let PAGE: Puppeteer.Page;
|
||||
|
||||
async function getPage() {
|
||||
async function getPage(headless: boolean) {
|
||||
if (PAGE) return PAGE;
|
||||
|
||||
const _browser = await Puppeteer.launch({
|
||||
headless: false,
|
||||
headless,
|
||||
args: ["--no-sandbox"],
|
||||
});
|
||||
|
||||
@ -24,9 +24,9 @@ async function getPage() {
|
||||
return _page;
|
||||
}
|
||||
|
||||
export async function renderImage(blueprint_string: string) {
|
||||
export async function renderImage(blueprint_string: string, options?: { headless: boolean }) {
|
||||
const tl = timeLogger("localFbeRenderer");
|
||||
const page = await getPage();
|
||||
const page = await getPage(options?.headless ?? true);
|
||||
|
||||
tl("Page loaded");
|
||||
|
||||
|
@ -1,77 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { promisify } from "util";
|
||||
import {
|
||||
hasBlueprintImage,
|
||||
getBlueprintByImageHash,
|
||||
saveBlueprintImage,
|
||||
} from "@factorio-sites/database";
|
||||
import { optimise } from "./image-optimiser";
|
||||
|
||||
if (!process.env.DIR) throw Error("no 'DIR' environment variable");
|
||||
|
||||
const fsReadFile = promisify(fs.readFile);
|
||||
const fsUnlink = promisify(fs.unlink);
|
||||
const FILE_DIR = path.normalize(process.env.DIR);
|
||||
|
||||
const uploadFile = async (image_path: string) => {
|
||||
const image_hash = path.basename(image_path, ".png");
|
||||
|
||||
if (!(await getBlueprintByImageHash(image_hash))) {
|
||||
console.log(`Image ${image_hash} has no database record, skipping...`);
|
||||
return;
|
||||
}
|
||||
if (image_hash.includes("(")) {
|
||||
console.log(`Image ${image_hash} is a duplicate, deleting...`);
|
||||
await fsUnlink(image_path);
|
||||
return;
|
||||
}
|
||||
if (await hasBlueprintImage(image_hash)) {
|
||||
console.log(`Image ${image_hash} already exists, deleting...`);
|
||||
await fsUnlink(image_path);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Processing ${image_hash}...`);
|
||||
const image = await fsReadFile(image_path);
|
||||
const min_image = await optimise(image);
|
||||
|
||||
console.log(`Image ${image_hash} processed, uploading...`);
|
||||
await saveBlueprintImage(image_hash, min_image);
|
||||
await fsUnlink(image_path);
|
||||
};
|
||||
|
||||
export async function uploadLocalFiles() {
|
||||
// console.log(`Reading directory`, FILE_DIR);
|
||||
// const files = await fsReaddir(FILE_DIR);
|
||||
// for (let i = 0; i < files.length; i++) {
|
||||
// if (fs.statSync(path.join(FILE_DIR, files[i])).isDirectory()) continue;
|
||||
// await uploadFile(path.join(FILE_DIR, files[i]));
|
||||
// }
|
||||
console.log(`Watching directory`, FILE_DIR);
|
||||
const work_done_buffeer: string[] = [];
|
||||
const work_buffer: string[] = [];
|
||||
fs.watch(FILE_DIR, (type, file) => {
|
||||
if (type === "change" && file && file.endsWith(".png")) {
|
||||
const file_path = path.join(FILE_DIR, file);
|
||||
if (work_buffer.includes(file_path) || work_done_buffeer.includes(file_path)) {
|
||||
return;
|
||||
}
|
||||
work_buffer.push(file_path);
|
||||
}
|
||||
});
|
||||
|
||||
let working = false;
|
||||
const doWork = async () => {
|
||||
if (working || !work_buffer.length) return setTimeout(doWork, 1000);
|
||||
working = true;
|
||||
const file_path = work_buffer.shift();
|
||||
if (file_path) {
|
||||
await uploadFile(file_path);
|
||||
work_done_buffeer.push(file_path);
|
||||
}
|
||||
working = false;
|
||||
doWork();
|
||||
};
|
||||
doWork();
|
||||
}
|
40
apps/blueprint-image-function/src/local-test.ts
Normal file
40
apps/blueprint-image-function/src/local-test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
getBlueprintById,
|
||||
getBlueprintStringByHash,
|
||||
hasBlueprintImage,
|
||||
init,
|
||||
saveBlueprintImage,
|
||||
} from "@factorio-sites/database";
|
||||
import { optimise } from "./image-optimiser";
|
||||
import { renderImage } from "./image-renderer";
|
||||
|
||||
export async function local_test(blueprint_id: string) {
|
||||
await init();
|
||||
const blueprint = await getBlueprintById(blueprint_id);
|
||||
|
||||
if (!blueprint) {
|
||||
return console.log("Blueprint not found");
|
||||
}
|
||||
|
||||
if (await hasBlueprintImage(blueprint.image_hash, "300")) {
|
||||
// return console.log("Image already exists");
|
||||
}
|
||||
|
||||
const blueprint_string = await getBlueprintStringByHash(blueprint.blueprint_hash);
|
||||
if (!blueprint_string) {
|
||||
return console.log("Blueprint string not found");
|
||||
}
|
||||
|
||||
const image = await renderImage(blueprint_string, { headless: false });
|
||||
console.log("Image generated");
|
||||
|
||||
// Make thumbnail, max size 300px
|
||||
const min_image = await optimise(image, 300);
|
||||
|
||||
await saveBlueprintImage(blueprint.image_hash, min_image, "300");
|
||||
console.log(`Saved image with image hash ${blueprint.image_hash}`);
|
||||
|
||||
await saveBlueprintImage(blueprint.image_hash, image, "original");
|
||||
|
||||
console.log("done");
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import { subscribeToPubSub } from "./pubsub-render";
|
||||
import { functionHttpHandler, functionPubSubHandler } from "./function-handler";
|
||||
// import { local_test } from "./local-test";
|
||||
|
||||
subscribeToPubSub().catch((reason) => console.error("Fatal error:", reason));
|
||||
// uploadLocalFiles().catch((reason) => console.error("Fatal error:", reason));
|
||||
// import { subscribeToPubSub } from "./pubsub-render";
|
||||
// subscribeToPubSub().catch((reason) => console.error("Fatal error:", reason));
|
||||
// rePublishAllBlueprints().catch((reason) => console.error("Fatal error:", reason));
|
||||
|
||||
// image hash = a99525f97c26c7242ecdd96679043b1a5e65dd0c
|
||||
// SELECT * FROM BlueprintBook WHERE blueprint_ids CONTAINS Key(Blueprint, 4532736400293888)
|
||||
// bp = Key(Blueprint, 4532736400293888)
|
||||
// book = Key(BlueprintBook, 5034207050989568)
|
||||
// page = 6225886932107264
|
||||
exports.renderImageHttp = functionHttpHandler;
|
||||
exports.renderImagePubSub = functionPubSubHandler;
|
||||
|
||||
// local_test("8737437e-f15b-459c-8c1d-d0074f3a89ca");
|
||||
|
@ -35,11 +35,12 @@ export async function subscribeToPubSub() {
|
||||
};
|
||||
|
||||
try {
|
||||
const data = JSON.parse(message.data.toString());
|
||||
if (!data.blueprintId) return ack("blueprintId not found in message body", false);
|
||||
if (!message.attributes.blueprintId) {
|
||||
return ack("blueprintId not found in message body", false);
|
||||
}
|
||||
console.log("------------------------------------------------");
|
||||
console.log("[pubsub] generating image for", data.blueprintId);
|
||||
const blueprint = await getBlueprintById(data.blueprintId);
|
||||
console.log("[pubsub] generating image for", message.attributes.blueprintId);
|
||||
const blueprint = await getBlueprintById(message.attributes.blueprintId);
|
||||
if (!blueprint) return ack("Blueprint not found", false);
|
||||
|
||||
if (await hasBlueprintImage(blueprint.image_hash)) {
|
||||
@ -58,7 +59,7 @@ export async function subscribeToPubSub() {
|
||||
|
||||
return ack("[pubsub] image saved", true);
|
||||
} catch (reason) {
|
||||
return ack(`[pubsub:error] ${reason}`, false);
|
||||
return ack(`[pubsub:error] ${reason.stack || reason}`, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { getBlueprintImageRequestTopic, getPaginatedBlueprints } from "@factorio-sites/database";
|
||||
// import { getBlueprintImageRequestTopic, getPaginatedBlueprints } from "@factorio-sites/database";
|
||||
|
||||
export async function rePublishAllBlueprints() {
|
||||
const topic = getBlueprintImageRequestTopic();
|
||||
const fetchPage = async (page = 1) => {
|
||||
const blueprints = await getPaginatedBlueprints(page);
|
||||
if (blueprints.length === 0) {
|
||||
return console.log("No more blueprints found");
|
||||
}
|
||||
console.log(`Publishing page ${page} with ${blueprints.length} blueprints`);
|
||||
// export async function rePublishAllBlueprints() {
|
||||
// const topic = getBlueprintImageRequestTopic();
|
||||
// const fetchPage = async (page = 1) => {
|
||||
// const blueprints = await getPaginatedBlueprints(page);
|
||||
// if (blueprints.length === 0) {
|
||||
// return console.log("No more blueprints found");
|
||||
// }
|
||||
// console.log(`Publishing page ${page} with ${blueprints.length} blueprints`);
|
||||
|
||||
await Promise.all(
|
||||
blueprints.map((blueprint) => {
|
||||
return topic.publishJSON({ blueprintId: blueprint.id });
|
||||
})
|
||||
);
|
||||
fetchPage(page + 1);
|
||||
};
|
||||
await fetchPage();
|
||||
}
|
||||
// await Promise.all(
|
||||
// blueprints.map((blueprint) => {
|
||||
// return topic.publishJSON({ blueprintId: blueprint.id });
|
||||
// })
|
||||
// );
|
||||
// fetchPage(page + 1);
|
||||
// };
|
||||
// await fetchPage();
|
||||
// }
|
||||
|
||||
export {};
|
||||
|
@ -2,6 +2,7 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"target": "ES2019",
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["**/*.spec.ts"],
|
||||
|
@ -1,14 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
domains: ["storage.googleapis.com"],
|
||||
},
|
||||
webpack(config, options) {
|
||||
const { dev, isServer } = options;
|
||||
|
||||
// Do not run type checking twice:
|
||||
if (dev && isServer) {
|
||||
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
config.plugins.push(new ForkTsCheckerWebpackPlugin());
|
||||
}
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
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");
|
@ -0,0 +1,171 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "enum_user_role" AS ENUM ('user', 'moderator', 'admin');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "blueprint" (
|
||||
"id" UUID NOT NULL,
|
||||
"label" VARCHAR(255),
|
||||
"description" TEXT,
|
||||
"game_version" VARCHAR(255),
|
||||
"blueprint_hash" VARCHAR(40) NOT NULL,
|
||||
"image_hash" VARCHAR(40) NOT NULL,
|
||||
"image_version" INTEGER NOT NULL DEFAULT 1,
|
||||
"tags" VARCHAR(255)[],
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "blueprint_book" (
|
||||
"id" UUID NOT NULL,
|
||||
"label" VARCHAR(255),
|
||||
"description" TEXT,
|
||||
"child_tree" JSON NOT NULL,
|
||||
"blueprint_hash" VARCHAR(40) NOT NULL,
|
||||
"is_modded" BOOLEAN NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "blueprint_page" (
|
||||
"id" UUID NOT NULL,
|
||||
"user_id" UUID,
|
||||
"blueprint_id" UUID,
|
||||
"blueprint_book_id" UUID,
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"description_markdown" TEXT,
|
||||
"tags" VARCHAR(255)[],
|
||||
"factorioprints_id" VARCHAR(255),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"id" UUID NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"session_token" UUID NOT NULL,
|
||||
"useragent" VARCHAR(255) NOT NULL,
|
||||
"ip" VARCHAR(255) NOT NULL,
|
||||
"last_used" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user" (
|
||||
"id" UUID NOT NULL,
|
||||
"email" VARCHAR(255),
|
||||
"username" VARCHAR(255) NOT NULL,
|
||||
"role" "enum_user_role" DEFAULT E'user',
|
||||
"steam_id" VARCHAR(255),
|
||||
"password" VARCHAR(255),
|
||||
"password_reset_token" UUID,
|
||||
"password_reset_at" TIMESTAMP(3),
|
||||
"last_password_change" TIMESTAMP(3),
|
||||
"last_login_at" TIMESTAMP(3),
|
||||
"last_login_ip" VARCHAR(255) NOT NULL,
|
||||
"email_validated" BOOLEAN NOT NULL DEFAULT false,
|
||||
"email_validate_token" UUID,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_favorites" (
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"blueprint_page_id" UUID NOT NULL,
|
||||
|
||||
PRIMARY KEY ("user_id","blueprint_page_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_blueprintToblueprint_book" (
|
||||
"A" UUID NOT NULL,
|
||||
"B" UUID NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_blueprint_books" (
|
||||
"A" UUID NOT NULL,
|
||||
"B" UUID NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "blueprint.blueprint_hash_unique" ON "blueprint"("blueprint_hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "blueprint_book.blueprint_hash_unique" ON "blueprint_book"("blueprint_hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "blueprint_page.blueprint_id_unique" ON "blueprint_page"("blueprint_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "blueprint_page.blueprint_book_id_unique" ON "blueprint_page"("blueprint_book_id");
|
||||
|
||||
-- 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");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user.email_unique" ON "user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user.username_unique" ON "user"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user.steam_id_unique" ON "user"("steam_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_blueprintToblueprint_book_AB_unique" ON "_blueprintToblueprint_book"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_blueprintToblueprint_book_B_index" ON "_blueprintToblueprint_book"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_blueprint_books_AB_unique" ON "_blueprint_books"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_blueprint_books_B_index" ON "_blueprint_books"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "blueprint_page" ADD FOREIGN KEY ("blueprint_book_id") REFERENCES "blueprint_book"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "blueprint_page" ADD FOREIGN KEY ("blueprint_id") REFERENCES "blueprint"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_favorites" ADD FOREIGN KEY ("blueprint_page_id") REFERENCES "blueprint_page"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_favorites" ADD FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_blueprintToblueprint_book" ADD FOREIGN KEY ("A") REFERENCES "blueprint"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_blueprintToblueprint_book" ADD FOREIGN KEY ("B") REFERENCES "blueprint_book"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_blueprint_books" ADD FOREIGN KEY ("A") REFERENCES "blueprint_book"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_blueprint_books" ADD FOREIGN KEY ("B") REFERENCES "blueprint_book"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `image_hash` to the `blueprint_page` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "blueprint_page" ADD COLUMN "image_hash" VARCHAR(40) NOT NULL;
|
@ -14,43 +14,33 @@ enum enum_user_role {
|
||||
}
|
||||
|
||||
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?
|
||||
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?
|
||||
blueprint_books blueprint_book[]
|
||||
}
|
||||
|
||||
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])
|
||||
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?
|
||||
blueprints blueprint[]
|
||||
blueprint_books blueprint_book[] @relation("blueprint_books")
|
||||
blueprint_books_relation blueprint_book[] @relation("blueprint_books")
|
||||
}
|
||||
|
||||
model blueprint_page {
|
||||
@ -61,6 +51,7 @@ model blueprint_page {
|
||||
title String @db.VarChar(255)
|
||||
description_markdown String?
|
||||
tags String[] @db.VarChar(255)
|
||||
image_hash String @db.VarChar(40)
|
||||
factorioprints_id String? @unique @db.VarChar(255)
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
@ -1,21 +1,22 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "1.1.2",
|
||||
"@emotion/react": "11.1.4",
|
||||
"@chakra-ui/react": "1.3.3",
|
||||
"@emotion/react": "11.1.5",
|
||||
"@emotion/server": "11.0.0",
|
||||
"@emotion/styled": "11.0.0",
|
||||
"@fbe/editor": "./fbe-editor-v1.0.0.tgz",
|
||||
"@google-cloud/pubsub": "2.7.0",
|
||||
"@google-cloud/secret-manager": "3.2.3",
|
||||
"@google-cloud/storage": "5.7.0",
|
||||
"@hookstate/core": "3.0.3",
|
||||
"@emotion/styled": "11.1.5",
|
||||
"@fbe/editor": "file:.yalc/@fbe/editor",
|
||||
"@google-cloud/pubsub": "2.9.0",
|
||||
"@google-cloud/secret-manager": "3.4.0",
|
||||
"@google-cloud/storage": "5.7.4",
|
||||
"@hookstate/core": "3.0.5",
|
||||
"@prisma/client": "2.18.0",
|
||||
"bbcode-to-react": "0.2.9",
|
||||
"bcrypt": "5.0.0",
|
||||
"cookie": "0.4.1",
|
||||
"document-register-element": "1.14.10",
|
||||
"formik": "2.2.6",
|
||||
"framer-motion": "3.1.4",
|
||||
"next": "10.0.5",
|
||||
"framer-motion": "3.3.0",
|
||||
"next": "10.0.7",
|
||||
"nprogress": "0.2.0",
|
||||
"openid": "2.0.7",
|
||||
"pako": "1.0.11",
|
||||
@ -23,9 +24,10 @@
|
||||
"phin": "3.5.1",
|
||||
"react": "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-markdown": "5.0.3",
|
||||
"sequelize": "6.3.5"
|
||||
"react-multi-select-component": "3.1.3",
|
||||
"fork-ts-checker-webpack-plugin": "6.1.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,45 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { css } from "@emotion/react";
|
||||
import { BlueprintPage } from "@factorio-sites/database";
|
||||
import { BlueprintPage } from "@factorio-sites/types";
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
import { MdFavorite } from "react-icons/md";
|
||||
import { useState } from "react";
|
||||
|
||||
const linkStyles = css`
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
margin: 5px 10px 5px 0;
|
||||
background: #353535;
|
||||
|
||||
.block {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.image {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #ccc;
|
||||
background: #4c4c4c;
|
||||
}
|
||||
|
||||
&.tile {
|
||||
.block {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -26,26 +51,61 @@ const formatDate = (datenum: number) => {
|
||||
interface BlueprintLinkProps {
|
||||
blueprint: BlueprintPage;
|
||||
editLink?: boolean;
|
||||
type: "tile" | "row";
|
||||
}
|
||||
|
||||
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>
|
||||
export const BlueprintLink: React.FC<BlueprintLinkProps> = ({
|
||||
blueprint,
|
||||
editLink,
|
||||
type = "tile",
|
||||
}) => {
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const onImageError = (error: unknown) => {
|
||||
console.log(error);
|
||||
setImageError(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div css={linkStyles} className={type}>
|
||||
<Link
|
||||
href={editLink ? `/user/blueprint/${blueprint.id}` : `/blueprint/${blueprint.id}`}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
<Box className="block">
|
||||
{type === "tile" && (
|
||||
<div className="image">
|
||||
{imageError ? (
|
||||
<div>
|
||||
Looks like this image can\t load. <button>Try generating it again</button>
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
loader={({ src }) => src}
|
||||
src={`https://storage.googleapis.com/blueprint-images/300/${blueprint.image_hash}.webp`}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
alt={blueprint.title}
|
||||
onError={onImageError}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Box className="details">
|
||||
<Text css={{ display: "flex", alignItems: "center", marginRight: "1rem" }}>
|
||||
<MdFavorite css={{ marginRight: "5px" }} />
|
||||
{blueprint.favorite_count || "0"}
|
||||
</Text>
|
||||
<Text>{blueprint.title}</Text>
|
||||
</Box>
|
||||
{type === "row" && (
|
||||
<Box>
|
||||
<Text>{formatDate(blueprint.updated_at)}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { css } from "@emotion/react";
|
||||
import Link from "next/link";
|
||||
import BBCode from "bbcode-to-react";
|
||||
import { ChildTree } from "@factorio-sites/database";
|
||||
import { ChildTree } from "@factorio-sites/types";
|
||||
|
||||
const componentStyles = css`
|
||||
.blueprint,
|
||||
|
@ -70,7 +70,7 @@ export const ImageEditor: React.FC<{ string: string }> = ({ string }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} style={{ width: "100%", height: "auto" }} />
|
||||
<canvas id="pbe" ref={canvasRef} style={{ width: "100%", height: "auto" }} />
|
||||
{/* <img src={image} alt="blueprint" style={{ width: "500px" }}></img> */}
|
||||
</div>
|
||||
);
|
||||
|
@ -2,8 +2,9 @@ import {
|
||||
createBlueprint,
|
||||
createBlueprintPage,
|
||||
createBlueprintBook,
|
||||
getBlueprintById,
|
||||
} from "@factorio-sites/database";
|
||||
import { parseBlueprintString } from "@factorio-sites/node-utils";
|
||||
import { getFirstBlueprintFromChildTree, parseBlueprintString } from "@factorio-sites/node-utils";
|
||||
import { parseDatabaseError } from "../../../utils/api.utils";
|
||||
import { apiHandler } from "../../../utils/api-handler";
|
||||
|
||||
@ -37,12 +38,22 @@ const handler = apiHandler(async (req, res, { session }) => {
|
||||
};
|
||||
|
||||
if (parsed?.data.blueprint) {
|
||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, info);
|
||||
const page = await createBlueprintPage("blueprint", insertedId, info);
|
||||
const blueprint = await createBlueprint(parsed.data.blueprint, info);
|
||||
const page = await createBlueprintPage("blueprint", blueprint.id, {
|
||||
...info,
|
||||
image_hash: blueprint.image_hash,
|
||||
});
|
||||
return res.status(201).json({ success: true, id: page.id });
|
||||
} else if (parsed?.data.blueprint_book) {
|
||||
const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||
const page = await createBlueprintPage("blueprint_book", insertedId, info);
|
||||
const result = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||
const firstBlueprintId = getFirstBlueprintFromChildTree(result.child_tree);
|
||||
const firstBlueprint = await getBlueprintById(firstBlueprintId);
|
||||
if (!firstBlueprint) throw Error("Failed to find blueprint");
|
||||
const page = await createBlueprintPage("blueprint_book", result.id, {
|
||||
...info,
|
||||
image_hash: firstBlueprint.image_hash,
|
||||
firstBlueprintId,
|
||||
});
|
||||
return res.status(201).json({ success: true, id: page.id });
|
||||
}
|
||||
} catch (reason) {
|
||||
|
@ -34,12 +34,12 @@ const handler = apiHandler(async (req, res, { session }) => {
|
||||
console.log(info);
|
||||
|
||||
if (parsed?.data.blueprint) {
|
||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, info);
|
||||
const page = await editBlueprintPage(id, "blueprint", insertedId, info);
|
||||
const result = await createBlueprint(parsed.data.blueprint, info);
|
||||
const page = await editBlueprintPage(id, "blueprint", result.id, info);
|
||||
return res.status(200).json({ success: true, id: page.id });
|
||||
} else if (parsed?.data.blueprint_book) {
|
||||
const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||
const page = await editBlueprintPage(id, "blueprint_book", insertedId, info);
|
||||
const result = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||
const page = await editBlueprintPage(id, "blueprint_book", result.id, info);
|
||||
return res.status(200).json({ success: true, id: page.id });
|
||||
}
|
||||
} catch (reason) {
|
||||
|
36
apps/blueprints/src/pages/api/blueprint/thumbnail.ts
Normal file
36
apps/blueprints/src/pages/api/blueprint/thumbnail.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { NextApiHandler } from "next";
|
||||
import {
|
||||
getBlueprintBookById,
|
||||
getBlueprintById,
|
||||
getBlueprintImageRequestTopic,
|
||||
getBlueprintPageById,
|
||||
} from "@factorio-sites/database";
|
||||
import { getFirstBlueprintFromChildTree } from "@factorio-sites/node-utils";
|
||||
import { apiHandler, ApiError } from "../../../utils/api-handler";
|
||||
|
||||
const handler: NextApiHandler = apiHandler(async (req, res) => {
|
||||
if (!req.query.id) throw new ApiError(400, "No id in query");
|
||||
const blueprintPage = await getBlueprintPageById(req.query.id as string);
|
||||
if (!blueprintPage) return res.status(404).json({ error: "Blueprint page not found" });
|
||||
|
||||
const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||
|
||||
if (blueprintPage.blueprint_id) {
|
||||
blueprintImageRequestTopic.publishJSON({
|
||||
blueprintId: blueprintPage.blueprint_id,
|
||||
});
|
||||
return res.json({ blueprint_id: blueprintPage.blueprint_id });
|
||||
} else if (blueprintPage.blueprint_book_id) {
|
||||
const blueprintBook = await getBlueprintBookById(blueprintPage.blueprint_book_id);
|
||||
if (!blueprintBook) throw new ApiError(500, "Failed to find blueprint book");
|
||||
const firstBlueprintId = getFirstBlueprintFromChildTree(blueprintBook.child_tree);
|
||||
const firstBlueprint = await getBlueprintById(firstBlueprintId);
|
||||
if (!firstBlueprint) throw new ApiError(500, "Failed to find blueprint");
|
||||
blueprintImageRequestTopic.publishJSON({
|
||||
blueprintId: firstBlueprintId,
|
||||
});
|
||||
return res.json({ blueprint_id: firstBlueprintId });
|
||||
}
|
||||
});
|
||||
|
||||
export default handler;
|
@ -5,8 +5,10 @@ import {
|
||||
getBlueprintById,
|
||||
getBlueprintStringByHash,
|
||||
hasBlueprintImage,
|
||||
Blueprint,
|
||||
} from "@factorio-sites/database";
|
||||
import { Blueprint } from "@factorio-sites/types";
|
||||
|
||||
const DISABLED = true;
|
||||
|
||||
const getOneMessage = async (): Promise<Blueprint> => {
|
||||
const topic = getBlueprintImageRequestTopic();
|
||||
@ -43,6 +45,8 @@ const getOneMessage = async (): Promise<Blueprint> => {
|
||||
};
|
||||
|
||||
const handler: NextApiHandler = async (req, res) => {
|
||||
if (DISABLED) return res.status(400).send("Method not availablee");
|
||||
|
||||
// Allow the url to be used in the blueprint editor
|
||||
if (
|
||||
req.headers.origin &&
|
||||
|
@ -33,15 +33,6 @@ const handler: NextApiHandler = async (req, res) => {
|
||||
if (user) {
|
||||
const session = await createSession(user, useragent, ip);
|
||||
setUserToken(res, session.session_token);
|
||||
|
||||
// Redirect from in browser to let the cookie be stored
|
||||
// If not, the first render still happens as guest
|
||||
res.setHeader("content-type", "text/html");
|
||||
return res.status(200).end(`
|
||||
<html><head>
|
||||
<meta http-equiv="refresh" content="0;url=${process.env.BASE_URL}" />
|
||||
</head></html>
|
||||
`);
|
||||
}
|
||||
// First time logging in, make new user
|
||||
else {
|
||||
@ -54,9 +45,16 @@ const handler: NextApiHandler = async (req, res) => {
|
||||
|
||||
const session = await createSession(user, useragent, ip);
|
||||
setUserToken(res, session.session_token);
|
||||
res.setHeader("Location", `${process.env.BASE_URL}`);
|
||||
return res.status(302).end();
|
||||
}
|
||||
|
||||
// Redirect from in browser to let the cookie be stored
|
||||
// If not, the first render still happens as guest
|
||||
res.setHeader("content-type", "text/html");
|
||||
return res.status(200).end(`
|
||||
<html><head>
|
||||
<meta http-equiv="refresh" content="0;url=${process.env.BASE_URL}" />
|
||||
</head></html>
|
||||
`);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
@ -3,14 +3,12 @@ import { NextPage } from "next";
|
||||
import BBCode from "bbcode-to-react";
|
||||
import { Button, Grid, Image } from "@chakra-ui/react";
|
||||
import {
|
||||
BlueprintBook,
|
||||
Blueprint,
|
||||
BlueprintPage,
|
||||
getBlueprintBookById,
|
||||
getBlueprintById,
|
||||
getBlueprintPageById,
|
||||
isBlueprintPageUserFavorite,
|
||||
} from "@factorio-sites/database";
|
||||
import { BlueprintBook, Blueprint, BlueprintPage } from "@factorio-sites/types";
|
||||
import { BlueprintStringData, timeLogger } from "@factorio-sites/common-utils";
|
||||
import { chakraResponsive, parseBlueprintStringClient } from "@factorio-sites/web-utils";
|
||||
import { Panel } from "../../components/Panel";
|
||||
@ -24,11 +22,10 @@ import styled from "@emotion/styled";
|
||||
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
|
||||
|
||||
type Selected =
|
||||
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash"> }
|
||||
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash"> };
|
||||
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash" | "label"> }
|
||||
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash" | "label"> };
|
||||
|
||||
interface IndexProps {
|
||||
image_exists: boolean;
|
||||
selected: Selected;
|
||||
blueprint: Blueprint | null;
|
||||
blueprint_book: BlueprintBook | null;
|
||||
@ -46,7 +43,6 @@ const StyledTable = styled.table`
|
||||
`;
|
||||
|
||||
export const Index: NextPage<IndexProps> = ({
|
||||
image_exists,
|
||||
selected,
|
||||
blueprint,
|
||||
blueprint_book,
|
||||
@ -90,7 +86,6 @@ export const Index: NextPage<IndexProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
console.log({
|
||||
image_exists,
|
||||
selected,
|
||||
blueprint,
|
||||
blueprint_book,
|
||||
@ -149,24 +144,20 @@ export const Index: NextPage<IndexProps> = ({
|
||||
gridColumn="1"
|
||||
>
|
||||
{blueprint_book ? (
|
||||
<>
|
||||
<div>This string contains a blueprint book </div>
|
||||
<br />
|
||||
<div css={{ maxHeight: "400px", overflow: "auto" }}>
|
||||
<BookChildTree
|
||||
child_tree={[
|
||||
{
|
||||
id: blueprint_book.id,
|
||||
name: blueprint_book.label,
|
||||
type: "blueprint_book",
|
||||
children: blueprint_book.child_tree,
|
||||
},
|
||||
]}
|
||||
base_url={`/blueprint/${blueprint_page.id}`}
|
||||
selected_id={selected.data.id}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div css={{ maxHeight: "400px", overflow: "auto" }}>
|
||||
<BookChildTree
|
||||
child_tree={[
|
||||
{
|
||||
id: blueprint_book.id,
|
||||
name: blueprint_book.label,
|
||||
type: "blueprint_book",
|
||||
children: blueprint_book.child_tree,
|
||||
},
|
||||
]}
|
||||
base_url={`/blueprint/${blueprint_page.id}`}
|
||||
selected_id={selected.data.id}
|
||||
/>
|
||||
</div>
|
||||
) : blueprint ? (
|
||||
<Markdown>{blueprint_page.description_markdown}</Markdown>
|
||||
) : null}
|
||||
@ -216,12 +207,10 @@ export const Index: NextPage<IndexProps> = ({
|
||||
{selected.type === "blueprint" && data?.blueprint && (
|
||||
<Panel
|
||||
title={
|
||||
(
|
||||
<span>
|
||||
Entities for{" "}
|
||||
{data.blueprint.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
|
||||
</span>
|
||||
) as any
|
||||
<span>
|
||||
Entities for{" "}
|
||||
{data.blueprint.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
|
||||
</span>
|
||||
}
|
||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
||||
>
|
||||
@ -257,7 +246,10 @@ export const Index: NextPage<IndexProps> = ({
|
||||
</StyledTable>
|
||||
</Panel>
|
||||
)}
|
||||
<Panel title="string" gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}>
|
||||
<Panel
|
||||
title={`string for ${selected.type.replace("_", " ")} "${selected.data.label}"`}
|
||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
||||
>
|
||||
<>
|
||||
{blueprintString && <CopyButton content={blueprintString} marginBottom="0.5rem" />}
|
||||
<textarea
|
||||
@ -370,6 +362,7 @@ export const getServerSideProps = pageHandler(async (context, { session }) => {
|
||||
id: selected_blueprint.id,
|
||||
blueprint_hash: selected_blueprint.blueprint_hash,
|
||||
image_hash: selected_blueprint.image_hash,
|
||||
label: selected_blueprint.label,
|
||||
},
|
||||
};
|
||||
} else if (selected_blueprint_book) {
|
||||
@ -378,20 +371,17 @@ export const getServerSideProps = pageHandler(async (context, { session }) => {
|
||||
data: {
|
||||
id: selected_blueprint_book.id,
|
||||
blueprint_hash: selected_blueprint_book.blueprint_hash,
|
||||
label: selected_blueprint_book.label,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// const image_exists =
|
||||
// selected.type === "blueprint" ? await hasBlueprintImage(selected.data.image_hash) : false;
|
||||
|
||||
const favorite = session
|
||||
? !!(await isBlueprintPageUserFavorite(session.user.id, blueprint_page.id))
|
||||
: false;
|
||||
|
||||
return {
|
||||
props: {
|
||||
image_exists: false,
|
||||
blueprint,
|
||||
blueprint_book,
|
||||
selected,
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React from "react";
|
||||
import { NextPage, NextPageContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { BlueprintPage, searchBlueprintPages, init } from "@factorio-sites/database";
|
||||
import { useRouter } from "next/router";
|
||||
import { searchBlueprintPages, init } from "@factorio-sites/database";
|
||||
import { SimpleGrid, Box, RadioGroup, Stack, Radio } from "@chakra-ui/react";
|
||||
import { BlueprintPage } from "@factorio-sites/types";
|
||||
import { Panel } from "../components/Panel";
|
||||
import { Pagination } from "../components/Pagination";
|
||||
import { useRouterQueryToHref } from "../hooks/query.hook";
|
||||
import { useRouter } from "next/router";
|
||||
import { BlueprintLink } from "../components/BlueprintLink";
|
||||
import { TagsSelect } from "../components/TagsSelect";
|
||||
import { queryValueAsArray } from "../utils/query.utils";
|
||||
@ -67,10 +68,12 @@ export const Index: NextPage<IndexProps> = ({
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Box css={{ display: "flex" }}>
|
||||
{blueprints.map((bp) => (
|
||||
<BlueprintLink key={bp.id} blueprint={bp} />
|
||||
<BlueprintLink key={bp.id} blueprint={bp} type="tile" />
|
||||
))}
|
||||
</Box>
|
||||
<Box>
|
||||
<Pagination page={currentPage} totalPages={totalPages} totalItems={totalItems} />
|
||||
</Box>
|
||||
</Panel>
|
||||
|
@ -14,11 +14,12 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { Formik, Field } from "formik";
|
||||
import { css } from "@emotion/react";
|
||||
import { chakraResponsive } from "@factorio-sites/web-utils";
|
||||
import { Panel } from "../../components/Panel";
|
||||
import { validateCreateBlueprintForm } from "../../utils/validate";
|
||||
import { useAuth } from "../../providers/auth";
|
||||
import { ImageEditor } from "../../components/ImageEditor";
|
||||
import { chakraResponsive } from "@factorio-sites/web-utils";
|
||||
import { TagsSelect } from "../../components/TagsSelect";
|
||||
|
||||
const FieldStyle = css`
|
||||
margin-bottom: 1rem;
|
||||
@ -35,7 +36,7 @@ export const UserBlueprintCreate: NextPage = () => {
|
||||
return (
|
||||
<div css={{ margin: "0.7rem" }}>
|
||||
<Formik
|
||||
initialValues={{ title: "", description: "", string: "" }}
|
||||
initialValues={{ title: "", description: "", string: "", tags: [] }}
|
||||
validate={validateCreateBlueprintForm}
|
||||
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
||||
setStatus("");
|
||||
@ -57,7 +58,7 @@ export const UserBlueprintCreate: NextPage = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, handleSubmit, status, values, errors }) => (
|
||||
{({ isSubmitting, handleSubmit, status, values, errors, setFieldValue }) => (
|
||||
<SimpleGrid
|
||||
columns={2}
|
||||
gap={6}
|
||||
@ -97,14 +98,12 @@ export const UserBlueprintCreate: NextPage = () => {
|
||||
|
||||
<Field name="tags">
|
||||
{({ field, meta }: any) => (
|
||||
<FormControl
|
||||
id="tags"
|
||||
// isRequired
|
||||
isInvalid={meta.touched && meta.error}
|
||||
css={FieldStyle}
|
||||
>
|
||||
<FormLabel>Tags (coming soon)</FormLabel>
|
||||
<Input type="text" {...field} disabled />
|
||||
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<TagsSelect
|
||||
value={field.value}
|
||||
onChange={(tags) => setFieldValue("tags", tags)}
|
||||
/>
|
||||
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
|
@ -14,35 +14,25 @@ import {
|
||||
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 { Blueprint, BlueprintBook, BlueprintPage } from "@factorio-sites/types";
|
||||
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";
|
||||
import { useFbeData } from "../../../hooks/fbe.hook";
|
||||
import { TagsSelect } from "../../../components/TagsSelect";
|
||||
|
||||
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 };
|
||||
@ -54,7 +44,6 @@ interface UserBlueprintProps {
|
||||
export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, selected }) => {
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
const { data } = useFbeData();
|
||||
|
||||
if (!auth) {
|
||||
router.push("/");
|
||||
@ -62,15 +51,6 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
|
||||
if (!blueprintPage) return null;
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const TAGS = Object.keys(data.entities)
|
||||
.filter((key) => !key.startsWith("factorio_logo") && !key.startsWith("crash_site"))
|
||||
.map((key) => {
|
||||
const item = data.entities[key];
|
||||
return { value: item.name, label: item.name.replace(/_/g, " ") };
|
||||
});
|
||||
|
||||
return (
|
||||
<div css={{ margin: "0.7rem" }}>
|
||||
<Formik
|
||||
@ -78,7 +58,7 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
title: blueprintPage.title,
|
||||
description: blueprintPage.description_markdown,
|
||||
string: selected.string,
|
||||
tags: [] as { value: string; label: string }[],
|
||||
tags: [] as string[],
|
||||
}}
|
||||
validate={validateCreateBlueprintForm}
|
||||
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
||||
@ -90,7 +70,6 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
body: JSON.stringify({
|
||||
...values,
|
||||
id: blueprintPage.id,
|
||||
tags: values.tags.map((tag) => tag.value),
|
||||
}),
|
||||
}).then((res) => res.json());
|
||||
|
||||
@ -147,13 +126,9 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
{({ field, meta }: any) => (
|
||||
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<MultiSelect
|
||||
css={{ color: "black" }}
|
||||
options={TAGS}
|
||||
<TagsSelect
|
||||
value={field.value}
|
||||
onChange={(value: any) => setFieldValue("tags", value)}
|
||||
labelledBy="Select"
|
||||
hasSelectAll={false}
|
||||
onChange={(tags) => setFieldValue("tags", tags)}
|
||||
/>
|
||||
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import { Button, SimpleGrid, Box } from "@chakra-ui/react";
|
||||
import { Panel } from "../../components/Panel";
|
||||
import Link from "next/link";
|
||||
import { Button, SimpleGrid, Box } from "@chakra-ui/react";
|
||||
import { getBlueprintPageByUserId } from "@factorio-sites/database";
|
||||
import { BlueprintPage } from "@factorio-sites/types";
|
||||
import { pageHandler } from "../../utils/page-handler";
|
||||
import { BlueprintPage, getBlueprintPageByUserId } from "@factorio-sites/database";
|
||||
import { BlueprintLink } from "../../components/BlueprintLink";
|
||||
|
||||
import { Panel } from "../../components/Panel";
|
||||
interface UserBlueprintsProps {
|
||||
blueprints: BlueprintPage[];
|
||||
}
|
||||
@ -33,7 +33,7 @@ export const UserBlueprints: NextPage<UserBlueprintsProps> = ({ blueprints }) =>
|
||||
</Box>
|
||||
<Box>
|
||||
{blueprints.map((bp) => (
|
||||
<BlueprintLink key={bp.id} blueprint={bp} editLink />
|
||||
<BlueprintLink key={bp.id} blueprint={bp} editLink type="row" />
|
||||
))}
|
||||
</Box>
|
||||
</Panel>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getSessionByToken, init } from "@factorio-sites/database";
|
||||
import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils";
|
||||
import { deleteSessionToken, getSessionToken, jsonReplaceErrors } from "@factorio-sites/node-utils";
|
||||
|
||||
type Await<T> = T extends PromiseLike<infer U> ? Await<U> : T;
|
||||
|
||||
@ -10,6 +10,19 @@ interface CustomContext {
|
||||
useragent: string;
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public message: string,
|
||||
public data?: Record<string, unknown>
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
toJSON() {
|
||||
return { ...this.data, status: this.status, message: this.message };
|
||||
}
|
||||
}
|
||||
|
||||
export const apiHandler = (
|
||||
fn: (req: NextApiRequest, res: NextApiResponse, ctx: CustomContext) => Promise<any>
|
||||
) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
@ -25,5 +38,13 @@ export const apiHandler = (
|
||||
deleteSessionToken(res);
|
||||
}
|
||||
|
||||
return fn(req, res, { session, ip, useragent });
|
||||
return fn(req, res, { session, ip, useragent }).catch((error) => {
|
||||
if (error instanceof ApiError) {
|
||||
res.status(error.status).send(JSON.stringify({ error }));
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
res.status(500).send(JSON.stringify({ error }, jsonReplaceErrors));
|
||||
} else {
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,27 +1,35 @@
|
||||
FROM node:14-slim as builder
|
||||
|
||||
RUN apt-get -qy update && apt-get -qy install openssl
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
COPY fbe-editor-v1.0.0.tgz .
|
||||
COPY yalc.lock .
|
||||
COPY .yalc ./.yalc/
|
||||
|
||||
RUN yarn
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN yarn run db-gen
|
||||
RUN yarn nx build blueprints
|
||||
|
||||
FROM node:14-slim
|
||||
|
||||
RUN apt-get -qy update && apt-get -qy install openssl
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY apps/blueprints/prod.package.json ./package.json
|
||||
COPY yarn.lock .
|
||||
COPY fbe-editor-v1.0.0.tgz .
|
||||
COPY yalc.lock .
|
||||
COPY .yalc ./.yalc/
|
||||
|
||||
RUN yarn install --production
|
||||
|
||||
COPY --from=builder /usr/src/app/node_modules/.prisma ./node_modules/.prisma/
|
||||
COPY --from=builder /usr/src/app/dist/apps/blueprints .
|
||||
|
||||
CMD ["yarn", "next", "start"]
|
@ -7,5 +7,6 @@ module.exports = {
|
||||
"<rootDir>/libs/utils",
|
||||
"<rootDir>/libs/common-utils",
|
||||
"<rootDir>/libs/web-utils",
|
||||
"<rootDir>/libs/types",
|
||||
],
|
||||
};
|
||||
|
@ -2,4 +2,3 @@ export * from "./lib/postgres/database";
|
||||
export * from "./lib/data";
|
||||
export * from "./lib/gcp-pubsub";
|
||||
export * from "./lib/gcp-storage";
|
||||
export * from "./lib/types";
|
||||
|
@ -3,7 +3,7 @@ import { encodeBlueprint, hashString } from "@factorio-sites/node-utils";
|
||||
import { blueprint as BlueprintModel } from "@prisma/client";
|
||||
import { saveBlueprintString } from "../gcp-storage";
|
||||
import { prisma } from "../postgres/database";
|
||||
import { Blueprint } from "../types";
|
||||
import { Blueprint } from "@factorio-sites/types";
|
||||
|
||||
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||
|
||||
@ -36,21 +36,19 @@ export async function createBlueprint(
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
) {
|
||||
): Promise<Blueprint> {
|
||||
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 };
|
||||
return exists;
|
||||
}
|
||||
|
||||
// Write string to google storage
|
||||
await saveBlueprintString(blueprint_hash, string);
|
||||
|
||||
// Write blueprint details to datastore
|
||||
|
||||
const result = await prisma.blueprint.create({
|
||||
data: {
|
||||
label: blueprint.label,
|
||||
@ -71,5 +69,5 @@ export async function createBlueprint(
|
||||
// blueprintId: result.id,
|
||||
// });
|
||||
|
||||
return { insertedId: result.id };
|
||||
return mapBlueprintInstanceToEntry(result);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { encodeBlueprint, hashString } from "@factorio-sites/node-utils";
|
||||
import { blueprint_book } from "@prisma/client";
|
||||
import { saveBlueprintString } from "../gcp-storage";
|
||||
import { prisma } from "../postgres/database";
|
||||
import { BlueprintBook, ChildTree } from "../types";
|
||||
import { BlueprintBook, ChildTree } from "@factorio-sites/types";
|
||||
import { createBlueprint } from "./blueprint";
|
||||
|
||||
const mapBlueprintBookEntityToObject = (entity: blueprint_book): BlueprintBook => ({
|
||||
@ -34,7 +34,7 @@ export async function createBlueprintBook(
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
): Promise<{ insertedId: string; child_tree: ChildTree }> {
|
||||
): Promise<BlueprintBook> {
|
||||
const string = await encodeBlueprint({ blueprint_book: blueprintBook });
|
||||
const blueprint_hash = hashString(string);
|
||||
|
||||
@ -42,7 +42,7 @@ export async function createBlueprintBook(
|
||||
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 };
|
||||
return exists;
|
||||
}
|
||||
|
||||
// Write string to google storage
|
||||
@ -59,19 +59,19 @@ export async function createBlueprintBook(
|
||||
const result = await createBlueprint(blueprint.blueprint, extraInfo);
|
||||
child_tree.push({
|
||||
type: "blueprint",
|
||||
id: result.insertedId,
|
||||
id: result.id,
|
||||
name: blueprint.blueprint.label,
|
||||
});
|
||||
blueprint_ids.push(result.insertedId);
|
||||
blueprint_ids.push(result.id);
|
||||
} else if (blueprint.blueprint_book) {
|
||||
const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo);
|
||||
child_tree.push({
|
||||
type: "blueprint_book",
|
||||
id: result.insertedId,
|
||||
id: result.id,
|
||||
name: blueprint.blueprint_book.label,
|
||||
children: result.child_tree,
|
||||
});
|
||||
blueprint_book_ids.push(result.insertedId);
|
||||
blueprint_book_ids.push(result.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,10 +84,20 @@ export async function createBlueprintBook(
|
||||
child_tree: child_tree as any,
|
||||
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(),
|
||||
blueprint_books: {
|
||||
connect: blueprint_book_ids.map((id) => ({
|
||||
id,
|
||||
})),
|
||||
},
|
||||
blueprints: {
|
||||
connect: blueprint_ids.map((id) => ({
|
||||
id,
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Created Blueprint book ${result.id}`);
|
||||
|
||||
return { insertedId: result.id, child_tree };
|
||||
return mapBlueprintBookEntityToObject(result);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { blueprint_page } from "@prisma/client";
|
||||
import { join, raw, sqltag } from "@prisma/client/runtime";
|
||||
|
||||
import { getBlueprintImageRequestTopic } from "../gcp-pubsub";
|
||||
import { prisma } from "../postgres/database";
|
||||
import { BlueprintPage } from "../types";
|
||||
import { BlueprintPage } from "@factorio-sites/types";
|
||||
|
||||
const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage => ({
|
||||
id: entity.id,
|
||||
@ -11,6 +11,7 @@ const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage =
|
||||
title: entity.title,
|
||||
description_markdown: entity.description_markdown || "",
|
||||
tags: entity.tags,
|
||||
image_hash: entity.image_hash,
|
||||
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,
|
||||
@ -78,30 +79,44 @@ export async function searchBlueprintPages({
|
||||
export async function createBlueprintPage(
|
||||
type: "blueprint" | "blueprint_book",
|
||||
targetId: string,
|
||||
extraInfo: {
|
||||
data: {
|
||||
title: string;
|
||||
user_id: string | null;
|
||||
description_markdown: string;
|
||||
tags?: string[];
|
||||
image_hash: string;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
firstBlueprintId?: string;
|
||||
factorioprints_id?: string;
|
||||
}
|
||||
) {
|
||||
const page = await prisma.blueprint_page.create({
|
||||
data: {
|
||||
user_id: extraInfo.user_id,
|
||||
title: extraInfo.title,
|
||||
description_markdown: extraInfo.description_markdown,
|
||||
factorioprints_id: extraInfo.factorioprints_id,
|
||||
user_id: data.user_id,
|
||||
title: data.title,
|
||||
description_markdown: data.description_markdown,
|
||||
factorioprints_id: data.factorioprints_id,
|
||||
blueprint_id: type === "blueprint" ? targetId : undefined,
|
||||
blueprint_book_id: type === "blueprint_book" ? targetId : undefined,
|
||||
tags: extraInfo.tags ? extraInfo.tags : [],
|
||||
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(),
|
||||
tags: data.tags ? data.tags : [],
|
||||
image_hash: data.image_hash,
|
||||
updated_at: data.updated_at ? new Date(data.updated_at * 1000) : new Date(),
|
||||
created_at: data.created_at ? new Date(data.created_at * 1000) : new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||
if (type === "blueprint") {
|
||||
blueprintImageRequestTopic.publishJSON({
|
||||
blueprintId: targetId,
|
||||
});
|
||||
} else if (data.firstBlueprintId) {
|
||||
blueprintImageRequestTopic.publishJSON({
|
||||
blueprintId: data.firstBlueprintId,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Created Blueprint Page`);
|
||||
return page;
|
||||
}
|
||||
|
@ -38,12 +38,20 @@ export async function saveBlueprintFromFactorioprints(
|
||||
|
||||
if (parsed.data.blueprint) {
|
||||
console.log("string has one blueprint...");
|
||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, extraInfo);
|
||||
await createBlueprintPage("blueprint", insertedId, extraInfoPage);
|
||||
const result = await createBlueprint(parsed.data.blueprint, extraInfo);
|
||||
await createBlueprintPage("blueprint", result.id, {
|
||||
...extraInfoPage,
|
||||
firstBlueprintId: result.id,
|
||||
image_hash: "",
|
||||
});
|
||||
} 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);
|
||||
const result = await createBlueprintBook(parsed.data.blueprint_book, extraInfo);
|
||||
await createBlueprintPage("blueprint_book", result.id, {
|
||||
...extraInfoPage,
|
||||
firstBlueprintId: undefined,
|
||||
image_hash: "",
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { PubSub, Message } from "@google-cloud/pubsub";
|
||||
// export { Message } from "@google-cloud/pubsub";
|
||||
|
||||
const pubsub = new PubSub();
|
||||
|
||||
|
@ -22,13 +22,30 @@ export async function saveBlueprintString(hash: string, content: string) {
|
||||
* BlueprintImage
|
||||
*/
|
||||
|
||||
export async function saveBlueprintImage(hash: string, image: Buffer): Promise<void> {
|
||||
return IMAGE_BUCKET.file(`${hash}.webp`).save(image, {
|
||||
type sizeType = "original" | "300";
|
||||
|
||||
export async function saveBlueprintImage(
|
||||
hash: string,
|
||||
image: Buffer,
|
||||
type: sizeType = "original"
|
||||
): Promise<void> {
|
||||
return IMAGE_BUCKET.file(`${type}/${hash}.webp`).save(image, {
|
||||
contentType: "image/webp",
|
||||
});
|
||||
}
|
||||
|
||||
export async function hasBlueprintImage(hash: string): Promise<boolean> {
|
||||
const [result] = await IMAGE_BUCKET.file(`${hash}.webp`).exists();
|
||||
export async function hasBlueprintImage(
|
||||
hash: string,
|
||||
type: sizeType = "original"
|
||||
): Promise<boolean> {
|
||||
const [result] = await IMAGE_BUCKET.file(`${type}/${hash}.webp`).exists();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getBlueprintByImageHash(
|
||||
hash: string,
|
||||
type: sizeType = "original"
|
||||
): Promise<Buffer> {
|
||||
const [result] = await IMAGE_BUCKET.file(`${type}/${hash}.webp`).download();
|
||||
return result;
|
||||
}
|
||||
|
@ -38,4 +38,7 @@ const promise = _init()
|
||||
console.log("Database failed to init!", reason);
|
||||
});
|
||||
|
||||
export const init = async () => promise;
|
||||
export const init = async () => {
|
||||
await promise;
|
||||
await prisma.$connect();
|
||||
};
|
||||
|
@ -1,64 +0,0 @@
|
||||
import { DataTypes, UUIDV4, Optional, Model, Sequelize } from "sequelize";
|
||||
|
||||
interface BlueprintAttributes {
|
||||
id: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
game_version?: string;
|
||||
blueprint_hash: string;
|
||||
image_hash: string;
|
||||
image_version: number;
|
||||
tags: string[];
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface BlueprintInstance
|
||||
extends Model<
|
||||
Omit<BlueprintAttributes, "created_at" | "updated_at">,
|
||||
Optional<BlueprintAttributes, "id" | "created_at" | "updated_at">
|
||||
>,
|
||||
BlueprintAttributes {}
|
||||
|
||||
export const getBlueprintModel = (sequelize: Sequelize) => {
|
||||
return sequelize.define<BlueprintInstance>(
|
||||
"blueprint",
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: UUIDV4,
|
||||
},
|
||||
label: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
game_version: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
blueprint_hash: {
|
||||
type: DataTypes.STRING(40),
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
image_hash: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: false,
|
||||
},
|
||||
image_version: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
},
|
||||
tags: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||
set(value: string[]) {
|
||||
this.setDataValue("tags", Array.isArray(value) ? value : []);
|
||||
},
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
import { DataTypes, UUIDV4, Optional, Model, Sequelize } from "sequelize";
|
||||
import { ChildTree } from "../../types";
|
||||
|
||||
interface BlueprintBookAttributes {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
child_tree: ChildTree;
|
||||
blueprint_hash: string;
|
||||
is_modded: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface BlueprintBookInstance
|
||||
extends Model<
|
||||
Omit<BlueprintBookAttributes, "created_at" | "updated_at">,
|
||||
Optional<BlueprintBookAttributes, "id" | "created_at" | "updated_at">
|
||||
>,
|
||||
BlueprintBookAttributes {}
|
||||
|
||||
export const getBlueprintBookModel = (sequelize: Sequelize) => {
|
||||
return sequelize.define<BlueprintBookInstance>(
|
||||
"blueprint_book",
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: UUIDV4,
|
||||
},
|
||||
label: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
child_tree: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: false,
|
||||
},
|
||||
blueprint_hash: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: false,
|
||||
},
|
||||
is_modded: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
@ -1,73 +0,0 @@
|
||||
import { DataTypes, UUIDV4, Optional, Model, Sequelize } from "sequelize";
|
||||
|
||||
interface BlueprintPageAttributes {
|
||||
id: string;
|
||||
user_id: string | null;
|
||||
blueprint_id?: string;
|
||||
blueprint_book_id?: string;
|
||||
title: string;
|
||||
description_markdown: string;
|
||||
tags: string[];
|
||||
factorioprints_id?: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface BlueprintPageInstance
|
||||
extends Model<
|
||||
Omit<BlueprintPageAttributes, "created_at" | "updated_at">,
|
||||
Optional<BlueprintPageAttributes, "id" | "created_at" | "updated_at">
|
||||
>,
|
||||
BlueprintPageAttributes {}
|
||||
|
||||
export const getBlueprintPageModel = (sequelize: Sequelize) => {
|
||||
return sequelize.define<BlueprintPageInstance>(
|
||||
"blueprint_page",
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: UUIDV4,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.UUID,
|
||||
},
|
||||
blueprint_id: {
|
||||
type: DataTypes.UUID,
|
||||
unique: true,
|
||||
},
|
||||
blueprint_book_id: {
|
||||
type: DataTypes.UUID,
|
||||
unique: true,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
description_markdown: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
tags: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||
set(value: string[]) {
|
||||
this.setDataValue("tags", Array.isArray(value) ? value : []);
|
||||
},
|
||||
},
|
||||
factorioprints_id: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
},
|
||||
{ validate:{
|
||||
blueprintOrBook() {
|
||||
if (!this.blueprint_id && !this.blueprint_book_id) {
|
||||
throw new Error('Must have either a blueprint_id or a blueprint_book_id');
|
||||
}
|
||||
},
|
||||
externalOrInternal() {
|
||||
if (!this.user_id && !this.factorioprints_id) {
|
||||
throw new Error('Must have either a user_id or a factorioprints_id');
|
||||
}
|
||||
}
|
||||
}}
|
||||
);
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
import { DataTypes, UUIDV4, Optional, Model, Sequelize } from "sequelize";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
interface SessionAttributes {
|
||||
id: string;
|
||||
user_id: string;
|
||||
session_token: string;
|
||||
useragent: string;
|
||||
ip: string;
|
||||
last_used: Date;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface SessionInstance
|
||||
extends Model<
|
||||
Omit<SessionAttributes, "created_at" | "updated_at">,
|
||||
Optional<
|
||||
SessionAttributes,
|
||||
"id" | "session_token" | "last_used" | "created_at" | "updated_at"
|
||||
>
|
||||
>,
|
||||
SessionAttributes {}
|
||||
|
||||
export const getSessionModel = (sequelize: Sequelize) => {
|
||||
return sequelize.define<SessionInstance>(
|
||||
"session",
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: UUIDV4,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
session_token: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: () => crypto.randomBytes(30).toString("base64"),
|
||||
},
|
||||
useragent: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
ip: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
last_used: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
import { DataTypes, UUIDV4, Optional, Model, Sequelize } from "sequelize";
|
||||
|
||||
interface UserAttributes {
|
||||
id: string;
|
||||
email: string | null;
|
||||
username: string;
|
||||
password: string;
|
||||
role: "user" | "moderator" | "admin";
|
||||
steam_id: string | null;
|
||||
password_reset_token: string | null;
|
||||
password_reset_at: Date | null;
|
||||
last_password_change: Date;
|
||||
last_login_at: Date | null;
|
||||
last_login_ip: string;
|
||||
email_validated: boolean;
|
||||
email_validate_token: string | null;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface UserInstance
|
||||
extends Model<
|
||||
Omit<UserAttributes, "created_at" | "updated_at">,
|
||||
Optional<
|
||||
UserAttributes,
|
||||
| "id"
|
||||
| "role"
|
||||
| "steam_id"
|
||||
| "email"
|
||||
| "password"
|
||||
| "password_reset_token"
|
||||
| "password_reset_at"
|
||||
| "last_password_change"
|
||||
| "last_login_at"
|
||||
| "email_validated"
|
||||
| "email_validate_token"
|
||||
| "created_at"
|
||||
| "updated_at"
|
||||
>
|
||||
>,
|
||||
UserAttributes {}
|
||||
|
||||
export const getUsertModel = (sequelize: Sequelize) => {
|
||||
return sequelize.define<UserInstance>(
|
||||
"user",
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: UUIDV4,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
validate: { isEmail: true },
|
||||
set(val: string) {
|
||||
this.setDataValue("email", val.toLowerCase().trim());
|
||||
},
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.ENUM("user", "moderator", "admin"),
|
||||
defaultValue: "user",
|
||||
},
|
||||
steam_id: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
password_reset_token: {
|
||||
type: DataTypes.UUID,
|
||||
},
|
||||
password_reset_at: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
last_password_change: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
last_login_at: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
last_login_ip: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: { isIP: true },
|
||||
},
|
||||
email_validated: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
},
|
||||
email_validate_token: {
|
||||
type: DataTypes.UUID,
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
@ -1,9 +1,10 @@
|
||||
import { IncomingMessage } from "http";
|
||||
import * as crypto from "crypto";
|
||||
import * as pako from "pako";
|
||||
import * as cookie from "cookie";
|
||||
import { BlueprintStringData } from "@factorio-sites/common-utils";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IncomingMessage } from "http";
|
||||
import { BlueprintStringData } from "@factorio-sites/common-utils";
|
||||
import { ChildTree } from "@factorio-sites/types";
|
||||
|
||||
export const parseBlueprintString = async (
|
||||
string: string
|
||||
@ -67,3 +68,33 @@ export const deleteSessionToken = (res: NextApiResponse) => {
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export function getFirstBlueprintFromChildTree(child_tree: ChildTree): string {
|
||||
// First try flat search
|
||||
const result = child_tree.find((child) => child.type === "blueprint");
|
||||
if (result) return result.id;
|
||||
|
||||
// Recusrive search
|
||||
let blueprint_id: string | null = null;
|
||||
child_tree.forEach((child) => {
|
||||
if (child.type === "blueprint_book") {
|
||||
const bp = getFirstBlueprintFromChildTree([child]);
|
||||
if (bp) blueprint_id = bp;
|
||||
}
|
||||
});
|
||||
|
||||
if (!blueprint_id) throw Error("Failed to find blueprint id in child_tree");
|
||||
|
||||
return blueprint_id;
|
||||
}
|
||||
|
||||
export function jsonReplaceErrors(_: string, value: unknown) {
|
||||
if (value instanceof Error) {
|
||||
return Object.getOwnPropertyNames(value).reduce((error, key) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error[key] = (value as any)[key];
|
||||
return error;
|
||||
}, {} as Record<string, unknown>);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
5
libs/types/.eslintrc.json
Normal file
5
libs/types/.eslintrc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"rules": {}
|
||||
}
|
7
libs/types/README.md
Normal file
7
libs/types/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# types
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test types` to execute the unit tests via [Jest](https://jestjs.io).
|
14
libs/types/jest.config.js
Normal file
14
libs/types/jest.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
displayName: "types",
|
||||
preset: "../../jest.preset.js",
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsConfig: "<rootDir>/tsconfig.spec.json",
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[tj]sx?$": "ts-jest",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
|
||||
coverageDirectory: "../../coverage/libs/types",
|
||||
};
|
1
libs/types/src/index.ts
Normal file
1
libs/types/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./lib/types";
|
@ -47,6 +47,7 @@ export interface BlueprintPage {
|
||||
title: string;
|
||||
description_markdown: string;
|
||||
tags: string[];
|
||||
image_hash: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
factorioprints_id: string | null;
|
13
libs/types/tsconfig.json
Normal file
13
libs/types/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
11
libs/types/tsconfig.lib.json
Normal file
11
libs/types/tsconfig.lib.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["**/*.spec.ts"],
|
||||
"include": ["**/*.ts"]
|
||||
}
|
9
libs/types/tsconfig.spec.json
Normal file
9
libs/types/tsconfig.spec.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"]
|
||||
}
|
47
nx.json
47
nx.json
@ -1,14 +1,9 @@
|
||||
{
|
||||
"npmScope": "factorio-sites",
|
||||
"affected": {
|
||||
"defaultBase": "master"
|
||||
},
|
||||
"affected": { "defaultBase": "master" },
|
||||
"implicitDependencies": {
|
||||
"workspace.json": "*",
|
||||
"package.json": {
|
||||
"dependencies": "*",
|
||||
"devDependencies": "*"
|
||||
},
|
||||
"package.json": { "dependencies": "*", "devDependencies": "*" },
|
||||
"tsconfig.base.json": "*",
|
||||
"tslint.json": "*",
|
||||
".eslintrc.json": "*",
|
||||
@ -17,36 +12,18 @@
|
||||
"tasksRunnerOptions": {
|
||||
"default": {
|
||||
"runner": "@nrwl/workspace/tasks-runners/default",
|
||||
"options": {
|
||||
"cacheableOperations": ["build", "lint", "test", "e2e"]
|
||||
}
|
||||
"options": { "cacheableOperations": ["build", "lint", "test", "e2e"] }
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"blueprints": {
|
||||
"tags": []
|
||||
},
|
||||
"blueprints-e2e": {
|
||||
"tags": [],
|
||||
"implicitDependencies": ["blueprints"]
|
||||
},
|
||||
"blueprint-image-function": {
|
||||
"tags": []
|
||||
},
|
||||
"factorioprints-scraper": {
|
||||
"tags": []
|
||||
},
|
||||
"database": {
|
||||
"tags": []
|
||||
},
|
||||
"node-utils": {
|
||||
"tags": []
|
||||
},
|
||||
"common-utils": {
|
||||
"tags": []
|
||||
},
|
||||
"web-utils": {
|
||||
"tags": []
|
||||
}
|
||||
"blueprints": { "tags": [] },
|
||||
"blueprints-e2e": { "tags": [], "implicitDependencies": ["blueprints"] },
|
||||
"blueprint-image-function": { "tags": [] },
|
||||
"factorioprints-scraper": { "tags": [] },
|
||||
"database": { "tags": [] },
|
||||
"node-utils": { "tags": [] },
|
||||
"common-utils": { "tags": [] },
|
||||
"web-utils": { "tags": [] },
|
||||
"types": { "tags": [] }
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,8 @@
|
||||
"@factorio-sites/database": ["libs/database/src/index.ts"],
|
||||
"@factorio-sites/node-utils": ["libs/node-utils/src/index.ts"],
|
||||
"@factorio-sites/common-utils": ["libs/common-utils/src/index.ts"],
|
||||
"@factorio-sites/web-utils": ["libs/web-utils/src/index.ts"]
|
||||
"@factorio-sites/web-utils": ["libs/web-utils/src/index.ts"],
|
||||
"@factorio-sites/types": ["libs/types/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "tmp"]
|
||||
|
@ -260,6 +260,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"types": {
|
||||
"root": "libs/types",
|
||||
"sourceRoot": "libs/types/src",
|
||||
"projectType": "library",
|
||||
"architect": {
|
||||
"lint": {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/types/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/types"],
|
||||
"options": {
|
||||
"jestConfig": "libs/types/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
|
Loading…
Reference in New Issue
Block a user