mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2025-03-17 21:17:57 +02:00
Feat: improve blueprint creation, add comments to blueprints
This commit is contained in:
parent
2a415f0c6e
commit
e18cd37245
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@
|
||||
/out-tsc
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
|
@ -0,0 +1,21 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "comment" (
|
||||
"id" UUID NOT NULL,
|
||||
"blueprint_page_id" UUID NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"reply_comment_id" UUID,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment" ADD FOREIGN KEY ("blueprint_page_id") REFERENCES "blueprint_page"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment" ADD FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment" ADD FOREIGN KEY ("reply_comment_id") REFERENCES "comment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -16,7 +16,7 @@ enum enum_user_role {
|
||||
model blueprint {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
label String? @db.VarChar(255)
|
||||
description String?
|
||||
description String? @db.Text
|
||||
game_version String? @db.VarChar(255)
|
||||
blueprint_hash String @unique @db.VarChar(40)
|
||||
image_hash String @db.VarChar(40)
|
||||
@ -32,7 +32,7 @@ model blueprint {
|
||||
model blueprint_book {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
label String? @db.VarChar(255)
|
||||
description String?
|
||||
description String? @db.Text
|
||||
child_tree Json @db.JsonB
|
||||
blueprint_hash String @unique @db.VarChar(40)
|
||||
is_modded Boolean
|
||||
@ -51,7 +51,7 @@ model blueprint_page {
|
||||
blueprint_book_id String? @unique @db.Uuid
|
||||
blueprint_ids String[] @db.Uuid
|
||||
title String @db.VarChar(255)
|
||||
description_markdown String?
|
||||
description_markdown String? @db.Text
|
||||
tags String[] @db.VarChar(255)
|
||||
image_hash String @db.VarChar(40)
|
||||
factorioprints_id String? @unique @db.VarChar(255)
|
||||
@ -61,6 +61,7 @@ model blueprint_page {
|
||||
blueprint blueprint? @relation(fields: [blueprint_id], references: [id])
|
||||
user_favorites user_favorites[]
|
||||
user user? @relation(fields: [user_id], references: [id])
|
||||
comments comment[]
|
||||
}
|
||||
|
||||
model session {
|
||||
@ -94,6 +95,7 @@ model user {
|
||||
session session[]
|
||||
user_favorites user_favorites[]
|
||||
blueprint_pages blueprint_page[]
|
||||
comment comment[]
|
||||
}
|
||||
|
||||
model user_favorites {
|
||||
@ -107,6 +109,20 @@ model user_favorites {
|
||||
@@id([user_id, blueprint_page_id])
|
||||
}
|
||||
|
||||
model comment {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
blueprint_page_id String @db.Uuid
|
||||
user_id String @db.Uuid
|
||||
body String @db.Text
|
||||
reply_comment_id String? @db.Uuid
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
blueprint_page blueprint_page @relation(fields: [blueprint_page_id], references: [id])
|
||||
user user @relation(fields: [user_id], references: [id])
|
||||
reply_to comment? @relation("commentToComment", fields: [reply_comment_id], references: [id])
|
||||
replies comment[] @relation("commentToComment")
|
||||
}
|
||||
|
||||
model blueprint_entities {
|
||||
entity String @unique
|
||||
}
|
||||
|
114
apps/blueprints/src/components/Comments.tsx
Normal file
114
apps/blueprints/src/components/Comments.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { FormEventHandler, useEffect, useState } from "react";
|
||||
import { CommentWithUsername } from "@factorio-sites/types";
|
||||
import { useAuth } from "../providers/auth";
|
||||
import { Button } from "./Button";
|
||||
import styled from "@emotion/styled";
|
||||
import { format } from "date-fns";
|
||||
import { getLocaleDateFormat } from "@factorio-sites/web-utils";
|
||||
|
||||
interface CommentsProps {
|
||||
blueprint_page_id: string;
|
||||
}
|
||||
|
||||
const AddCommentDiv = styled.div`
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
textarea {
|
||||
color: #fff;
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
background: #414040;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 400px;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.close {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const CommentDiv = styled.div`
|
||||
background: #4e4c4c;
|
||||
margin: 0.5rem 0;
|
||||
`;
|
||||
|
||||
export const Comments: React.FC<CommentsProps> = ({ blueprint_page_id }) => {
|
||||
const auth = useAuth();
|
||||
const [addCommentOpen, setAddCommentOpen] = useState(false);
|
||||
const [comments, setComments] = useState<CommentWithUsername[]>([]);
|
||||
const [commentBody, setCommentBody] = useState("");
|
||||
|
||||
const fetchTopLevelComments = async () => {
|
||||
const result = await fetch(`/api/blueprint/comments?blueprint_page_id=${blueprint_page_id}`);
|
||||
const body = await result.json();
|
||||
setComments(body.comments);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTopLevelComments();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
console.log({ comments });
|
||||
|
||||
const onSubmitComment: FormEventHandler<HTMLFormElement> = async (event) => {
|
||||
event.preventDefault();
|
||||
const result = await fetch("/api/blueprint/comment", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ blueprint_page_id, body: commentBody }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setCommentBody("");
|
||||
setAddCommentOpen(false);
|
||||
console.log("result", await result.json());
|
||||
fetchTopLevelComments();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{auth && (
|
||||
<AddCommentDiv>
|
||||
{addCommentOpen ? (
|
||||
<form onSubmit={onSubmitComment}>
|
||||
<div>Add a new comment</div>
|
||||
<textarea
|
||||
value={commentBody}
|
||||
onChange={(event) => setCommentBody(event.target.value)}
|
||||
/>
|
||||
<Button primary type="submit">
|
||||
Send
|
||||
</Button>
|
||||
<Button type="button" className="close" onClick={() => setAddCommentOpen(false)}>
|
||||
close
|
||||
</Button>
|
||||
</form>
|
||||
) : (
|
||||
<Button onClick={() => setAddCommentOpen(true)}>Add comment</Button>
|
||||
)}
|
||||
</AddCommentDiv>
|
||||
)}
|
||||
{comments?.length ? (
|
||||
comments.map((comment) => (
|
||||
<CommentDiv key={comment.id}>
|
||||
<div>
|
||||
{comment.user.username} at{" "}
|
||||
{format(new Date(comment.created_at), getLocaleDateFormat() + " HH:mm")}
|
||||
</div>
|
||||
<div>{comment.body}</div>
|
||||
</CommentDiv>
|
||||
))
|
||||
) : (
|
||||
<div>There are no comments yet</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -6,6 +6,15 @@ const StyledTooltip = styled.span`
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
|
||||
&:active:after {
|
||||
content: attr(title);
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
top: 5px;
|
||||
right: 10%;
|
||||
background: #bada55;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface TooltipProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
|
@ -18,6 +18,7 @@ import { BlueprintImage, RENDERERS } from "./BlueprintImage";
|
||||
import { useAuth } from "../../providers/auth";
|
||||
import { Button } from "../Button";
|
||||
import { PUBLIC_URL } from "../../utils/env";
|
||||
import { Comments } from "../Comments";
|
||||
|
||||
const StyledBlueptintPage = styled(Grid)`
|
||||
grid-gap: 16px;
|
||||
@ -133,7 +134,7 @@ export const BlueprintSubPage: React.FC<BlueprintProps> = ({
|
||||
{string && (
|
||||
<BlueprintImage
|
||||
string={string}
|
||||
label={blueprint.label}
|
||||
label={blueprint.label || ""}
|
||||
blueprint_hash={blueprint.blueprint_hash}
|
||||
onSetRenderer={setRenderer}
|
||||
/>
|
||||
@ -184,6 +185,10 @@ export const BlueprintSubPage: React.FC<BlueprintProps> = ({
|
||||
{data && <BlueprintEntities data={data} />}
|
||||
</Panel>
|
||||
|
||||
<Panel title="Comments" gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 4" })}>
|
||||
<Comments blueprint_page_id={blueprint_page.id} />
|
||||
</Panel>
|
||||
|
||||
<Panel
|
||||
title="Blueprint data"
|
||||
className="bp-strings"
|
||||
|
@ -30,6 +30,7 @@ import { BlueprintImage, RENDERERS } from "./BlueprintImage";
|
||||
import { Button } from "../Button";
|
||||
import { useAuth } from "../../providers/auth";
|
||||
import { PUBLIC_URL } from "../../utils/env";
|
||||
import { Comments } from "../Comments";
|
||||
|
||||
const StyledBlueptintPage = styled(Grid)`
|
||||
grid-gap: 16px;
|
||||
@ -206,7 +207,7 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
{selectedBlueprintString && (
|
||||
<BlueprintImage
|
||||
string={selectedBlueprintString}
|
||||
label={selected.data.label}
|
||||
label={selected.data.label || ""}
|
||||
blueprint_hash={selected.data.blueprint_hash}
|
||||
onSetRenderer={setRenderer}
|
||||
/>
|
||||
@ -261,6 +262,10 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
<Panel title="Comments" gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 4" })}>
|
||||
<Comments blueprint_page_id={blueprint_page.id} />
|
||||
</Panel>
|
||||
|
||||
<Panel
|
||||
className="bp-strings"
|
||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 4" })}
|
||||
|
20
apps/blueprints/src/pages/api/blueprint/comment.ts
Normal file
20
apps/blueprints/src/pages/api/blueprint/comment.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { apiHandler } from "../../../utils/api-handler";
|
||||
import { createComment } from "@factorio-sites/database";
|
||||
|
||||
const handler = apiHandler(async (req, res, { session }) => {
|
||||
if (req.method !== "POST") return res.status(400).json({ error: "method must be POST" });
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).json({ status: "Not authenticated" });
|
||||
}
|
||||
|
||||
const { body, blueprint_page_id } = req.body;
|
||||
console.log({ body, blueprint_page_id });
|
||||
|
||||
const result = await createComment(blueprint_page_id, session.user, body);
|
||||
console.log(result);
|
||||
|
||||
res.status(200).json({ status: "Comment submitted" });
|
||||
});
|
||||
|
||||
export default handler;
|
17
apps/blueprints/src/pages/api/blueprint/comments.ts
Normal file
17
apps/blueprints/src/pages/api/blueprint/comments.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { apiHandler } from "../../../utils/api-handler";
|
||||
import { getComments } from "@factorio-sites/database";
|
||||
|
||||
const handler = apiHandler(async (req, res, { session }) => {
|
||||
if (req.method !== "GET") return res.status(400).json({ error: "method must be GET" });
|
||||
|
||||
const blueprint_page_id = req.query.blueprint_page_id as string;
|
||||
|
||||
if (!blueprint_page_id) return res.status(400).json({ status: "blueprint_page_id required" });
|
||||
|
||||
const comments = await getComments(blueprint_page_id);
|
||||
console.log(comments);
|
||||
|
||||
res.status(200).json({ comments });
|
||||
});
|
||||
|
||||
export default handler;
|
@ -3,7 +3,7 @@ import { blueprintDataToDbData, encodeBlueprint, hashString } from "@factorio-si
|
||||
import { blueprint as BlueprintModel } from "@prisma/client";
|
||||
import { saveBlueprintString } from "../gcp-storage";
|
||||
import { prisma } from "../postgres/database";
|
||||
import { Blueprint, BlueprintData } from "@factorio-sites/types";
|
||||
import { Blueprint, BlueprintData, DbBlueprintData } from "@factorio-sites/types";
|
||||
|
||||
// const blueprintImageRequestTopic = getBlueprintImageRequestTopic();
|
||||
|
||||
@ -11,12 +11,14 @@ const mapBlueprintInstanceToEntry = (entity: BlueprintModel): Blueprint => ({
|
||||
id: entity.id,
|
||||
blueprint_hash: entity.blueprint_hash,
|
||||
image_hash: entity.image_hash,
|
||||
image_version: entity.image_version,
|
||||
label: entity.label || "",
|
||||
description: entity.description || "",
|
||||
tags: entity.tags,
|
||||
created_at: entity.created_at && entity.created_at.getTime() / 1000,
|
||||
updated_at: entity.updated_at && entity.updated_at.getTime() / 1000,
|
||||
game_version: entity.game_version || null,
|
||||
data: (entity.data as unknown) as DbBlueprintData,
|
||||
});
|
||||
|
||||
export async function getBlueprintById(id: string): Promise<Blueprint | null> {
|
||||
|
34
libs/database/src/lib/data/comment.ts
Normal file
34
libs/database/src/lib/data/comment.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { user } from "@prisma/client";
|
||||
import { prisma } from "../postgres/database";
|
||||
|
||||
export async function createComment(blueprint_page_id: string, user: user, body: string) {
|
||||
const result = await prisma.comment.create({
|
||||
data: {
|
||||
body,
|
||||
blueprint_page_id,
|
||||
user_id: user.id,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getComments(blueprint_page_id: string) {
|
||||
const result = await prisma.comment.findMany({
|
||||
where: {
|
||||
blueprint_page_id,
|
||||
reply_comment_id: null,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
created_at: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
@ -2,4 +2,5 @@ export * from "./user";
|
||||
export * from "./blueprint";
|
||||
export * from "./blueprint_book";
|
||||
export * from "./blueprint_page";
|
||||
export * from "./comment";
|
||||
export * from "./factorioprints";
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Signal } from "./blueprint-string";
|
||||
import { comment, blueprint, user } from "@prisma/client";
|
||||
import { Icon, Signal } from "./blueprint-string";
|
||||
|
||||
export interface ChildTreeBlueprint {
|
||||
type: "blueprint";
|
||||
@ -15,16 +16,10 @@ export interface ChildTreeBlueprintBook {
|
||||
|
||||
export type ChildTree = Array<ChildTreeBlueprint | ChildTreeBlueprintBook>;
|
||||
|
||||
export interface Blueprint {
|
||||
id: string;
|
||||
label: string; // from source
|
||||
description: string | null; // from source
|
||||
game_version: string | null; // from source
|
||||
blueprint_hash: string;
|
||||
image_hash: string;
|
||||
export interface Blueprint extends Omit<blueprint, "data" | "created_at" | "updated_at"> {
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
tags: string[];
|
||||
data: DbBlueprintData | null;
|
||||
}
|
||||
|
||||
export interface BlueprintBook {
|
||||
@ -72,3 +67,7 @@ export interface DbBlueprintData {
|
||||
recipes: Record<string, number>;
|
||||
tiles: Record<string, number>;
|
||||
}
|
||||
|
||||
export type Comment = comment;
|
||||
|
||||
export type CommentWithUsername = comment & { user: Pick<user, "username"> };
|
||||
|
Loading…
x
Reference in New Issue
Block a user