Refactor
This commit is contained in:
parent
a4c207ba46
commit
1949587871
7 changed files with 164 additions and 147 deletions
|
@ -1,6 +1,6 @@
|
|||
import * as D from "io-ts/Decoder"
|
||||
import { APP_URL, CLIENT_ID, CLIENT_SECRET } from "../env"
|
||||
import { expectJson } from "./helpers"
|
||||
import { expectJson } from "../api/helpers"
|
||||
|
||||
const API_URL = "https://discord.com/api/v10"
|
||||
const REDIRECT_URL = `${APP_URL}/authorize`
|
72
src/auth/auth.handlers.ts
Normal file
72
src/auth/auth.handlers.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { AUTH_URL, fetchToken, verifyToken } from "./auth.api"
|
||||
import { HttpError } from "../api/helpers"
|
||||
import type { RequestHandler } from "express"
|
||||
import path from "path"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Middleware
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const verifyUser: RequestHandler = async (req, res, next) => {
|
||||
const token = req.cookies.token
|
||||
|
||||
if (!token) {
|
||||
res.redirect("/login")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = await verifyToken(token)
|
||||
;(req as any).user = user
|
||||
next()
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError && error.status === 401) {
|
||||
res.redirect("/login")
|
||||
return
|
||||
}
|
||||
|
||||
res
|
||||
.status(500)
|
||||
.send(error instanceof Error ? error.message : "Something went wrong")
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Endpoints
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const getLogin: RequestHandler = async (req, res) => {
|
||||
const token = req.cookies.token
|
||||
|
||||
if (token) {
|
||||
res.redirect("/members")
|
||||
}
|
||||
|
||||
res.render(path.join(__dirname, "login.view.ejs"), {
|
||||
layout: path.join(process.cwd(), "src/views/_layout"),
|
||||
authUrl: AUTH_URL,
|
||||
})
|
||||
}
|
||||
|
||||
export const authorize: RequestHandler = async (req, res) => {
|
||||
const code = req.query.code
|
||||
|
||||
if (typeof code !== "string") {
|
||||
throw new Error("Invalid code")
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenResponse = await fetchToken(code)
|
||||
res.cookie("token", tokenResponse.access_token)
|
||||
res.redirect("/members")
|
||||
} catch (e) {
|
||||
res
|
||||
.status(500)
|
||||
.send(e instanceof Error ? e.message : "Something went wrong")
|
||||
}
|
||||
}
|
||||
|
||||
export const logout: RequestHandler = (req, res) => {
|
||||
res.clearCookie("token")
|
||||
res.redirect("/login")
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
<h1 class="text-2xl font-medium">Univerzity za klima</h1>
|
||||
<p class="text-lg font-medium mb-12">Členstvo</p>
|
||||
<a href="<%= authUrl %>" class="bg-discord-default text-white px-6 py-4 rounded hover:bg-discord-active transition-colors">
|
||||
<%- include("./_discord.svg.ejs", { classes: "inline w-6 mr-2 -ml-1" }) %>
|
||||
<%- include("../views/_discord.svg.ejs", { classes: "inline w-6 mr-2 -ml-1" }) %>
|
||||
<span>Přihlásit se přes Discord</span>
|
||||
</a>
|
||||
</div>
|
|
@ -1,6 +1,3 @@
|
|||
import * as dotenv from "dotenv"
|
||||
dotenv.config()
|
||||
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as D from "io-ts/Decoder"
|
||||
|
|
159
src/index.ts
159
src/index.ts
|
@ -1,64 +1,14 @@
|
|||
import { BOT_TOKEN, GUILD_ID, PORT } from "./env"
|
||||
import * as dotenv from "dotenv"
|
||||
dotenv.config()
|
||||
|
||||
import { PORT } from "./env"
|
||||
|
||||
import { Client, Events, GatewayIntentBits } from "discord.js"
|
||||
import cookieParser from "cookie-parser"
|
||||
import express from "express"
|
||||
import path from "path"
|
||||
import { AUTH_URL, fetchToken, verifyToken } from "./api/auth"
|
||||
import { HttpError } from "./api/helpers"
|
||||
import layouts from "express-ejs-layouts"
|
||||
|
||||
import type { RequestHandler } from "express"
|
||||
import type { Role } from "discord.js"
|
||||
import type { User } from "./api/auth"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Model
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
let resolveResource: null | ((roles: Array<Role>) => void) = null
|
||||
|
||||
const resource: Promise<Array<Role>> = new Promise((resolve, reject) => {
|
||||
resolveResource = resolve
|
||||
})
|
||||
|
||||
const processRoles = (roles: Array<Role>) => {
|
||||
return roles
|
||||
.filter((role) => role.name !== "@everyone")
|
||||
.map((role) => ({
|
||||
name: role.name,
|
||||
color: role.hexColor,
|
||||
members: role.members.map((member) => ({
|
||||
nickname: member.nickname,
|
||||
username: member.user.username,
|
||||
discriminator: member.user.discriminator,
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
const getAvatarUrl = (user: User) =>
|
||||
`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Discord client
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
|
||||
})
|
||||
|
||||
client.once(Events.ClientReady, async (c) => {
|
||||
console.log(`Ready! Logged in as ${c.user.tag}`)
|
||||
const guild = await c.guilds.fetch(GUILD_ID)
|
||||
const [_, rolesCollection] = await Promise.all([
|
||||
guild.members.fetch(),
|
||||
guild.roles.fetch(),
|
||||
])
|
||||
resolveResource?.(rolesCollection.toJSON())
|
||||
console.log("Roles fetched")
|
||||
})
|
||||
|
||||
client.login(BOT_TOKEN)
|
||||
import { authorize, getLogin, logout, verifyUser } from "./auth/auth.handlers"
|
||||
import { getMembers } from "./members/members.handlers"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Server
|
||||
|
@ -68,102 +18,27 @@ const app = express()
|
|||
|
||||
app.use(cookieParser())
|
||||
app.use(layouts)
|
||||
app.set("views", path.join(__dirname, "views"))
|
||||
app.set("view engine", "ejs")
|
||||
|
||||
const verifyUser: RequestHandler = async (req, res, next) => {
|
||||
const token = req.cookies.token
|
||||
|
||||
if (!token) {
|
||||
res.redirect(AUTH_URL)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = await verifyToken(token)
|
||||
;(req as any).user = user
|
||||
next()
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError && error.status === 401) {
|
||||
res.redirect(AUTH_URL)
|
||||
return
|
||||
}
|
||||
|
||||
res
|
||||
.status(500)
|
||||
.send(error instanceof Error ? error.message : "Something went wrong")
|
||||
}
|
||||
}
|
||||
|
||||
app.use("/", express.static("static"))
|
||||
|
||||
app.get("/", async (req, res) => {
|
||||
const token = req.cookies.token
|
||||
|
||||
if (token) {
|
||||
app.get("/", (req, res) => {
|
||||
if (!req.cookies.token) {
|
||||
res.redirect("/login")
|
||||
} else {
|
||||
res.redirect("/members")
|
||||
}
|
||||
|
||||
res.render("index", {
|
||||
layout: path.join(__dirname, "views/_layout"),
|
||||
authUrl: AUTH_URL,
|
||||
})
|
||||
})
|
||||
|
||||
app.get("/authorize", async (req, res) => {
|
||||
const code = req.query.code
|
||||
// Auth
|
||||
app.get("/login", getLogin)
|
||||
app.get("/authorize", authorize)
|
||||
app.post("/logout", logout)
|
||||
|
||||
if (typeof code !== "string") {
|
||||
throw new Error("Invalid code")
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenResponse = await fetchToken(code)
|
||||
res.cookie("token", tokenResponse.access_token)
|
||||
res.redirect("/members")
|
||||
} catch (e) {
|
||||
res
|
||||
.status(500)
|
||||
.send(e instanceof Error ? e.message : "Something went wrong")
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/members.json", verifyUser, async (req, res) => {
|
||||
try {
|
||||
const roles = await resource
|
||||
res.json(processRoles(roles))
|
||||
} catch (e) {
|
||||
res
|
||||
.status(500)
|
||||
.send(e instanceof Error ? e.message : "Something went wrong")
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/members", verifyUser, async (req, res) => {
|
||||
try {
|
||||
const roles = await resource
|
||||
const user: User = (req as any).user
|
||||
|
||||
res.render("members", {
|
||||
layout: path.join(__dirname, "views/_layout"),
|
||||
roles: processRoles(roles),
|
||||
user: {
|
||||
...user,
|
||||
avatarUrl: getAvatarUrl(user),
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
res
|
||||
.status(500)
|
||||
.send(e instanceof Error ? e.message : "Something went wrong")
|
||||
}
|
||||
})
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
res.clearCookie("token")
|
||||
res.redirect("/")
|
||||
})
|
||||
// Members
|
||||
app.get("/members", verifyUser, getMembers)
|
||||
|
||||
// Listen
|
||||
app.listen(PORT, () => {
|
||||
console.log(`⚡️[server]: Server is running at http://localhost:${PORT}`)
|
||||
})
|
||||
|
|
73
src/members/members.handlers.ts
Normal file
73
src/members/members.handlers.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { BOT_TOKEN, GUILD_ID } from "../env"
|
||||
|
||||
import { Client, Events, GatewayIntentBits } from "discord.js"
|
||||
|
||||
import type { RequestHandler } from "express"
|
||||
import type { Role } from "discord.js"
|
||||
import type { User } from "../auth/auth.api"
|
||||
import path from "path"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const processRoles = (roles: Array<Role>) => {
|
||||
return roles
|
||||
.filter((role) => role.name !== "@everyone")
|
||||
.map((role) => ({
|
||||
name: role.name,
|
||||
color: role.hexColor,
|
||||
members: role.members.map((member) => ({
|
||||
nickname: member.nickname,
|
||||
username: member.user.username,
|
||||
discriminator: member.user.discriminator,
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
const getAvatarUrl = (user: User) =>
|
||||
`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
|
||||
|
||||
export const getRolesAndMembers = () =>
|
||||
new Promise<Array<Role>>((resolve) => {
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
|
||||
})
|
||||
|
||||
client.once(Events.ClientReady, async (c) => {
|
||||
console.log(`Ready! Logged in as ${c.user.tag}`)
|
||||
const guild = await c.guilds.fetch(GUILD_ID)
|
||||
const [_, rolesCollection] = await Promise.all([
|
||||
guild.members.fetch(),
|
||||
guild.roles.fetch(),
|
||||
])
|
||||
resolve(rolesCollection.toJSON())
|
||||
console.log("Roles fetched")
|
||||
})
|
||||
|
||||
client.login(BOT_TOKEN)
|
||||
})
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Endpoints
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const getMembers: RequestHandler = async (req, res) => {
|
||||
try {
|
||||
const roles = await getRolesAndMembers()
|
||||
const user: User = (req as any).user
|
||||
|
||||
res.render(path.join(__dirname, "members.view.ejs"), {
|
||||
layout: path.join(process.cwd(), "src/views/_layout"),
|
||||
roles: processRoles(roles),
|
||||
user: {
|
||||
...user,
|
||||
avatarUrl: getAvatarUrl(user),
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
res
|
||||
.status(500)
|
||||
.send(e instanceof Error ? e.message : "Something went wrong")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue