forked from mirrors/pronouns.cc
feat: bundle frontend with API executable
This commit is contained in:
parent
57c7a0f4de
commit
6c9ebf1d08
13 changed files with 105 additions and 16 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,8 +10,9 @@ pnpm-debug.log*
|
|||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
frontend/dist/*
|
||||
dist-ssr
|
||||
!frontend/dist/.empty
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
|
|
6
Makefile
6
Makefile
|
@ -1,10 +1,14 @@
|
|||
.PHONY: all
|
||||
all: frontend backend
|
||||
mv api pronouns
|
||||
|
||||
.PHONY: migrate
|
||||
migrate:
|
||||
go run -v ./scripts/migrate
|
||||
|
||||
.PHONY: backend
|
||||
backend:
|
||||
go build -v -o api -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD`" ./backend
|
||||
CGO_ENABLED=0 go build -v -o api -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD`" ./backend
|
||||
|
||||
.PHONY: frontend
|
||||
frontend:
|
||||
|
|
11
README.md
11
README.md
|
@ -9,6 +9,17 @@ A work-in-progress site to share your pronouns and preferred terms.
|
|||
- Temporary data is stored in Redis
|
||||
- The frontend is written in TypeScript with React, using [Vite](https://vitejs.dev/)
|
||||
|
||||
## Development
|
||||
|
||||
Note that
|
||||
|
||||
## Building
|
||||
|
||||
Run `make all`. This will build the frontend, then embed that in the backend.
|
||||
|
||||
The resulting `pronouns` binary is a statically linked executable containing everything needed to run the website.
|
||||
Note that it should still be run behind a reverse proxy for TLS.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2022 Sam <u1f320>
|
||||
|
|
|
@ -2,13 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/u1f320/pronouns.cc/backend/log"
|
||||
"codeberg.org/u1f320/pronouns.cc/backend/server"
|
||||
"codeberg.org/u1f320/pronouns.cc/frontend"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
)
|
||||
|
||||
|
@ -23,11 +28,14 @@ func main() {
|
|||
// mount api routes
|
||||
mountRoutes(s)
|
||||
|
||||
r := chi.NewMux()
|
||||
setupFrontend(r, s)
|
||||
|
||||
e := make(chan error)
|
||||
|
||||
// run server in another goroutine (for gracefully shutting down, see below)
|
||||
go func() {
|
||||
e <- http.ListenAndServe(port, s.Router)
|
||||
e <- http.ListenAndServe(port, r)
|
||||
}()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
|
@ -44,3 +52,44 @@ func main() {
|
|||
log.Fatalf("Error running server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupFrontend(r chi.Router, s *server.Server) {
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/@{user}", a)
|
||||
r.Get("/@{user}/{member}", a)
|
||||
|
||||
r.Mount("/api", s.Router)
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, ".js") {
|
||||
data, err = frontend.Data.ReadFile("dist" + r.URL.Path)
|
||||
w.Header().Add("content-type", "application/javascript")
|
||||
} else if strings.HasSuffix(r.URL.Path, ".css") {
|
||||
data, err = frontend.Data.ReadFile("dist" + r.URL.Path)
|
||||
w.Header().Add("content-type", "text/css")
|
||||
} else if strings.HasSuffix(r.URL.Path, ".map") {
|
||||
data, err = frontend.Data.ReadFile("dist" + r.URL.Path)
|
||||
} else {
|
||||
data, err = frontend.Data.ReadFile("dist/index.html")
|
||||
w.Header().Add("content-type", "text/html")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
})
|
||||
}
|
||||
|
||||
func a(w http.ResponseWriter, r *http.Request) {
|
||||
user := chi.URLParam(r, "user")
|
||||
member := chi.URLParam(r, "member")
|
||||
|
||||
fmt.Fprintf(w, "user: %v, member: %v", user, member)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
|
@ -85,13 +84,13 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Println(token)
|
||||
|
||||
render.JSON(w, r, discordCallbackResponse{
|
||||
HasAccount: true,
|
||||
Token: token,
|
||||
User: &u,
|
||||
})
|
||||
return nil
|
||||
|
||||
} else if err != db.ErrUserNotFound { // internal error
|
||||
return err
|
||||
}
|
||||
|
|
6
frontend/data.go
Normal file
6
frontend/data.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package frontend
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed dist/*
|
||||
var Data embed.FS
|
|
@ -1,4 +1,4 @@
|
|||
import { Routes, Route, useParams } from "react-router-dom";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import "./App.css";
|
||||
import Container from "./lib/Container";
|
||||
import Navigation from "./lib/Navigation";
|
||||
|
@ -15,8 +15,8 @@ function App() {
|
|||
<Container>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/@:username" element={<User />} />
|
||||
<Route path="/@:username/:member" element={<User />} />
|
||||
<Route path="/u/:username" element={<User />} />
|
||||
<Route path="/u/:username/:member" element={<User />} />
|
||||
<Route path="/edit" element={<EditMe />} />
|
||||
<Route path="/edit/:member" element={<EditMe />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
|
|
@ -7,7 +7,7 @@ import Logo from "./logo";
|
|||
import { useRecoilState } from "recoil";
|
||||
import { userState } from "./store";
|
||||
import fetchAPI from "./fetch";
|
||||
import { MeUser } from "./types";
|
||||
import { APIError, ErrorCode, MeUser } from "./types";
|
||||
|
||||
function Navigation() {
|
||||
const [user, setUser] = useRecoilState(userState);
|
||||
|
@ -17,7 +17,15 @@ function Navigation() {
|
|||
|
||||
fetchAPI<MeUser>("/users/@me").then(
|
||||
(res) => setUser(res),
|
||||
(err) => console.log("fetching /users/@me", err)
|
||||
(err) => {
|
||||
console.log("fetching /users/@me", err);
|
||||
if (
|
||||
(err as APIError).code == ErrorCode.InvalidToken ||
|
||||
(err as APIError).code == ErrorCode.Forbidden
|
||||
) {
|
||||
localStorage.removeItem("pronouns-token");
|
||||
}
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
|
@ -45,7 +53,7 @@ function Navigation() {
|
|||
|
||||
const nav = user ? (
|
||||
<>
|
||||
<NavItem to={`/@${user.username}`}>@{user.username}</NavItem>
|
||||
<NavItem to={`/u/${user.username}`}>@{user.username}</NavItem>
|
||||
<NavItem to="/settings">Settings</NavItem>
|
||||
<NavItem to="/logout">Log out</NavItem>
|
||||
</>
|
||||
|
|
|
@ -6,10 +6,18 @@ export default async function fetchAPI<T>(
|
|||
method = "GET",
|
||||
body = null
|
||||
) {
|
||||
let headers = {};
|
||||
const token = localStorage.getItem("pronouns-token");
|
||||
if (token) {
|
||||
headers = {
|
||||
Authorization: token,
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await fetch(`/api/v1${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: localStorage.getItem("pronouns-token"),
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: body ? JSON.stringify(body) : null,
|
||||
|
|
|
@ -15,7 +15,10 @@ async function getCurrentUser() {
|
|||
try {
|
||||
return await fetchAPI<MeUser>("/users/@me");
|
||||
} catch (e) {
|
||||
if ((e as APIError).code === ErrorCode.Forbidden) {
|
||||
if (
|
||||
(e as APIError).code === ErrorCode.Forbidden ||
|
||||
(e as APIError).code === ErrorCode.InvalidToken
|
||||
) {
|
||||
localStorage.removeItem("pronouns-token");
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ export enum ErrorCode {
|
|||
|
||||
InvalidState = 1001,
|
||||
InvalidOAuthCode = 1002,
|
||||
InvalidToken = 1003,
|
||||
|
||||
UserNotFound = 2001,
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export default function Discord() {
|
|||
console.log("token:", resp.token);
|
||||
localStorage.setItem("pronouns-token", resp.token);
|
||||
|
||||
if (resp.user) setUser(resp.user as MeUser);
|
||||
setUser(resp.user);
|
||||
},
|
||||
(err) => {
|
||||
console.log(err);
|
||||
|
|
|
@ -11,7 +11,6 @@ export default defineConfig({
|
|||
// assumes port 8080 in .env for development
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue