Compare commits

...

21 Commits

Author SHA1 Message Date
d1a6b17920 update README.md 2024-04-09 18:02:32 -04:00
7cf8cef8cf update docker compose 2024-04-09 17:53:50 -04:00
1c4fd23e76 update README.md 2024-04-09 17:51:12 -04:00
dc67c7eb10 use variable for api base url 2024-04-09 17:50:06 -04:00
82bd76618c update frontend README.md 2024-04-09 17:47:09 -04:00
b0027575b6 Finish merge 2024-04-09 17:43:06 -04:00
9d9bce9caa remove nginx 2024-04-09 17:37:24 -04:00
a92975961d add missing public/ directory 2024-04-09 17:37:05 -04:00
1c362d163d add react frontend 2024-04-09 17:21:59 -04:00
e481968ab2 remove frontend for rewrite 2024-04-09 17:19:46 -04:00
c3a88e187f display threads (poorly) 2024-04-06 18:54:20 -04:00
3e9f97e0e0 add cors middleware 2024-04-06 18:29:56 -04:00
ebea9faabf add jquery (broken) 2024-04-06 18:14:17 -04:00
221dbba3be remove location /api/ from nginx.conf 2024-04-06 17:57:44 -04:00
db4ea90a99 update docker-compose.yml 2024-04-06 17:50:34 -04:00
8f2d6b1613 add Dockerfile and nginx configuration 2024-04-06 17:50:21 -04:00
45c7886090 refactor: create src directory 2024-04-06 17:27:23 -04:00
327f8e4964 ignore .idea 2024-04-06 17:15:57 -04:00
e79f2840d7 Merge branch 'dev' of git.juggalol.com:agatha/forum-app into dev 2024-04-06 15:57:53 -04:00
2db6e18d5b feat: add authentication for administration (#2)
Reviewed-on: #2
Co-authored-by: agatha <agatha@juggalol.com>
Co-committed-by: agatha <agatha@juggalol.com>
2024-04-06 19:56:48 +00:00
42ee9326ae chore: remove unused import 2024-04-06 13:33:44 -04:00
19 changed files with 304 additions and 7 deletions

View File

@ -1,6 +1,12 @@
# Forum App with FastAPI
Model is after 4chan
# Singleboard Forum
A 4ch ripoff, coded as an exercise in learning full stack development with React
and FastAPI.
Board: has threads, unique counter for posts
Thread: Collection of related posts
Post: post_id, thread_id, title, content
## Demo
Start the Docker containers:
```
docker compose up -d
```
Interact with the backend API to add some posts by browsing to http://localhost:8000/docs,
then browse to the frontend at http://localhost:3000.

View File

@ -1,4 +1,5 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import models
from database import engine
@ -6,6 +7,14 @@ from routers import auth, forum
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=False,
allow_methods=['GET', 'POST'],
allow_headers=['*']
)
models.Base.metadata.create_all(bind=engine)
app.include_router(forum.router)

View File

@ -7,3 +7,13 @@ services:
ports:
- '8000:8000'
restart: unless-stopped
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- '3000:3000'
restart: unless-stopped
depends_on:
- backend

1
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

13
frontend/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM node:alpine
WORKDIR /app
COPY ./app/package.json /app/package.json
RUN npm install
COPY ./app /app
RUN npm run build
RUN npm install -g serve
EXPOSE 3000
CMD ["serve", "-s", "--no-clipboard", "build"]

View File

@ -1,4 +1,22 @@
# Forum Frontend
Should be extremely simple to start with no styling.
The frontend for the forum is written in React.
Node.js with a nice framework should be the ultimate goal, but could just do simple HTML and JS by hand for now.
**Note**: Before running, update the `apiUrl` in `app/src/App.js` to point your backend.
## Development
To start the frontend in development mode and serve on http://localhost:3000:
```shell
npm run start
```
## Production
```shell
# Install serve
npm install -g serve
# Build for production
npm run build
# Serve
serve -s --no-clipboard build
```

23
frontend/app/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

40
frontend/app/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "forum",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
"bulma": "^1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

43
frontend/app/src/App.js Normal file
View File

@ -0,0 +1,43 @@
import 'bulma/css/bulma.css'
import {useEffect, useState} from "react";
import axios from "axios";
import PostList from "./components/Post/PostList";
const apiUrl = "http://localhost:8000";
function App() {
const [postList, setPostsList] = useState([]);
const fetchPosts = async () => {
await axios.get(apiUrl + "/catalog")
.then((response) => { setPostsList(response.data)})
}
useEffect(() => {
fetchPosts();
}, []);
async function handleClickRefresh() {
await fetchPosts();
}
return (
<>
<div className="container">
<section className="hero is-info">
<div className="hero-body">
<p className="title">Singleboard Forum App</p>
</div>
</section>
<div className="section">
<div className="container" style={{marginBottom: "50px"}}>
<button className="button" onClick={handleClickRefresh}>Refresh Posts</button>
</div>
<PostList posts={postList}/>
</div>
</div>
</>
);
}
export default App;

View File

@ -0,0 +1,19 @@
import PostShow from "./PostShow";
function PostList({posts}) {
const renderedPosts = posts.map((post, key) => {
return <PostShow title={post.title} content={post.content} key={key}/>
})
return (
<>
<div className="container">
<div className="columns is-multiline">
{renderedPosts}
</div>
</div>
</>
);
}
export default PostList

View File

@ -0,0 +1,14 @@
function PostShow({title, content, post_id}) {
return (
<>
<div className="column is-4">
<h1 className="is-size-5 has-text-weight-bold">{title}</h1>
<p>{content}</p>
<br />
<p className="is-italic">0 replies</p>
</div>
</>
);
}
export default PostShow;

View File

@ -0,0 +1,8 @@
import React from "react";
import ReactDOM from "react-dom/client"
import App from "./App";
const el = document.getElementById("root");
const root = ReactDOM.createRoot(el)
root.render(<App />)

22
frontend/conf/nginx.conf Normal file
View File

@ -0,0 +1,22 @@
events {
worker_connections 1024;
}
http {
server {
listen 8000 default_server;
listen [::]:8000 default_server;
root /app;
index index.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location /api {
proxy_pass http://backend:8000/;
}
}
}