initial commit

This commit is contained in:
Joonas 2023-01-12 17:03:38 +02:00
commit c3cca4e80a
13 changed files with 8149 additions and 0 deletions

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# Welcome to Remix!
- [Remix Docs](https://remix.run/docs)
## Development
From your terminal:
```sh
npm run dev
```
This starts your app in development mode, rebuilding assets on file changes.
## Deployment
First, build your app for production:
```sh
npm run build
```
Then run the app in production mode:
```sh
npm start
```
Now you'll need to pick a host to deploy it to.
### DIY
If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
Make sure to deploy the output of `remix build`
- `build/`
- `public/build/`
### Using a Template
When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server.
```sh
cd ..
# create a new project, and pick a pre-configured host
npx create-remix@latest
cd my-new-remix-app
# remove the new project's app (not the old one!)
rm -rf app
# copy your app over
cp -R ../my-old-remix-app/app app
```

22
app/entry.client.jsx Normal file
View File

@ -0,0 +1,22 @@
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
function hydrate() {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
}
if (typeof requestIdleCallback === "function") {
requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1);
}

111
app/entry.server.jsx Normal file
View File

@ -0,0 +1,111 @@
import { PassThrough } from "stream";
import { Response } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import isbot from "isbot";
import { renderToPipeableStream } from "react-dom/server";
const ABORT_DELAY = 5000;
export default function handleRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
) {
return isbot(request.headers.get("user-agent"))
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
}
function handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
) {
return new Promise((resolve, reject) => {
let didError = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onAllReady() {
const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);
pipe(body);
},
onShellError(error) {
reject(error);
},
onError(error) {
didError = true;
console.error(error);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
function handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
) {
return new Promise((resolve, reject) => {
let didError = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);
pipe(body);
},
onShellError(err) {
reject(err);
},
onError(error) {
didError = true;
console.error(error);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}

31
app/root.jsx Normal file
View File

@ -0,0 +1,31 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
export const meta = () => ({
charset: "utf-8",
title: "New Remix App",
viewport: "width=device-width,initial-scale=1",
});
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}

67
app/routes/index.jsx Normal file
View File

@ -0,0 +1,67 @@
import { useLoaderData, Form } from "@remix-run/react";
import { unstable_createFileUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import prisma from "~/utils/db.server";
import { redirect } from "@remix-run/node";
import { Link } from "@remix-run/react";
export async function action({ request }) {
const clonedData = request.clone();
const formData = await clonedData.formData();
const title = formData.get("title");
const post = formData.get("post");
const fileUploadHandler = unstable_createFileUploadHandler({
directory: './public/uploads/',
maxPartSize: 500000,
file: ({ filename }) => filename,
});
const multiPartformdata = await unstable_parseMultipartFormData(request, fileUploadHandler);
const imageName = multiPartformdata.get("image").name;
const newThread = await prisma.thread.create({
data: {
title: title,
post: post,
imageName: imageName,
},
});
return redirect(`threads/${newThread.id}`);
}
export async function loader() {
const data = await prisma.thread.findMany({
include: {
posts: true,
},
});
return data;
}
export default function Index() {
const data = useLoaderData();
console.log(data)
return (
<div>
<Form method="post" encType="multipart/form-data">
<label htmlFor="">Title: <input required type="text" name="title" /></label>
<br />
<label htmlFor="">Post: <input required type="text" name="post" /></label>
<br />
<label htmlFor="">Image: <input required accept="image/*" type="file" id="image-upload" name="image" /></label>
<br />
<button type="submit">Submit</button>
</Form>
<ul>
{data.map(thread =>
<li key={thread.id}>
<Link to={`threads/${thread.id}`}>{thread.title}</Link>
</li>
)}
</ul>
</div>
);
};

View File

@ -0,0 +1,81 @@
import { redirect, unstable_createFileUploadHandler, unstable_parseMultipartFormData } from "@remix-run/node";
import { useLoaderData, Form } from "@remix-run/react";
import prisma from "~/utils/db.server";
export async function action({ request, params }) {
const threadId = params.threadId;
const clonedData = request.clone();
const formData = await clonedData.formData();
const post = formData.get("post");
const fileUploadHandler = unstable_createFileUploadHandler({
directory: './public/uploads/',
maxPartSize: 500000,
file: ({ filename }) => filename,
});
const multiPartformdata = await unstable_parseMultipartFormData(request, fileUploadHandler);
let imageName;
multiPartformdata.get("image") !== null ? imageName = multiPartformdata.get("image").name : imageName = null;
const createPost = await prisma.post.create({
data: {
comment: post,
imageName: imageName,
postId: parseInt(threadId),
},
});
return redirect(`/threads/${threadId}`);
};
export async function loader({ params }) {
const threadId = params.threadId;
const thread = await prisma.thread.findUnique({
where: {
id: Number(threadId),
},
include: {
posts: true,
},
});
return thread;
}
export default function Thread() {
const data = useLoaderData();
return (
<div>
<div>
<span><strong>{data.title}</strong> Post id: <strong>{data.id}</strong> Created at: <strong>{data.createdAt}</strong> Reply count: <strong>{data.posts.length}</strong></span>
<br />
<a href={`/${data.imageName}`} target="_blank"><img src={`/uploads/${data.imageName}`} alt="post image" width={240} /></a>
<p>{data.post}</p>
<hr />
<ul>
{data.posts.map(post =>
<li key={post.id}>
<span>Reply id: <strong>{post.id}</strong> Replied at: <strong>{post.createdAt}</strong></span>
<br />
{post.imageName !== 'null' ? <img width={240} src={`/uploads/${post.imageName}`} alt="post image" /> : ''}
<p>{post.comment}</p>
</li>
)}
</ul>
</div>
<Form method="post" encType="multipart/form-data">
<label htmlFor="text-input">
Post: <input required minLength={3} name="post" type="text" />
</label>
<label htmlFor="image-upload">
Image: <input type="file" name="image" id="image-upload" />
</label>
<button type="submit">Submit</button>
</Form>
</div>
);
}

14
app/utils/db.server.js Normal file
View File

@ -0,0 +1,14 @@
import { PrismaClient } from "@prisma/client";
let prisma;
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
} else {
if (!global.prisma) {
global.prisma = new PrismaClient();
}
prisma = global.prisma;
}
export default prisma;

20
jsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"include": ["**/*.js", "**/*.jsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"noEmit": true
}
}

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"private": true,
"sideEffects": false,
"scripts": {
"build": "remix build",
"dev": "remix dev",
"start": "remix-serve build"
},
"dependencies": {
"@prisma/client": "^4.8.1",
"@remix-run/node": "^1.10.0",
"@remix-run/react": "^1.10.0",
"@remix-run/serve": "^1.10.0",
"isbot": "^3.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@remix-run/dev": "^1.10.0",
"@remix-run/eslint-config": "^1.10.0",
"eslint": "^8.27.0",
"prisma": "^4.8.1"
},
"engines": {
"node": ">=14"
}
}

7678
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

37
prisma/schema.prisma Normal file
View File

@ -0,0 +1,37 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Thread {
id Int @id @default(autoincrement())
title String
post String
imageName String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
comment String
imageName String?
post Thread @relation(fields: [postId], references: [id])
postId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

8
remix.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
ignoredRouteFiles: ["**/.*"],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",
};