mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2024-11-24 08:52:36 +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": [
|
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||||
"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";
|
import * as sharp from "sharp";
|
||||||
|
|
||||||
const RESIZE_ENABLED = false;
|
|
||||||
|
|
||||||
// const calculateImageSizeMod = (pixels: number) =>
|
// const calculateImageSizeMod = (pixels: number) =>
|
||||||
// Math.min(Math.max((-pixels + 500) / 20500 + 1, 0.3), 1);
|
// 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) =>
|
export const optimise = async (image: Buffer, max_dimention = 5000): Promise<Buffer> => {
|
||||||
Math.min(Math.max((-pixels + 3000) / 33000 + 1, 0.3), 1);
|
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> => {
|
console.log(
|
||||||
let sharp_image = sharp(image);
|
JSON.stringify({
|
||||||
if (RESIZE_ENABLED) {
|
input_size: `${Math.round(image.byteLength / 1024) / 1000}mb`,
|
||||||
const MAX_IMAGE_DIMENTION = 5000;
|
output_size: `${Math.round(sharp_image.byteLength / 1024) / 1000}mb`,
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const min_image = await sharp_image.toBuffer();
|
return sharp_image;
|
||||||
|
|
||||||
console.log({
|
|
||||||
input_size_mb: image.byteLength / 1024_000,
|
|
||||||
output_size_mb: min_image.byteLength / 1024_000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return min_image;
|
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,11 @@ import * as Puppeteer from "puppeteer";
|
|||||||
let BROWSER: Puppeteer.Browser;
|
let BROWSER: Puppeteer.Browser;
|
||||||
let PAGE: Puppeteer.Page;
|
let PAGE: Puppeteer.Page;
|
||||||
|
|
||||||
async function getPage() {
|
async function getPage(headless: boolean) {
|
||||||
if (PAGE) return PAGE;
|
if (PAGE) return PAGE;
|
||||||
|
|
||||||
const _browser = await Puppeteer.launch({
|
const _browser = await Puppeteer.launch({
|
||||||
headless: false,
|
headless,
|
||||||
args: ["--no-sandbox"],
|
args: ["--no-sandbox"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -24,9 +24,9 @@ async function getPage() {
|
|||||||
return _page;
|
return _page;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderImage(blueprint_string: string) {
|
export async function renderImage(blueprint_string: string, options?: { headless: boolean }) {
|
||||||
const tl = timeLogger("localFbeRenderer");
|
const tl = timeLogger("localFbeRenderer");
|
||||||
const page = await getPage();
|
const page = await getPage(options?.headless ?? true);
|
||||||
|
|
||||||
tl("Page loaded");
|
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));
|
// import { subscribeToPubSub } from "./pubsub-render";
|
||||||
// uploadLocalFiles().catch((reason) => console.error("Fatal error:", reason));
|
// subscribeToPubSub().catch((reason) => console.error("Fatal error:", reason));
|
||||||
// rePublishAllBlueprints().catch((reason) => console.error("Fatal error:", reason));
|
// rePublishAllBlueprints().catch((reason) => console.error("Fatal error:", reason));
|
||||||
|
|
||||||
// image hash = a99525f97c26c7242ecdd96679043b1a5e65dd0c
|
exports.renderImageHttp = functionHttpHandler;
|
||||||
// SELECT * FROM BlueprintBook WHERE blueprint_ids CONTAINS Key(Blueprint, 4532736400293888)
|
exports.renderImagePubSub = functionPubSubHandler;
|
||||||
// bp = Key(Blueprint, 4532736400293888)
|
|
||||||
// book = Key(BlueprintBook, 5034207050989568)
|
// local_test("8737437e-f15b-459c-8c1d-d0074f3a89ca");
|
||||||
// page = 6225886932107264
|
|
||||||
|
@ -35,11 +35,12 @@ export async function subscribeToPubSub() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(message.data.toString());
|
if (!message.attributes.blueprintId) {
|
||||||
if (!data.blueprintId) return ack("blueprintId not found in message body", false);
|
return ack("blueprintId not found in message body", false);
|
||||||
|
}
|
||||||
console.log("------------------------------------------------");
|
console.log("------------------------------------------------");
|
||||||
console.log("[pubsub] generating image for", data.blueprintId);
|
console.log("[pubsub] generating image for", message.attributes.blueprintId);
|
||||||
const blueprint = await getBlueprintById(data.blueprintId);
|
const blueprint = await getBlueprintById(message.attributes.blueprintId);
|
||||||
if (!blueprint) return ack("Blueprint not found", false);
|
if (!blueprint) return ack("Blueprint not found", false);
|
||||||
|
|
||||||
if (await hasBlueprintImage(blueprint.image_hash)) {
|
if (await hasBlueprintImage(blueprint.image_hash)) {
|
||||||
@ -58,7 +59,7 @@ export async function subscribeToPubSub() {
|
|||||||
|
|
||||||
return ack("[pubsub] image saved", true);
|
return ack("[pubsub] image saved", true);
|
||||||
} catch (reason) {
|
} 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() {
|
// export async function rePublishAllBlueprints() {
|
||||||
const topic = getBlueprintImageRequestTopic();
|
// const topic = getBlueprintImageRequestTopic();
|
||||||
const fetchPage = async (page = 1) => {
|
// const fetchPage = async (page = 1) => {
|
||||||
const blueprints = await getPaginatedBlueprints(page);
|
// const blueprints = await getPaginatedBlueprints(page);
|
||||||
if (blueprints.length === 0) {
|
// if (blueprints.length === 0) {
|
||||||
return console.log("No more blueprints found");
|
// return console.log("No more blueprints found");
|
||||||
}
|
// }
|
||||||
console.log(`Publishing page ${page} with ${blueprints.length} blueprints`);
|
// console.log(`Publishing page ${page} with ${blueprints.length} blueprints`);
|
||||||
|
|
||||||
await Promise.all(
|
// await Promise.all(
|
||||||
blueprints.map((blueprint) => {
|
// blueprints.map((blueprint) => {
|
||||||
return topic.publishJSON({ blueprintId: blueprint.id });
|
// return topic.publishJSON({ blueprintId: blueprint.id });
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
fetchPage(page + 1);
|
// fetchPage(page + 1);
|
||||||
};
|
// };
|
||||||
await fetchPage();
|
// await fetchPage();
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
export {};
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../../dist/out-tsc",
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"target": "ES2019",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"exclude": ["**/*.spec.ts"],
|
"exclude": ["**/*.spec.ts"],
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
domains: ["storage.googleapis.com"],
|
||||||
|
},
|
||||||
webpack(config, options) {
|
webpack(config, options) {
|
||||||
const { dev, isServer } = options;
|
const { dev, isServer } = options;
|
||||||
|
|
||||||
// Do not run type checking twice:
|
// Do not run type checking twice:
|
||||||
if (dev && isServer) {
|
if (dev && isServer) {
|
||||||
|
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||||
config.plugins.push(new ForkTsCheckerWebpackPlugin());
|
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 {
|
model blueprint {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
label String? @db.VarChar(255)
|
label String? @db.VarChar(255)
|
||||||
description String?
|
description String?
|
||||||
game_version String? @db.VarChar(255)
|
game_version String? @db.VarChar(255)
|
||||||
blueprint_hash String @unique @db.VarChar(40)
|
blueprint_hash String @unique @db.VarChar(40)
|
||||||
image_hash String @db.VarChar(40)
|
image_hash String @db.VarChar(40)
|
||||||
image_version Int @default(1)
|
image_version Int @default(1)
|
||||||
tags String[] @db.VarChar(255)
|
tags String[] @db.VarChar(255)
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
blueprint_page blueprint_page?
|
blueprint_page blueprint_page?
|
||||||
|
blueprint_books blueprint_book[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model blueprint_book {
|
model blueprint_book {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
label String? @db.VarChar(255)
|
label String? @db.VarChar(255)
|
||||||
description String?
|
description String?
|
||||||
child_tree Json @db.Json
|
child_tree Json @db.Json
|
||||||
blueprint_hash String @unique @db.VarChar(40)
|
blueprint_hash String @unique @db.VarChar(40)
|
||||||
is_modded Boolean
|
is_modded Boolean
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
blueprint_page blueprint_page?
|
blueprint_page blueprint_page?
|
||||||
}
|
blueprints blueprint[]
|
||||||
|
blueprint_books blueprint_book[] @relation("blueprint_books")
|
||||||
model blueprint_book_blueprints {
|
blueprint_books_relation blueprint_book[] @relation("blueprint_books")
|
||||||
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 {
|
model blueprint_page {
|
||||||
@ -61,6 +51,7 @@ model blueprint_page {
|
|||||||
title String @db.VarChar(255)
|
title String @db.VarChar(255)
|
||||||
description_markdown String?
|
description_markdown String?
|
||||||
tags String[] @db.VarChar(255)
|
tags String[] @db.VarChar(255)
|
||||||
|
image_hash String @db.VarChar(40)
|
||||||
factorioprints_id String? @unique @db.VarChar(255)
|
factorioprints_id String? @unique @db.VarChar(255)
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
{
|
{
|
||||||
"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/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",
|
||||||
@ -23,9 +24,10 @@
|
|||||||
"phin": "3.5.1",
|
"phin": "3.5.1",
|
||||||
"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",
|
||||||
|
"fork-ts-checker-webpack-plugin": "6.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,45 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
import { css } from "@emotion/react";
|
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 { Box, Text } from "@chakra-ui/react";
|
||||||
import { MdFavorite } from "react-icons/md";
|
import { MdFavorite } from "react-icons/md";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const linkStyles = css`
|
const linkStyles = css`
|
||||||
width: 100%;
|
margin: 5px 10px 5px 0;
|
||||||
margin: 5px 0;
|
background: #353535;
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
position: relative;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #ccc;
|
background: #4c4c4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tile {
|
||||||
|
.block {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -26,26 +51,61 @@ const formatDate = (datenum: number) => {
|
|||||||
interface BlueprintLinkProps {
|
interface BlueprintLinkProps {
|
||||||
blueprint: BlueprintPage;
|
blueprint: BlueprintPage;
|
||||||
editLink?: boolean;
|
editLink?: boolean;
|
||||||
|
type: "tile" | "row";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlueprintLink: React.FC<BlueprintLinkProps> = ({ blueprint, editLink }) => (
|
export const BlueprintLink: React.FC<BlueprintLinkProps> = ({
|
||||||
<div css={linkStyles}>
|
blueprint,
|
||||||
<Link
|
editLink,
|
||||||
href={editLink ? `/user/blueprint/${blueprint.id}` : `/blueprint/${blueprint.id}`}
|
type = "tile",
|
||||||
passHref
|
}) => {
|
||||||
>
|
const [imageError, setImageError] = useState(false);
|
||||||
<a>
|
const onImageError = (error: unknown) => {
|
||||||
<Box css={{ display: "flex", justifyContent: "space-between" }}>
|
console.log(error);
|
||||||
<Text>{blueprint.title}</Text>
|
setImageError(true);
|
||||||
<Box css={{ display: "flex" }}>
|
};
|
||||||
<Text css={{ display: "flex", alignItems: "center", marginRight: "2rem" }}>
|
|
||||||
<MdFavorite css={{ marginRight: "0.5rem" }} />
|
return (
|
||||||
{blueprint.favorite_count}
|
<div css={linkStyles} className={type}>
|
||||||
</Text>
|
<Link
|
||||||
<Text>{formatDate(blueprint.updated_at)}</Text>
|
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>
|
||||||
</Box>
|
</a>
|
||||||
</a>
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { css } from "@emotion/react";
|
import { css } from "@emotion/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import BBCode from "bbcode-to-react";
|
import BBCode from "bbcode-to-react";
|
||||||
import { ChildTree } from "@factorio-sites/database";
|
import { ChildTree } from "@factorio-sites/types";
|
||||||
|
|
||||||
const componentStyles = css`
|
const componentStyles = css`
|
||||||
.blueprint,
|
.blueprint,
|
||||||
|
@ -70,7 +70,7 @@ export const ImageEditor: React.FC<{ string: string }> = ({ string }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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> */}
|
{/* <img src={image} alt="blueprint" style={{ width: "500px" }}></img> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,8 +2,9 @@ import {
|
|||||||
createBlueprint,
|
createBlueprint,
|
||||||
createBlueprintPage,
|
createBlueprintPage,
|
||||||
createBlueprintBook,
|
createBlueprintBook,
|
||||||
|
getBlueprintById,
|
||||||
} from "@factorio-sites/database";
|
} 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 { parseDatabaseError } from "../../../utils/api.utils";
|
||||||
import { apiHandler } from "../../../utils/api-handler";
|
import { apiHandler } from "../../../utils/api-handler";
|
||||||
|
|
||||||
@ -37,12 +38,22 @@ const handler = apiHandler(async (req, res, { session }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (parsed?.data.blueprint) {
|
if (parsed?.data.blueprint) {
|
||||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, info);
|
const blueprint = await createBlueprint(parsed.data.blueprint, info);
|
||||||
const page = await createBlueprintPage("blueprint", insertedId, info);
|
const page = await createBlueprintPage("blueprint", blueprint.id, {
|
||||||
|
...info,
|
||||||
|
image_hash: blueprint.image_hash,
|
||||||
|
});
|
||||||
return res.status(201).json({ success: true, id: page.id });
|
return res.status(201).json({ success: true, id: page.id });
|
||||||
} else if (parsed?.data.blueprint_book) {
|
} else if (parsed?.data.blueprint_book) {
|
||||||
const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, info);
|
const result = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||||
const page = await createBlueprintPage("blueprint_book", insertedId, 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 });
|
return res.status(201).json({ success: true, id: page.id });
|
||||||
}
|
}
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
|
@ -34,12 +34,12 @@ const handler = apiHandler(async (req, res, { session }) => {
|
|||||||
console.log(info);
|
console.log(info);
|
||||||
|
|
||||||
if (parsed?.data.blueprint) {
|
if (parsed?.data.blueprint) {
|
||||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, info);
|
const result = await createBlueprint(parsed.data.blueprint, info);
|
||||||
const page = await editBlueprintPage(id, "blueprint", insertedId, info);
|
const page = await editBlueprintPage(id, "blueprint", result.id, info);
|
||||||
return res.status(200).json({ success: true, id: page.id });
|
return res.status(200).json({ success: true, id: page.id });
|
||||||
} else if (parsed?.data.blueprint_book) {
|
} else if (parsed?.data.blueprint_book) {
|
||||||
const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, info);
|
const result = await createBlueprintBook(parsed.data.blueprint_book, info);
|
||||||
const page = await editBlueprintPage(id, "blueprint_book", insertedId, info);
|
const page = await editBlueprintPage(id, "blueprint_book", result.id, info);
|
||||||
return res.status(200).json({ success: true, id: page.id });
|
return res.status(200).json({ success: true, id: page.id });
|
||||||
}
|
}
|
||||||
} catch (reason) {
|
} 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,
|
getBlueprintById,
|
||||||
getBlueprintStringByHash,
|
getBlueprintStringByHash,
|
||||||
hasBlueprintImage,
|
hasBlueprintImage,
|
||||||
Blueprint,
|
|
||||||
} from "@factorio-sites/database";
|
} from "@factorio-sites/database";
|
||||||
|
import { Blueprint } from "@factorio-sites/types";
|
||||||
|
|
||||||
|
const DISABLED = true;
|
||||||
|
|
||||||
const getOneMessage = async (): Promise<Blueprint> => {
|
const getOneMessage = async (): Promise<Blueprint> => {
|
||||||
const topic = getBlueprintImageRequestTopic();
|
const topic = getBlueprintImageRequestTopic();
|
||||||
@ -43,6 +45,8 @@ const getOneMessage = async (): Promise<Blueprint> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handler: NextApiHandler = async (req, res) => {
|
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
|
// Allow the url to be used in the blueprint editor
|
||||||
if (
|
if (
|
||||||
req.headers.origin &&
|
req.headers.origin &&
|
||||||
|
@ -33,15 +33,6 @@ const handler: NextApiHandler = async (req, res) => {
|
|||||||
if (user) {
|
if (user) {
|
||||||
const session = await createSession(user, useragent, ip);
|
const session = await createSession(user, useragent, ip);
|
||||||
setUserToken(res, session.session_token);
|
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
|
// First time logging in, make new user
|
||||||
else {
|
else {
|
||||||
@ -54,9 +45,16 @@ const handler: NextApiHandler = async (req, res) => {
|
|||||||
|
|
||||||
const session = await createSession(user, useragent, ip);
|
const session = await createSession(user, useragent, ip);
|
||||||
setUserToken(res, session.session_token);
|
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;
|
export default handler;
|
||||||
|
@ -3,14 +3,12 @@ import { NextPage } from "next";
|
|||||||
import BBCode from "bbcode-to-react";
|
import BBCode from "bbcode-to-react";
|
||||||
import { Button, Grid, Image } from "@chakra-ui/react";
|
import { Button, Grid, Image } from "@chakra-ui/react";
|
||||||
import {
|
import {
|
||||||
BlueprintBook,
|
|
||||||
Blueprint,
|
|
||||||
BlueprintPage,
|
|
||||||
getBlueprintBookById,
|
getBlueprintBookById,
|
||||||
getBlueprintById,
|
getBlueprintById,
|
||||||
getBlueprintPageById,
|
getBlueprintPageById,
|
||||||
isBlueprintPageUserFavorite,
|
isBlueprintPageUserFavorite,
|
||||||
} from "@factorio-sites/database";
|
} from "@factorio-sites/database";
|
||||||
|
import { BlueprintBook, Blueprint, BlueprintPage } from "@factorio-sites/types";
|
||||||
import { BlueprintStringData, timeLogger } from "@factorio-sites/common-utils";
|
import { BlueprintStringData, timeLogger } from "@factorio-sites/common-utils";
|
||||||
import { chakraResponsive, parseBlueprintStringClient } from "@factorio-sites/web-utils";
|
import { chakraResponsive, parseBlueprintStringClient } from "@factorio-sites/web-utils";
|
||||||
import { Panel } from "../../components/Panel";
|
import { Panel } from "../../components/Panel";
|
||||||
@ -24,11 +22,10 @@ import styled from "@emotion/styled";
|
|||||||
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
|
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
|
||||||
|
|
||||||
type Selected =
|
type Selected =
|
||||||
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash"> }
|
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash" | "label"> }
|
||||||
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash"> };
|
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash" | "label"> };
|
||||||
|
|
||||||
interface IndexProps {
|
interface IndexProps {
|
||||||
image_exists: boolean;
|
|
||||||
selected: Selected;
|
selected: Selected;
|
||||||
blueprint: Blueprint | null;
|
blueprint: Blueprint | null;
|
||||||
blueprint_book: BlueprintBook | null;
|
blueprint_book: BlueprintBook | null;
|
||||||
@ -46,7 +43,6 @@ const StyledTable = styled.table`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Index: NextPage<IndexProps> = ({
|
export const Index: NextPage<IndexProps> = ({
|
||||||
image_exists,
|
|
||||||
selected,
|
selected,
|
||||||
blueprint,
|
blueprint,
|
||||||
blueprint_book,
|
blueprint_book,
|
||||||
@ -90,7 +86,6 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log({
|
console.log({
|
||||||
image_exists,
|
|
||||||
selected,
|
selected,
|
||||||
blueprint,
|
blueprint,
|
||||||
blueprint_book,
|
blueprint_book,
|
||||||
@ -149,24 +144,20 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
gridColumn="1"
|
gridColumn="1"
|
||||||
>
|
>
|
||||||
{blueprint_book ? (
|
{blueprint_book ? (
|
||||||
<>
|
<div css={{ maxHeight: "400px", overflow: "auto" }}>
|
||||||
<div>This string contains a blueprint book </div>
|
<BookChildTree
|
||||||
<br />
|
child_tree={[
|
||||||
<div css={{ maxHeight: "400px", overflow: "auto" }}>
|
{
|
||||||
<BookChildTree
|
id: blueprint_book.id,
|
||||||
child_tree={[
|
name: blueprint_book.label,
|
||||||
{
|
type: "blueprint_book",
|
||||||
id: blueprint_book.id,
|
children: blueprint_book.child_tree,
|
||||||
name: blueprint_book.label,
|
},
|
||||||
type: "blueprint_book",
|
]}
|
||||||
children: blueprint_book.child_tree,
|
base_url={`/blueprint/${blueprint_page.id}`}
|
||||||
},
|
selected_id={selected.data.id}
|
||||||
]}
|
/>
|
||||||
base_url={`/blueprint/${blueprint_page.id}`}
|
</div>
|
||||||
selected_id={selected.data.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : blueprint ? (
|
) : blueprint ? (
|
||||||
<Markdown>{blueprint_page.description_markdown}</Markdown>
|
<Markdown>{blueprint_page.description_markdown}</Markdown>
|
||||||
) : null}
|
) : null}
|
||||||
@ -216,12 +207,10 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
{selected.type === "blueprint" && data?.blueprint && (
|
{selected.type === "blueprint" && data?.blueprint && (
|
||||||
<Panel
|
<Panel
|
||||||
title={
|
title={
|
||||||
(
|
<span>
|
||||||
<span>
|
Entities for{" "}
|
||||||
Entities for{" "}
|
{data.blueprint.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
|
||||||
{data.blueprint.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
|
</span>
|
||||||
</span>
|
|
||||||
) as any
|
|
||||||
}
|
}
|
||||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
||||||
>
|
>
|
||||||
@ -257,7 +246,10 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
</StyledTable>
|
</StyledTable>
|
||||||
</Panel>
|
</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" />}
|
{blueprintString && <CopyButton content={blueprintString} marginBottom="0.5rem" />}
|
||||||
<textarea
|
<textarea
|
||||||
@ -370,6 +362,7 @@ export const getServerSideProps = pageHandler(async (context, { session }) => {
|
|||||||
id: selected_blueprint.id,
|
id: selected_blueprint.id,
|
||||||
blueprint_hash: selected_blueprint.blueprint_hash,
|
blueprint_hash: selected_blueprint.blueprint_hash,
|
||||||
image_hash: selected_blueprint.image_hash,
|
image_hash: selected_blueprint.image_hash,
|
||||||
|
label: selected_blueprint.label,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (selected_blueprint_book) {
|
} else if (selected_blueprint_book) {
|
||||||
@ -378,20 +371,17 @@ export const getServerSideProps = pageHandler(async (context, { session }) => {
|
|||||||
data: {
|
data: {
|
||||||
id: selected_blueprint_book.id,
|
id: selected_blueprint_book.id,
|
||||||
blueprint_hash: selected_blueprint_book.blueprint_hash,
|
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
|
const favorite = session
|
||||||
? !!(await isBlueprintPageUserFavorite(session.user.id, blueprint_page.id))
|
? !!(await isBlueprintPageUserFavorite(session.user.id, blueprint_page.id))
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
image_exists: false,
|
|
||||||
blueprint,
|
blueprint,
|
||||||
blueprint_book,
|
blueprint_book,
|
||||||
selected,
|
selected,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { NextPage, NextPageContext } from "next";
|
import { NextPage, NextPageContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { 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 { SimpleGrid, Box, RadioGroup, Stack, Radio } from "@chakra-ui/react";
|
||||||
|
import { BlueprintPage } from "@factorio-sites/types";
|
||||||
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 { BlueprintLink } from "../components/BlueprintLink";
|
import { BlueprintLink } from "../components/BlueprintLink";
|
||||||
import { TagsSelect } from "../components/TagsSelect";
|
import { TagsSelect } from "../components/TagsSelect";
|
||||||
import { queryValueAsArray } from "../utils/query.utils";
|
import { queryValueAsArray } from "../utils/query.utils";
|
||||||
@ -67,10 +68,12 @@ export const Index: NextPage<IndexProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box css={{ display: "flex" }}>
|
||||||
{blueprints.map((bp) => (
|
{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} />
|
<Pagination page={currentPage} totalPages={totalPages} totalItems={totalItems} />
|
||||||
</Box>
|
</Box>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -14,11 +14,12 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Formik, Field } from "formik";
|
import { Formik, Field } from "formik";
|
||||||
import { css } from "@emotion/react";
|
import { css } from "@emotion/react";
|
||||||
|
import { chakraResponsive } from "@factorio-sites/web-utils";
|
||||||
import { Panel } from "../../components/Panel";
|
import { Panel } from "../../components/Panel";
|
||||||
import { validateCreateBlueprintForm } from "../../utils/validate";
|
import { validateCreateBlueprintForm } from "../../utils/validate";
|
||||||
import { useAuth } from "../../providers/auth";
|
import { useAuth } from "../../providers/auth";
|
||||||
import { ImageEditor } from "../../components/ImageEditor";
|
import { ImageEditor } from "../../components/ImageEditor";
|
||||||
import { chakraResponsive } from "@factorio-sites/web-utils";
|
import { TagsSelect } from "../../components/TagsSelect";
|
||||||
|
|
||||||
const FieldStyle = css`
|
const FieldStyle = css`
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
@ -35,7 +36,7 @@ export const UserBlueprintCreate: NextPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div css={{ margin: "0.7rem" }}>
|
<div css={{ margin: "0.7rem" }}>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{ title: "", description: "", string: "" }}
|
initialValues={{ title: "", description: "", string: "", tags: [] }}
|
||||||
validate={validateCreateBlueprintForm}
|
validate={validateCreateBlueprintForm}
|
||||||
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
||||||
setStatus("");
|
setStatus("");
|
||||||
@ -57,7 +58,7 @@ export const UserBlueprintCreate: NextPage = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isSubmitting, handleSubmit, status, values, errors }) => (
|
{({ isSubmitting, handleSubmit, status, values, errors, setFieldValue }) => (
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
columns={2}
|
columns={2}
|
||||||
gap={6}
|
gap={6}
|
||||||
@ -97,14 +98,12 @@ export const UserBlueprintCreate: NextPage = () => {
|
|||||||
|
|
||||||
<Field name="tags">
|
<Field name="tags">
|
||||||
{({ field, meta }: any) => (
|
{({ field, meta }: any) => (
|
||||||
<FormControl
|
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
|
||||||
id="tags"
|
<FormLabel>Tags</FormLabel>
|
||||||
// isRequired
|
<TagsSelect
|
||||||
isInvalid={meta.touched && meta.error}
|
value={field.value}
|
||||||
css={FieldStyle}
|
onChange={(tags) => setFieldValue("tags", tags)}
|
||||||
>
|
/>
|
||||||
<FormLabel>Tags (coming soon)</FormLabel>
|
|
||||||
<Input type="text" {...field} disabled />
|
|
||||||
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
@ -14,35 +14,25 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Textarea,
|
Textarea,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import MultiSelect from "react-multi-select-component";
|
|
||||||
import { chakraResponsive } from "@factorio-sites/web-utils";
|
import { chakraResponsive } from "@factorio-sites/web-utils";
|
||||||
import {
|
import {
|
||||||
Blueprint,
|
|
||||||
BlueprintBook,
|
|
||||||
BlueprintPage,
|
|
||||||
getBlueprintBookById,
|
getBlueprintBookById,
|
||||||
getBlueprintById,
|
getBlueprintById,
|
||||||
getBlueprintPageById,
|
getBlueprintPageById,
|
||||||
getBlueprintStringByHash,
|
getBlueprintStringByHash,
|
||||||
} from "@factorio-sites/database";
|
} from "@factorio-sites/database";
|
||||||
|
import { Blueprint, BlueprintBook, BlueprintPage } from "@factorio-sites/types";
|
||||||
import { pageHandler } from "../../../utils/page-handler";
|
import { pageHandler } from "../../../utils/page-handler";
|
||||||
import { Panel } from "../../../components/Panel";
|
import { Panel } from "../../../components/Panel";
|
||||||
import { validateCreateBlueprintForm } from "../../../utils/validate";
|
import { validateCreateBlueprintForm } from "../../../utils/validate";
|
||||||
import { useAuth } from "../../../providers/auth";
|
import { useAuth } from "../../../providers/auth";
|
||||||
import { ImageEditor } from "../../../components/ImageEditor";
|
import { ImageEditor } from "../../../components/ImageEditor";
|
||||||
import { useFbeData } from "../../../hooks/fbe.hook";
|
import { TagsSelect } from "../../../components/TagsSelect";
|
||||||
|
|
||||||
const FieldStyle = css`
|
const FieldStyle = css`
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// const TAGS = [
|
|
||||||
// { value: "foo", label: "foo" },
|
|
||||||
// { value: "bar", label: "bar" },
|
|
||||||
// { value: "x", label: "x" },
|
|
||||||
// { value: "y", label: "y" },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
type Selected =
|
type Selected =
|
||||||
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash">; string: string }
|
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash">; string: string }
|
||||||
| { type: "blueprint_book"; data: Pick<BlueprintBook, "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 }) => {
|
export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, selected }) => {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data } = useFbeData();
|
|
||||||
|
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
@ -62,15 +51,6 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
|||||||
|
|
||||||
if (!blueprintPage) return null;
|
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 (
|
return (
|
||||||
<div css={{ margin: "0.7rem" }}>
|
<div css={{ margin: "0.7rem" }}>
|
||||||
<Formik
|
<Formik
|
||||||
@ -78,7 +58,7 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
|||||||
title: blueprintPage.title,
|
title: blueprintPage.title,
|
||||||
description: blueprintPage.description_markdown,
|
description: blueprintPage.description_markdown,
|
||||||
string: selected.string,
|
string: selected.string,
|
||||||
tags: [] as { value: string; label: string }[],
|
tags: [] as string[],
|
||||||
}}
|
}}
|
||||||
validate={validateCreateBlueprintForm}
|
validate={validateCreateBlueprintForm}
|
||||||
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
||||||
@ -90,7 +70,6 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...values,
|
...values,
|
||||||
id: blueprintPage.id,
|
id: blueprintPage.id,
|
||||||
tags: values.tags.map((tag) => tag.value),
|
|
||||||
}),
|
}),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
|
|
||||||
@ -147,13 +126,9 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
|||||||
{({ field, meta }: any) => (
|
{({ field, meta }: any) => (
|
||||||
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
|
<FormControl id="tags" isInvalid={meta.touched && meta.error} css={FieldStyle}>
|
||||||
<FormLabel>Tags</FormLabel>
|
<FormLabel>Tags</FormLabel>
|
||||||
<MultiSelect
|
<TagsSelect
|
||||||
css={{ color: "black" }}
|
|
||||||
options={TAGS}
|
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(value: any) => setFieldValue("tags", value)}
|
onChange={(tags) => setFieldValue("tags", tags)}
|
||||||
labelledBy="Select"
|
|
||||||
hasSelectAll={false}
|
|
||||||
/>
|
/>
|
||||||
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
<FormErrorMessage>{meta.error}</FormErrorMessage>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { Button, SimpleGrid, Box } from "@chakra-ui/react";
|
|
||||||
import { Panel } from "../../components/Panel";
|
|
||||||
import Link from "next/link";
|
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 { pageHandler } from "../../utils/page-handler";
|
||||||
import { BlueprintPage, getBlueprintPageByUserId } from "@factorio-sites/database";
|
|
||||||
import { BlueprintLink } from "../../components/BlueprintLink";
|
import { BlueprintLink } from "../../components/BlueprintLink";
|
||||||
|
import { Panel } from "../../components/Panel";
|
||||||
interface UserBlueprintsProps {
|
interface UserBlueprintsProps {
|
||||||
blueprints: BlueprintPage[];
|
blueprints: BlueprintPage[];
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ export const UserBlueprints: NextPage<UserBlueprintsProps> = ({ blueprints }) =>
|
|||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{blueprints.map((bp) => (
|
{blueprints.map((bp) => (
|
||||||
<BlueprintLink key={bp.id} blueprint={bp} editLink />
|
<BlueprintLink key={bp.id} blueprint={bp} editLink type="row" />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getSessionByToken, init } from "@factorio-sites/database";
|
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;
|
type Await<T> = T extends PromiseLike<infer U> ? Await<U> : T;
|
||||||
|
|
||||||
@ -10,6 +10,19 @@ interface CustomContext {
|
|||||||
useragent: string;
|
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 = (
|
export const apiHandler = (
|
||||||
fn: (req: NextApiRequest, res: NextApiResponse, ctx: CustomContext) => Promise<any>
|
fn: (req: NextApiRequest, res: NextApiResponse, ctx: CustomContext) => Promise<any>
|
||||||
) => async (req: NextApiRequest, res: NextApiResponse) => {
|
) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@ -25,5 +38,13 @@ export const apiHandler = (
|
|||||||
deleteSessionToken(res);
|
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
|
FROM node:14-slim as builder
|
||||||
|
|
||||||
|
RUN apt-get -qy update && apt-get -qy install openssl
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
COPY fbe-editor-v1.0.0.tgz .
|
COPY yalc.lock .
|
||||||
|
COPY .yalc ./.yalc/
|
||||||
|
|
||||||
RUN yarn
|
RUN yarn
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn run db-gen
|
||||||
RUN yarn nx build blueprints
|
RUN yarn nx build blueprints
|
||||||
|
|
||||||
FROM node:14-slim
|
FROM node:14-slim
|
||||||
|
|
||||||
|
RUN apt-get -qy update && apt-get -qy install openssl
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY apps/blueprints/prod.package.json ./package.json
|
COPY apps/blueprints/prod.package.json ./package.json
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
COPY fbe-editor-v1.0.0.tgz .
|
COPY yalc.lock .
|
||||||
|
COPY .yalc ./.yalc/
|
||||||
|
|
||||||
RUN yarn install --production
|
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 .
|
COPY --from=builder /usr/src/app/dist/apps/blueprints .
|
||||||
|
|
||||||
CMD ["yarn", "next", "start"]
|
CMD ["yarn", "next", "start"]
|
@ -7,5 +7,6 @@ module.exports = {
|
|||||||
"<rootDir>/libs/utils",
|
"<rootDir>/libs/utils",
|
||||||
"<rootDir>/libs/common-utils",
|
"<rootDir>/libs/common-utils",
|
||||||
"<rootDir>/libs/web-utils",
|
"<rootDir>/libs/web-utils",
|
||||||
|
"<rootDir>/libs/types",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -2,4 +2,3 @@ export * from "./lib/postgres/database";
|
|||||||
export * from "./lib/data";
|
export * from "./lib/data";
|
||||||
export * from "./lib/gcp-pubsub";
|
export * from "./lib/gcp-pubsub";
|
||||||
export * from "./lib/gcp-storage";
|
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 { blueprint as BlueprintModel } from "@prisma/client";
|
||||||
import { saveBlueprintString } from "../gcp-storage";
|
import { saveBlueprintString } from "../gcp-storage";
|
||||||
import { prisma } from "../postgres/database";
|
import { prisma } from "../postgres/database";
|
||||||
import { Blueprint } from "../types";
|
import { Blueprint } from "@factorio-sites/types";
|
||||||
|
|
||||||
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||||
|
|
||||||
@ -36,21 +36,19 @@ export async function createBlueprint(
|
|||||||
created_at?: number;
|
created_at?: number;
|
||||||
updated_at?: number;
|
updated_at?: number;
|
||||||
}
|
}
|
||||||
) {
|
): Promise<Blueprint> {
|
||||||
const string = await encodeBlueprint({ blueprint });
|
const string = await encodeBlueprint({ blueprint });
|
||||||
const blueprint_hash = hashString(string);
|
const blueprint_hash = hashString(string);
|
||||||
const image_hash = hashString(getBlueprintContentForImageHash(blueprint));
|
const image_hash = hashString(getBlueprintContentForImageHash(blueprint));
|
||||||
|
|
||||||
const exists = await getBlueprintByHash(blueprint_hash);
|
const exists = await getBlueprintByHash(blueprint_hash);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return { insertedId: exists.id };
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write string to google storage
|
// Write string to google storage
|
||||||
await saveBlueprintString(blueprint_hash, string);
|
await saveBlueprintString(blueprint_hash, string);
|
||||||
|
|
||||||
// Write blueprint details to datastore
|
|
||||||
|
|
||||||
const result = await prisma.blueprint.create({
|
const result = await prisma.blueprint.create({
|
||||||
data: {
|
data: {
|
||||||
label: blueprint.label,
|
label: blueprint.label,
|
||||||
@ -71,5 +69,5 @@ export async function createBlueprint(
|
|||||||
// blueprintId: result.id,
|
// 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 { blueprint_book } from "@prisma/client";
|
||||||
import { saveBlueprintString } from "../gcp-storage";
|
import { saveBlueprintString } from "../gcp-storage";
|
||||||
import { prisma } from "../postgres/database";
|
import { prisma } from "../postgres/database";
|
||||||
import { BlueprintBook, ChildTree } from "../types";
|
import { BlueprintBook, ChildTree } from "@factorio-sites/types";
|
||||||
import { createBlueprint } from "./blueprint";
|
import { createBlueprint } from "./blueprint";
|
||||||
|
|
||||||
const mapBlueprintBookEntityToObject = (entity: blueprint_book): BlueprintBook => ({
|
const mapBlueprintBookEntityToObject = (entity: blueprint_book): BlueprintBook => ({
|
||||||
@ -34,7 +34,7 @@ export async function createBlueprintBook(
|
|||||||
created_at?: number;
|
created_at?: number;
|
||||||
updated_at?: number;
|
updated_at?: number;
|
||||||
}
|
}
|
||||||
): Promise<{ insertedId: string; child_tree: ChildTree }> {
|
): Promise<BlueprintBook> {
|
||||||
const string = await encodeBlueprint({ blueprint_book: blueprintBook });
|
const string = await encodeBlueprint({ blueprint_book: blueprintBook });
|
||||||
const blueprint_hash = hashString(string);
|
const blueprint_hash = hashString(string);
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export async function createBlueprintBook(
|
|||||||
if (exists) {
|
if (exists) {
|
||||||
const book = await getBlueprintBookById(exists.id);
|
const book = await getBlueprintBookById(exists.id);
|
||||||
if (!book) throw Error("this is impossible, just pleasing typescript");
|
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
|
// Write string to google storage
|
||||||
@ -59,19 +59,19 @@ export async function createBlueprintBook(
|
|||||||
const result = await createBlueprint(blueprint.blueprint, extraInfo);
|
const result = await createBlueprint(blueprint.blueprint, extraInfo);
|
||||||
child_tree.push({
|
child_tree.push({
|
||||||
type: "blueprint",
|
type: "blueprint",
|
||||||
id: result.insertedId,
|
id: result.id,
|
||||||
name: blueprint.blueprint.label,
|
name: blueprint.blueprint.label,
|
||||||
});
|
});
|
||||||
blueprint_ids.push(result.insertedId);
|
blueprint_ids.push(result.id);
|
||||||
} else if (blueprint.blueprint_book) {
|
} else if (blueprint.blueprint_book) {
|
||||||
const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo);
|
const result = await createBlueprintBook(blueprint.blueprint_book, extraInfo);
|
||||||
child_tree.push({
|
child_tree.push({
|
||||||
type: "blueprint_book",
|
type: "blueprint_book",
|
||||||
id: result.insertedId,
|
id: result.id,
|
||||||
name: blueprint.blueprint_book.label,
|
name: blueprint.blueprint_book.label,
|
||||||
children: result.child_tree,
|
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,
|
child_tree: child_tree as any,
|
||||||
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
|
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(),
|
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}`);
|
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 { blueprint_page } from "@prisma/client";
|
||||||
import { join, raw, sqltag } from "@prisma/client/runtime";
|
import { join, raw, sqltag } from "@prisma/client/runtime";
|
||||||
|
import { getBlueprintImageRequestTopic } from "../gcp-pubsub";
|
||||||
import { prisma } from "../postgres/database";
|
import { prisma } from "../postgres/database";
|
||||||
import { BlueprintPage } from "../types";
|
import { BlueprintPage } from "@factorio-sites/types";
|
||||||
|
|
||||||
const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage => ({
|
const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage => ({
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
@ -11,6 +11,7 @@ const mapBlueprintPageEntityToObject = (entity: blueprint_page): BlueprintPage =
|
|||||||
title: entity.title,
|
title: entity.title,
|
||||||
description_markdown: entity.description_markdown || "",
|
description_markdown: entity.description_markdown || "",
|
||||||
tags: entity.tags,
|
tags: entity.tags,
|
||||||
|
image_hash: entity.image_hash,
|
||||||
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,
|
||||||
@ -78,30 +79,44 @@ export async function searchBlueprintPages({
|
|||||||
export async function createBlueprintPage(
|
export async function createBlueprintPage(
|
||||||
type: "blueprint" | "blueprint_book",
|
type: "blueprint" | "blueprint_book",
|
||||||
targetId: string,
|
targetId: string,
|
||||||
extraInfo: {
|
data: {
|
||||||
title: string;
|
title: string;
|
||||||
user_id: string | null;
|
user_id: string | null;
|
||||||
description_markdown: string;
|
description_markdown: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
image_hash: string;
|
||||||
created_at?: number;
|
created_at?: number;
|
||||||
updated_at?: number;
|
updated_at?: number;
|
||||||
|
firstBlueprintId?: string;
|
||||||
factorioprints_id?: string;
|
factorioprints_id?: string;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const page = await prisma.blueprint_page.create({
|
const page = await prisma.blueprint_page.create({
|
||||||
data: {
|
data: {
|
||||||
user_id: extraInfo.user_id,
|
user_id: data.user_id,
|
||||||
title: extraInfo.title,
|
title: data.title,
|
||||||
description_markdown: extraInfo.description_markdown,
|
description_markdown: data.description_markdown,
|
||||||
factorioprints_id: extraInfo.factorioprints_id,
|
factorioprints_id: data.factorioprints_id,
|
||||||
blueprint_id: type === "blueprint" ? targetId : undefined,
|
blueprint_id: type === "blueprint" ? targetId : undefined,
|
||||||
blueprint_book_id: type === "blueprint_book" ? targetId : undefined,
|
blueprint_book_id: type === "blueprint_book" ? targetId : undefined,
|
||||||
tags: extraInfo.tags ? extraInfo.tags : [],
|
tags: data.tags ? data.tags : [],
|
||||||
updated_at: extraInfo.updated_at ? new Date(extraInfo.updated_at * 1000) : new Date(),
|
image_hash: data.image_hash,
|
||||||
created_at: extraInfo.created_at ? new Date(extraInfo.created_at * 1000) : new Date(),
|
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`);
|
console.log(`Created Blueprint Page`);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,20 @@ export async function saveBlueprintFromFactorioprints(
|
|||||||
|
|
||||||
if (parsed.data.blueprint) {
|
if (parsed.data.blueprint) {
|
||||||
console.log("string has one blueprint...");
|
console.log("string has one blueprint...");
|
||||||
const { insertedId } = await createBlueprint(parsed.data.blueprint, extraInfo);
|
const result = await createBlueprint(parsed.data.blueprint, extraInfo);
|
||||||
await createBlueprintPage("blueprint", insertedId, extraInfoPage);
|
await createBlueprintPage("blueprint", result.id, {
|
||||||
|
...extraInfoPage,
|
||||||
|
firstBlueprintId: result.id,
|
||||||
|
image_hash: "",
|
||||||
|
});
|
||||||
} else if (parsed.data.blueprint_book) {
|
} else if (parsed.data.blueprint_book) {
|
||||||
console.log("string has a blueprint book...");
|
console.log("string has a blueprint book...");
|
||||||
const { insertedId } = await createBlueprintBook(parsed.data.blueprint_book, extraInfo);
|
const result = await createBlueprintBook(parsed.data.blueprint_book, extraInfo);
|
||||||
await createBlueprintPage("blueprint_book", insertedId, extraInfoPage);
|
await createBlueprintPage("blueprint_book", result.id, {
|
||||||
|
...extraInfoPage,
|
||||||
|
firstBlueprintId: undefined,
|
||||||
|
image_hash: "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { PubSub, Message } from "@google-cloud/pubsub";
|
import { PubSub, Message } from "@google-cloud/pubsub";
|
||||||
// export { Message } from "@google-cloud/pubsub";
|
|
||||||
|
|
||||||
const pubsub = new PubSub();
|
const pubsub = new PubSub();
|
||||||
|
|
||||||
|
@ -22,13 +22,30 @@ export async function saveBlueprintString(hash: string, content: string) {
|
|||||||
* BlueprintImage
|
* BlueprintImage
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function saveBlueprintImage(hash: string, image: Buffer): Promise<void> {
|
type sizeType = "original" | "300";
|
||||||
return IMAGE_BUCKET.file(`${hash}.webp`).save(image, {
|
|
||||||
|
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",
|
contentType: "image/webp",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hasBlueprintImage(hash: string): Promise<boolean> {
|
export async function hasBlueprintImage(
|
||||||
const [result] = await IMAGE_BUCKET.file(`${hash}.webp`).exists();
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -38,4 +38,7 @@ const promise = _init()
|
|||||||
console.log("Database failed to init!", reason);
|
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 crypto from "crypto";
|
||||||
import * as pako from "pako";
|
import * as pako from "pako";
|
||||||
import * as cookie from "cookie";
|
import * as cookie from "cookie";
|
||||||
import { BlueprintStringData } from "@factorio-sites/common-utils";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
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 (
|
export const parseBlueprintString = async (
|
||||||
string: string
|
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;
|
title: string;
|
||||||
description_markdown: string;
|
description_markdown: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
image_hash: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
factorioprints_id: string | null;
|
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",
|
"npmScope": "factorio-sites",
|
||||||
"affected": {
|
"affected": { "defaultBase": "master" },
|
||||||
"defaultBase": "master"
|
|
||||||
},
|
|
||||||
"implicitDependencies": {
|
"implicitDependencies": {
|
||||||
"workspace.json": "*",
|
"workspace.json": "*",
|
||||||
"package.json": {
|
"package.json": { "dependencies": "*", "devDependencies": "*" },
|
||||||
"dependencies": "*",
|
|
||||||
"devDependencies": "*"
|
|
||||||
},
|
|
||||||
"tsconfig.base.json": "*",
|
"tsconfig.base.json": "*",
|
||||||
"tslint.json": "*",
|
"tslint.json": "*",
|
||||||
".eslintrc.json": "*",
|
".eslintrc.json": "*",
|
||||||
@ -17,36 +12,18 @@
|
|||||||
"tasksRunnerOptions": {
|
"tasksRunnerOptions": {
|
||||||
"default": {
|
"default": {
|
||||||
"runner": "@nrwl/workspace/tasks-runners/default",
|
"runner": "@nrwl/workspace/tasks-runners/default",
|
||||||
"options": {
|
"options": { "cacheableOperations": ["build", "lint", "test", "e2e"] }
|
||||||
"cacheableOperations": ["build", "lint", "test", "e2e"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"blueprints": {
|
"blueprints": { "tags": [] },
|
||||||
"tags": []
|
"blueprints-e2e": { "tags": [], "implicitDependencies": ["blueprints"] },
|
||||||
},
|
"blueprint-image-function": { "tags": [] },
|
||||||
"blueprints-e2e": {
|
"factorioprints-scraper": { "tags": [] },
|
||||||
"tags": [],
|
"database": { "tags": [] },
|
||||||
"implicitDependencies": ["blueprints"]
|
"node-utils": { "tags": [] },
|
||||||
},
|
"common-utils": { "tags": [] },
|
||||||
"blueprint-image-function": {
|
"web-utils": { "tags": [] },
|
||||||
"tags": []
|
"types": { "tags": [] }
|
||||||
},
|
|
||||||
"factorioprints-scraper": {
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"node-utils": {
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"common-utils": {
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"web-utils": {
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
"@factorio-sites/database": ["libs/database/src/index.ts"],
|
"@factorio-sites/database": ["libs/database/src/index.ts"],
|
||||||
"@factorio-sites/node-utils": ["libs/node-utils/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/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"]
|
"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": {
|
"cli": {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"version": "v1",
|
"version": "v1",
|
||||||
"packages": {
|
"packages": {
|
||||||
"@fbe/editor": {
|
"@fbe/editor": {
|
||||||
"signature": "77d812874aacc6417c4ac5324c11cddf",
|
"signature": "e5aba5c824b95e84453566186e48efe8",
|
||||||
"file": true,
|
"file": true,
|
||||||
"replaced": "./fbe-editor-v1.0.0.tgz"
|
"replaced": "./fbe-editor-v1.0.0.tgz"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user