client/server: lint and add functionality for admin to update homepage
This commit is contained in:
parent
be6de7c796
commit
b5024e3f45
26 changed files with 617 additions and 313 deletions
|
@ -5,9 +5,11 @@ Drift is a self-hostable Gist clone. It's also a major work-in-progress, but is
|
||||||
You can try a demo at https://drift.maxleiter.com. The demo is built on master but has no database, so files and accounts can be wiped at any time.
|
You can try a demo at https://drift.maxleiter.com. The demo is built on master but has no database, so files and accounts can be wiped at any time.
|
||||||
|
|
||||||
If you want to contribute, need support, or want to stay updated, you can join the IRC channel at #drift on irc.libera.chat or [reach me on twitter](https://twitter.com/Max_Leiter). If you don't have an IRC client yet, you can use a webclient [here](https://demo.thelounge.chat/#/connect?join=%23drift&nick=drift-user&realname=Drift%20User).
|
If you want to contribute, need support, or want to stay updated, you can join the IRC channel at #drift on irc.libera.chat or [reach me on twitter](https://twitter.com/Max_Leiter). If you don't have an IRC client yet, you can use a webclient [here](https://demo.thelounge.chat/#/connect?join=%23drift&nick=drift-user&realname=Drift%20User).
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
**Contents:**
|
**Contents:**
|
||||||
|
|
||||||
- [Setup](#setup)
|
- [Setup](#setup)
|
||||||
- [Development](#development)
|
- [Development](#development)
|
||||||
- [Production](#production)
|
- [Production](#production)
|
||||||
|
@ -50,8 +52,6 @@ You can change these to your liking.
|
||||||
- `MEMORY_DB`: if `true`, a sqlite database will not be created and changes will only exist in memory. Mainly for the demo.
|
- `MEMORY_DB`: if `true`, a sqlite database will not be created and changes will only exist in memory. Mainly for the demo.
|
||||||
- `REGISTRATION_PASSWORD`: if `true`, the user will be required to provide this password to sign-up, in addition to their username and account password. If it's not set, no additional password will be required.
|
- `REGISTRATION_PASSWORD`: if `true`, the user will be required to provide this password to sign-up, in addition to their username and account password. If it's not set, no additional password will be required.
|
||||||
- `SECRET_KEY`: the same secret key as the client
|
- `SECRET_KEY`: the same secret key as the client
|
||||||
- `WELCOME_CONTENT`: a markdown string that's rendered on the home page
|
|
||||||
- `WELCOME_TITLE`: the file title for the post on the homepage.
|
|
||||||
- `ENABLE_ADMIN`: the first account created is an administrator account
|
- `ENABLE_ADMIN`: the first account created is an administrator account
|
||||||
- `DRIFT_HOME`: defaults to ~/.drift, the directory for storing the database and eventually images
|
- `DRIFT_HOME`: defaults to ~/.drift, the directory for storing the database and eventually images
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ type Action = {
|
||||||
const ActionDropdown = ({
|
const ActionDropdown = ({
|
||||||
title = "Actions",
|
title = "Actions",
|
||||||
actions,
|
actions,
|
||||||
showTitle = false,
|
showTitle = false
|
||||||
}: {
|
}: {
|
||||||
title?: string,
|
title?: string
|
||||||
showTitle?: boolean,
|
showTitle?: boolean
|
||||||
actions: Action[]
|
actions: Action[]
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
@ -20,14 +20,9 @@ const ActionDropdown = ({
|
||||||
title={title}
|
title={title}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
{showTitle && <Popover.Item title>
|
{showTitle && <Popover.Item title>{title}</Popover.Item>}
|
||||||
{title}
|
{actions.map((action) => (
|
||||||
</Popover.Item>}
|
<Popover.Item onClick={action.onClick} key={action.title}>
|
||||||
{actions.map(action => (
|
|
||||||
<Popover.Item
|
|
||||||
onClick={action.onClick}
|
|
||||||
key={action.title}
|
|
||||||
>
|
|
||||||
{action.title}
|
{action.title}
|
||||||
</Popover.Item>
|
</Popover.Item>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Text, Spacer } from "@geist-ui/core"
|
||||||
import Cookies from "js-cookie"
|
import Cookies from "js-cookie"
|
||||||
import styles from "./admin.module.css"
|
import styles from "./admin.module.css"
|
||||||
import PostTable from "./post-table"
|
import PostTable from "./post-table"
|
||||||
|
import ServerInfo from "./server-info"
|
||||||
import UserTable from "./user-table"
|
import UserTable from "./user-table"
|
||||||
|
|
||||||
export const adminFetcher = async (
|
export const adminFetcher = async (
|
||||||
|
@ -24,6 +25,8 @@ const Admin = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.adminWrapper}>
|
<div className={styles.adminWrapper}>
|
||||||
<Text h2>Administration</Text>
|
<Text h2>Administration</Text>
|
||||||
|
<ServerInfo />
|
||||||
|
<Spacer height={1} />
|
||||||
<UserTable />
|
<UserTable />
|
||||||
<Spacer height={1} />
|
<Spacer height={1} />
|
||||||
<PostTable />
|
<PostTable />
|
||||||
|
|
|
@ -9,7 +9,7 @@ import ActionDropdown from "./action-dropdown"
|
||||||
|
|
||||||
const PostTable = () => {
|
const PostTable = () => {
|
||||||
const [posts, setPosts] = useState<Post[]>()
|
const [posts, setPosts] = useState<Post[]>()
|
||||||
const { setToast } = useToasts()
|
// const { setToast } = useToasts()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPosts = async () => {
|
const fetchPosts = async () => {
|
||||||
|
@ -109,14 +109,14 @@ const PostTable = () => {
|
||||||
dataIndex: "",
|
dataIndex: "",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
width: 50,
|
width: 50,
|
||||||
render(post: Post) {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
title="Actions"
|
title="Actions"
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
onClick: () => deletePost(post.id)
|
onClick: () => deletePost()
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -128,7 +128,11 @@ const PostTable = () => {
|
||||||
return (
|
return (
|
||||||
<SettingsGroup title="Posts">
|
<SettingsGroup title="Posts">
|
||||||
{!posts && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
{!posts && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
||||||
{posts && <Fieldset.Subtitle><h5>{posts.length} posts</h5></Fieldset.Subtitle>}
|
{posts && (
|
||||||
|
<Fieldset.Subtitle>
|
||||||
|
<h5>{posts.length} posts</h5>
|
||||||
|
</Fieldset.Subtitle>
|
||||||
|
)}
|
||||||
{posts && <Table columns={tableColumns} data={tablePosts} />}
|
{posts && <Table columns={tableColumns} data={tablePosts} />}
|
||||||
</SettingsGroup>
|
</SettingsGroup>
|
||||||
)
|
)
|
||||||
|
|
75
client/components/admin/server-info.tsx
Normal file
75
client/components/admin/server-info.tsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import SettingsGroup from "@components/settings-group"
|
||||||
|
import { Button, Input, Spacer, Textarea, useToasts } from "@geist-ui/core"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { adminFetcher } from "."
|
||||||
|
|
||||||
|
const Homepage = () => {
|
||||||
|
const [description, setDescription] = useState<string>("")
|
||||||
|
const [title, setTitle] = useState<string>("")
|
||||||
|
const { setToast } = useToasts()
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchServerInfo = async () => {
|
||||||
|
const res = await adminFetcher("/server-info")
|
||||||
|
const data = await res.json()
|
||||||
|
setDescription(data.welcomeMessage)
|
||||||
|
setTitle(data.welcomeTitle)
|
||||||
|
}
|
||||||
|
fetchServerInfo()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const res = await adminFetcher("/server-info", {
|
||||||
|
method: "PUT",
|
||||||
|
body: {
|
||||||
|
description,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
setToast({
|
||||||
|
type: "success",
|
||||||
|
text: "Server info updated"
|
||||||
|
})
|
||||||
|
setDescription(description)
|
||||||
|
setTitle(title)
|
||||||
|
} else {
|
||||||
|
setToast({
|
||||||
|
text: "Something went wrong",
|
||||||
|
type: "error"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsGroup title="Homepage">
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="title">Title</label>
|
||||||
|
<Input
|
||||||
|
id="title"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Spacer height={1} />
|
||||||
|
<div>
|
||||||
|
<label htmlFor="message">Description (markdown)</label>
|
||||||
|
<Textarea
|
||||||
|
width={"100%"}
|
||||||
|
height={10}
|
||||||
|
id="message"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Spacer height={1} />
|
||||||
|
<Button htmlType="submit">Update</Button>
|
||||||
|
</form>
|
||||||
|
</SettingsGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Homepage
|
|
@ -131,7 +131,8 @@ const UserTable = () => {
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
title: user.role === "admin" ? "Change role" : "Make admin",
|
title: user.role === "admin" ? "Change role" : "Make admin",
|
||||||
onClick: () => toggleRole(user.id, user.role === "admin" ? "user" : "admin")
|
onClick: () =>
|
||||||
|
toggleRole(user.id, user.role === "admin" ? "user" : "admin")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
|
@ -147,7 +148,11 @@ const UserTable = () => {
|
||||||
return (
|
return (
|
||||||
<SettingsGroup title="Users">
|
<SettingsGroup title="Users">
|
||||||
{!users && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
{!users && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
||||||
{users && <Fieldset.Subtitle><h5>{users.length} users</h5></Fieldset.Subtitle>}
|
{users && (
|
||||||
|
<Fieldset.Subtitle>
|
||||||
|
<h5>{users.length} users</h5>
|
||||||
|
</Fieldset.Subtitle>
|
||||||
|
)}
|
||||||
{users && <Table columns={usernameColumns} data={tableUsers} />}
|
{users && <Table columns={usernameColumns} data={tableUsers} />}
|
||||||
</SettingsGroup>
|
</SettingsGroup>
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,10 +89,10 @@ const Header = () => {
|
||||||
href: "/mine"
|
href: "/mine"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'settings',
|
name: "settings",
|
||||||
icon: <SettingsIcon />,
|
icon: <SettingsIcon />,
|
||||||
value: 'settings',
|
value: "settings",
|
||||||
href: '/settings'
|
href: "/settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sign out",
|
name: "sign out",
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { Button, useToasts, ButtonDropdown, Input } from "@geist-ui/core"
|
||||||
Button,
|
|
||||||
useToasts,
|
|
||||||
ButtonDropdown,
|
|
||||||
Input,
|
|
||||||
} from "@geist-ui/core"
|
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import generateUUID from "@lib/generate-uuid"
|
import generateUUID from "@lib/generate-uuid"
|
||||||
|
|
|
@ -97,10 +97,7 @@ const ListItem = ({
|
||||||
{post.files?.map((file: File) => {
|
{post.files?.map((file: File) => {
|
||||||
return (
|
return (
|
||||||
<div key={file.id}>
|
<div key={file.id}>
|
||||||
<Link
|
<Link color href={`/post/${post.id}#${file.title}`}>
|
||||||
color
|
|
||||||
href={`/post/${post.id}#${file.title}`}
|
|
||||||
>
|
|
||||||
{file.title || "Untitled file"}
|
{file.title || "Untitled file"}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,9 +85,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewParentClick = () => {
|
const viewParentClick = () => {
|
||||||
router.push(
|
router.push(`/post/${post.parent!.id}`)
|
||||||
`/post/${post.parent!.id}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
@ -123,11 +121,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
||||||
Edit a Copy
|
Edit a Copy
|
||||||
</Button>
|
</Button>
|
||||||
{post.parent && (
|
{post.parent && (
|
||||||
<Button
|
<Button auto icon={<Parent />} onClick={viewParentClick}>
|
||||||
auto
|
|
||||||
icon={<Parent />}
|
|
||||||
onClick={viewParentClick}
|
|
||||||
>
|
|
||||||
View Parent
|
View Parent
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import { Fieldset, Text, Divider } from "@geist-ui/core"
|
import { Fieldset, Text, Divider } from "@geist-ui/core"
|
||||||
import styles from './settings-group.module.css'
|
import styles from "./settings-group.module.css"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string,
|
title: string
|
||||||
children: React.ReactNode | React.ReactNode[],
|
children: React.ReactNode | React.ReactNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsGroup = ({
|
const SettingsGroup = ({ title, children }: Props) => {
|
||||||
title,
|
return (
|
||||||
children,
|
<Fieldset>
|
||||||
}: Props) => {
|
|
||||||
return <Fieldset>
|
|
||||||
<Fieldset.Content>
|
<Fieldset.Content>
|
||||||
<Text h4>{title}</Text>
|
<Text h4>{title}</Text>
|
||||||
</Fieldset.Content>
|
</Fieldset.Content>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Fieldset.Content className={styles.content}>
|
<Fieldset.Content className={styles.content}>{children}</Fieldset.Content>
|
||||||
{children}
|
|
||||||
</Fieldset.Content>
|
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsGroup
|
export default SettingsGroup
|
||||||
|
|
|
@ -3,12 +3,15 @@ import Profile from "./sections/profile"
|
||||||
import SettingsGroup from "../settings-group"
|
import SettingsGroup from "../settings-group"
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
return (<div style={{
|
return (
|
||||||
display: 'flex',
|
<div
|
||||||
flexDirection: 'column',
|
style={{
|
||||||
gap: 'var(--gap)',
|
display: "flex",
|
||||||
marginBottom: 'var(--gap)',
|
flexDirection: "column",
|
||||||
}}>
|
gap: "var(--gap)",
|
||||||
|
marginBottom: "var(--gap)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<SettingsGroup title="Profile">
|
<SettingsGroup title="Profile">
|
||||||
<Profile />
|
<Profile />
|
||||||
|
@ -16,7 +19,8 @@ const SettingsPage = () => {
|
||||||
<SettingsGroup title="Password">
|
<SettingsGroup title="Password">
|
||||||
<Password />
|
<Password />
|
||||||
</SettingsGroup>
|
</SettingsGroup>
|
||||||
</div>)
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsPage
|
export default SettingsPage
|
|
@ -3,9 +3,9 @@ import Cookies from "js-cookie"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
const Password = () => {
|
const Password = () => {
|
||||||
const [password, setPassword] = useState<string>('')
|
const [password, setPassword] = useState<string>("")
|
||||||
const [newPassword, setNewPassword] = useState<string>('')
|
const [newPassword, setNewPassword] = useState<string>("")
|
||||||
const [confirmPassword, setConfirmPassword] = useState<string>('')
|
const [confirmPassword, setConfirmPassword] = useState<string>("")
|
||||||
|
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@ const Password = () => {
|
||||||
setNewPassword(e.target.value)
|
setNewPassword(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConfirmPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleConfirmPasswordChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
setConfirmPassword(e.target.value)
|
setConfirmPassword(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,14 +28,14 @@ const Password = () => {
|
||||||
if (!password || !newPassword || !confirmPassword) {
|
if (!password || !newPassword || !confirmPassword) {
|
||||||
setToast({
|
setToast({
|
||||||
text: "Please fill out all fields",
|
text: "Please fill out all fields",
|
||||||
type: "error",
|
type: "error"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
setToast({
|
setToast({
|
||||||
text: "New password and confirm password do not match",
|
text: "New password and confirm password do not match",
|
||||||
type: "error",
|
type: "error"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,29 +43,28 @@ const Password = () => {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": `Bearer ${Cookies.get("drift-token")}`,
|
Authorization: `Bearer ${Cookies.get("drift-token")}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
oldPassword: password,
|
oldPassword: password,
|
||||||
newPassword,
|
newPassword
|
||||||
}),
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
setToast({
|
setToast({
|
||||||
text: "Password updated successfully",
|
text: "Password updated successfully",
|
||||||
type: "success",
|
type: "success"
|
||||||
})
|
})
|
||||||
setPassword('')
|
setPassword("")
|
||||||
setNewPassword('')
|
setNewPassword("")
|
||||||
setConfirmPassword('')
|
setConfirmPassword("")
|
||||||
} else {
|
} else {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
setToast({
|
setToast({
|
||||||
text: data.error ?? "Failed to update password",
|
text: data.error ?? "Failed to update password",
|
||||||
type: "error",
|
type: "error"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,24 +75,60 @@ const Password = () => {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "var(--gap)",
|
gap: "var(--gap)",
|
||||||
maxWidth: "300px",
|
maxWidth: "300px"
|
||||||
}}
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="current-password">Current password</label>
|
<label htmlFor="current-password">Current password</label>
|
||||||
<Input onChange={handlePasswordChange} minLength={6} maxLength={128} value={password} id="current-password" htmlType="password" required autoComplete="current-password" placeholder="Current Password" width={"100%"} />
|
<Input
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
minLength={6}
|
||||||
|
maxLength={128}
|
||||||
|
value={password}
|
||||||
|
id="current-password"
|
||||||
|
htmlType="password"
|
||||||
|
required
|
||||||
|
autoComplete="current-password"
|
||||||
|
placeholder="Current Password"
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new-password">New password</label>
|
<label htmlFor="new-password">New password</label>
|
||||||
<Input onChange={handleNewPasswordChange} minLength={6} maxLength={128} value={newPassword} id="new-password" htmlType="password" required autoComplete="new-password" placeholder="New Password" width={"100%"} />
|
<Input
|
||||||
|
onChange={handleNewPasswordChange}
|
||||||
|
minLength={6}
|
||||||
|
maxLength={128}
|
||||||
|
value={newPassword}
|
||||||
|
id="new-password"
|
||||||
|
htmlType="password"
|
||||||
|
required
|
||||||
|
autoComplete="new-password"
|
||||||
|
placeholder="New Password"
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="confirm-password">Confirm password</label>
|
<label htmlFor="confirm-password">Confirm password</label>
|
||||||
<Input onChange={handleConfirmPasswordChange} minLength={6} maxLength={128} value={confirmPassword} id="confirm-password" htmlType="password" required autoComplete="confirm-password" placeholder="Confirm Password" width={"100%"} />
|
<Input
|
||||||
|
onChange={handleConfirmPasswordChange}
|
||||||
|
minLength={6}
|
||||||
|
maxLength={128}
|
||||||
|
value={confirmPassword}
|
||||||
|
id="confirm-password"
|
||||||
|
htmlType="password"
|
||||||
|
required
|
||||||
|
autoComplete="confirm-password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button htmlType="submit" auto>Change Password</Button>
|
<Button htmlType="submit" auto>
|
||||||
</form>)
|
Change Password
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Password
|
export default Password
|
|
@ -10,7 +10,6 @@ const Profile = () => {
|
||||||
const [bio, setBio] = useState<string>()
|
const [bio, setBio] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(user)
|
|
||||||
if (user?.displayName) setName(user.displayName)
|
if (user?.displayName) setName(user.displayName)
|
||||||
if (user?.email) setEmail(user.email)
|
if (user?.email) setEmail(user.email)
|
||||||
if (user?.bio) setBio(user.bio)
|
if (user?.bio) setBio(user.bio)
|
||||||
|
@ -35,7 +34,7 @@ const Profile = () => {
|
||||||
if (!name && !email && !bio) {
|
if (!name && !email && !bio) {
|
||||||
setToast({
|
setToast({
|
||||||
text: "Please fill out at least one field",
|
text: "Please fill out at least one field",
|
||||||
type: "error",
|
type: "error"
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -43,32 +42,33 @@ const Profile = () => {
|
||||||
const data = {
|
const data = {
|
||||||
displayName: name,
|
displayName: name,
|
||||||
email,
|
email,
|
||||||
bio,
|
bio
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch("/server-api/user/profile", {
|
const res = await fetch("/server-api/user/profile", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": `Bearer ${Cookies.get("drift-token")}`,
|
Authorization: `Bearer ${Cookies.get("drift-token")}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
setToast({
|
setToast({
|
||||||
text: "Profile updated",
|
text: "Profile updated",
|
||||||
type: "success",
|
type: "success"
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setToast({
|
setToast({
|
||||||
text: "Something went wrong updating your profile",
|
text: "Something went wrong updating your profile",
|
||||||
type: "error",
|
type: "error"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
|
<>
|
||||||
<Note type="warning" marginBottom={"var(--gap)"}>
|
<Note type="warning" marginBottom={"var(--gap)"}>
|
||||||
This information will be publicly available on your profile
|
This information will be publicly available on your profile
|
||||||
</Note>
|
</Note>
|
||||||
|
@ -77,24 +77,48 @@ const Profile = () => {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "var(--gap)",
|
gap: "var(--gap)",
|
||||||
maxWidth: "300px",
|
maxWidth: "300px"
|
||||||
}}
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="displayName">Display name</label>
|
<label htmlFor="displayName">Display name</label>
|
||||||
<Input id="displayName" width={"100%"} placeholder="my name" value={name || ''} onChange={handleNameChange} />
|
<Input
|
||||||
|
id="displayName"
|
||||||
|
width={"100%"}
|
||||||
|
placeholder="my name"
|
||||||
|
value={name || ""}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email">Email</label>
|
<label htmlFor="email">Email</label>
|
||||||
<Input id="email" htmlType="email" width={"100%"} placeholder="my@email.io" value={email || ''} onChange={handleEmailChange} />
|
<Input
|
||||||
|
id="email"
|
||||||
|
htmlType="email"
|
||||||
|
width={"100%"}
|
||||||
|
placeholder="my@email.io"
|
||||||
|
value={email || ""}
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="bio">Biography (max 250 characters)</label>
|
<label htmlFor="bio">Biography (max 250 characters)</label>
|
||||||
<Textarea id="bio" width="100%" maxLength={250} placeholder="I enjoy..." value={bio || ''} onChange={handleBioChange} />
|
<Textarea
|
||||||
|
id="bio"
|
||||||
|
width="100%"
|
||||||
|
maxLength={250}
|
||||||
|
placeholder="I enjoy..."
|
||||||
|
value={bio || ""}
|
||||||
|
onChange={handleBioChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button htmlType="submit" auto>Submit</Button>
|
<Button htmlType="submit" auto>
|
||||||
</form></>)
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Profile
|
export default Profile
|
|
@ -3,14 +3,16 @@ import PageSeo from "@components/page-seo"
|
||||||
import HomeComponent from "@components/home"
|
import HomeComponent from "@components/home"
|
||||||
import { Page, Text } from "@geist-ui/core"
|
import { Page, Text } from "@geist-ui/core"
|
||||||
import type { GetStaticProps } from "next"
|
import type { GetStaticProps } from "next"
|
||||||
import { InferGetStaticPropsType } from 'next'
|
import { InferGetStaticPropsType } from "next"
|
||||||
type Props = {
|
type Props =
|
||||||
|
| {
|
||||||
introContent: string
|
introContent: string
|
||||||
introTitle: string
|
introTitle: string
|
||||||
rendered: string
|
rendered: string
|
||||||
} | {
|
}
|
||||||
|
| {
|
||||||
error: boolean
|
error: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async () => {
|
export const getStaticProps: GetStaticProps = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -32,21 +34,26 @@ export const getStaticProps: GetStaticProps = async () => {
|
||||||
// Next.js will attempt to re-generate the page:
|
// Next.js will attempt to re-generate the page:
|
||||||
// - When a request comes in
|
// - When a request comes in
|
||||||
// - At most every 60 seconds
|
// - At most every 60 seconds
|
||||||
revalidate: 60, // In seconds
|
revalidate: 60 // In seconds
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If there was an error, it's likely due to the server not running, so we attempt to regenerate the page
|
// If there was an error, it's likely due to the server not running, so we attempt to regenerate the page
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
error: true,
|
error: true
|
||||||
},
|
},
|
||||||
revalidate: 10, // In seconds
|
revalidate: 10 // In seconds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix props type
|
// TODO: fix props type
|
||||||
const Home = ({ rendered, introContent, introTitle, error }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
const Home = ({
|
||||||
|
rendered,
|
||||||
|
introContent,
|
||||||
|
introTitle,
|
||||||
|
error
|
||||||
|
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
return (
|
return (
|
||||||
<Page className={styles.wrapper}>
|
<Page className={styles.wrapper}>
|
||||||
<PageSeo />
|
<PageSeo />
|
||||||
|
|
|
@ -69,7 +69,6 @@ export const getServerSideProps: GetServerSideProps = async ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
post: json,
|
post: json,
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
import { Button, Divider, Text, Fieldset, Input, Page, Note, Textarea } from "@geist-ui/core"
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Text,
|
||||||
|
Fieldset,
|
||||||
|
Input,
|
||||||
|
Page,
|
||||||
|
Note,
|
||||||
|
Textarea
|
||||||
|
} from "@geist-ui/core"
|
||||||
import PageSeo from "@components/page-seo"
|
import PageSeo from "@components/page-seo"
|
||||||
import styles from "@styles/Home.module.css"
|
import styles from "@styles/Home.module.css"
|
||||||
import SettingsPage from "@components/settings"
|
import SettingsPage from "@components/settings"
|
||||||
|
@ -6,7 +15,10 @@ import SettingsPage from "@components/settings"
|
||||||
const Settings = () => (
|
const Settings = () => (
|
||||||
<Page width={"100%"}>
|
<Page width={"100%"}>
|
||||||
<PageSeo title="Drift - Settings" />
|
<PageSeo title="Drift - Settings" />
|
||||||
<Page.Content className={styles.main} style={{ gap: 'var(--gap)', display: 'flex', flexDirection: 'column' }}>
|
<Page.Content
|
||||||
|
className={styles.main}
|
||||||
|
style={{ gap: "var(--gap)", display: "flex", flexDirection: "column" }}
|
||||||
|
>
|
||||||
<SettingsPage />
|
<SettingsPage />
|
||||||
</Page.Content>
|
</Page.Content>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { errors } from "celebrate"
|
||||||
import secretKey from "@lib/middleware/secret-key"
|
import secretKey from "@lib/middleware/secret-key"
|
||||||
import markdown from "@lib/render-markdown"
|
import markdown from "@lib/render-markdown"
|
||||||
import config from "@lib/config"
|
import config from "@lib/config"
|
||||||
|
import { ServerInfo } from "@lib/models/ServerInfo"
|
||||||
|
|
||||||
export const app = express()
|
export const app = express()
|
||||||
|
|
||||||
|
@ -19,17 +20,25 @@ app.use("/files", files)
|
||||||
app.use("/admin", admin)
|
app.use("/admin", admin)
|
||||||
app.use("/health", health)
|
app.use("/health", health)
|
||||||
|
|
||||||
app.get("/welcome", secretKey, (req, res) => {
|
app.get("/welcome", secretKey, async (req, res) => {
|
||||||
const introContent = config.welcome_content
|
const serverInfo = await ServerInfo.findOne({
|
||||||
const introTitle = config.welcome_title
|
where: {
|
||||||
if (!introContent || !introTitle) {
|
id: "1"
|
||||||
return res.status(500).json({ error: "Missing welcome content" })
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!serverInfo) {
|
||||||
|
return res.status(500).json({
|
||||||
|
message: "Server info not found."
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { welcomeMessage, welcomeTitle } = serverInfo
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
title: introTitle,
|
title: welcomeTitle,
|
||||||
content: introContent,
|
content: welcomeMessage,
|
||||||
rendered: markdown(introContent)
|
rendered: markdown(welcomeMessage)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import config from "@lib/config"
|
||||||
import databasePath from "@lib/get-database-path"
|
import databasePath from "@lib/get-database-path"
|
||||||
import { Sequelize } from "sequelize-typescript"
|
import { Sequelize } from "sequelize-typescript"
|
||||||
import { SequelizeStorage, Umzug } from "umzug"
|
import { SequelizeStorage, Umzug } from "umzug"
|
||||||
|
import { QueryTypes } from "sequelize"
|
||||||
|
|
||||||
export const sequelize = new Sequelize({
|
export const sequelize = new Sequelize({
|
||||||
dialect: "sqlite",
|
dialect: "sqlite",
|
||||||
|
@ -11,10 +12,24 @@ export const sequelize = new Sequelize({
|
||||||
logging: console.log
|
logging: console.log
|
||||||
})
|
})
|
||||||
|
|
||||||
if (config.memory_db) {
|
export const initServerInfo = async () => {
|
||||||
console.log("Using in-memory database")
|
const serverInfo = await sequelize.query(
|
||||||
} else {
|
"SELECT * FROM `server-info` WHERE id = '1'",
|
||||||
console.log(`Database path: ${databasePath}`)
|
{
|
||||||
|
type: QueryTypes.SELECT
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (serverInfo.length === 0) {
|
||||||
|
console.log("server-info table not found, creating...")
|
||||||
|
console.log(
|
||||||
|
"You can change the welcome message and title on the admin settings page."
|
||||||
|
)
|
||||||
|
await sequelize.query("INSERT INTO `server-info` (id) VALUES ('1')", {
|
||||||
|
type: QueryTypes.INSERT
|
||||||
|
})
|
||||||
|
console.log("server-info table created.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const umzug = new Umzug({
|
export const umzug = new Umzug({
|
||||||
|
@ -30,6 +45,12 @@ export const umzug = new Umzug({
|
||||||
|
|
||||||
export type Migration = typeof umzug._types.migration
|
export type Migration = typeof umzug._types.migration
|
||||||
|
|
||||||
|
if (config.memory_db) {
|
||||||
|
console.log("Using in-memory database")
|
||||||
|
} else {
|
||||||
|
console.log(`Database path: ${databasePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
// If you're in a development environment, you can manually migrate with `yarn migrate:{up,down}` in the `server` folder
|
// If you're in a development environment, you can manually migrate with `yarn migrate:{up,down}` in the `server` folder
|
||||||
if (config.is_production) {
|
if (config.is_production) {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
|
|
33
server/src/lib/models/ServerInfo.ts
Normal file
33
server/src/lib/models/ServerInfo.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
Model,
|
||||||
|
Column,
|
||||||
|
Table,
|
||||||
|
IsUUID,
|
||||||
|
PrimaryKey,
|
||||||
|
DataType,
|
||||||
|
Unique
|
||||||
|
} from "sequelize-typescript"
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: "server-info"
|
||||||
|
})
|
||||||
|
export class ServerInfo extends Model {
|
||||||
|
@IsUUID(4)
|
||||||
|
@PrimaryKey
|
||||||
|
@Unique
|
||||||
|
@Column({
|
||||||
|
type: DataType.UUID,
|
||||||
|
defaultValue: DataType.UUIDV4
|
||||||
|
})
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.STRING
|
||||||
|
})
|
||||||
|
welcomeMessage!: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.STRING
|
||||||
|
})
|
||||||
|
welcomeTitle!: string
|
||||||
|
}
|
|
@ -10,17 +10,17 @@ export const up: Migration = async ({ context: queryInterface }) =>
|
||||||
}),
|
}),
|
||||||
queryInterface.addColumn("users", "displayName", {
|
queryInterface.addColumn("users", "displayName", {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true
|
||||||
}),
|
}),
|
||||||
queryInterface.addColumn("users", "bio", {
|
queryInterface.addColumn("users", "bio", {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true
|
||||||
}),
|
})
|
||||||
])
|
])
|
||||||
|
|
||||||
export const down: Migration = async ({ context: queryInterface }) =>
|
export const down: Migration = async ({ context: queryInterface }) =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
queryInterface.removeColumn("users", "email"),
|
queryInterface.removeColumn("users", "email"),
|
||||||
queryInterface.removeColumn("users", "displayName"),
|
queryInterface.removeColumn("users", "displayName"),
|
||||||
queryInterface.removeColumn("users", "bio"),
|
queryInterface.removeColumn("users", "bio")
|
||||||
])
|
])
|
||||||
|
|
37
server/src/migrations/10_add_server_info_table.ts
Normal file
37
server/src/migrations/10_add_server_info_table.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"use strict"
|
||||||
|
import { DataTypes } from "sequelize"
|
||||||
|
import type { Migration } from "../database"
|
||||||
|
|
||||||
|
export const up: Migration = async ({ context: queryInterface }) =>
|
||||||
|
queryInterface.createTable("server-info", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
defaultValue: 1
|
||||||
|
},
|
||||||
|
welcomeMessage: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
defaultValue:
|
||||||
|
"## Drift is a self-hostable clone of GitHub Gist. \nIt is a simple way to share code and text snippets with your friends, with support for the following:\n \n - Render GitHub Extended Markdown (including images)\n - User authentication\n - Private, public, and password protected posts\n - Markdown is rendered and stored on the server\n - Syntax highlighting and automatic language detection\n - Drag-and-drop file uploading\n\n If you want to signup, you can join at [/signup](/signup) as long as you have a passcode provided by the administrator (which you don't need for this demo). **This demo is on a memory-only database, so accounts and pastes can be deleted at any time.** \n\nYou can find the source code on [GitHub](https://github.com/MaxLeiter/drift).",
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
welcomeTitle: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
defaultValue: "Welcome to Drift",
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
defaultValue: DataTypes.NOW,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
defaultValue: DataTypes.NOW,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const down: Migration = async ({ context: queryInterface }) =>
|
||||||
|
queryInterface.dropTable("server-info")
|
|
@ -4,6 +4,7 @@ import { User } from "@lib/models/User"
|
||||||
import { File } from "@lib/models/File"
|
import { File } from "@lib/models/File"
|
||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import { celebrate, Joi } from "celebrate"
|
import { celebrate, Joi } from "celebrate"
|
||||||
|
import { ServerInfo } from "@lib/models/ServerInfo"
|
||||||
|
|
||||||
export const admin = Router()
|
export const admin = Router()
|
||||||
|
|
||||||
|
@ -197,3 +198,54 @@ admin.delete("/post/:id", async (req, res, next) => {
|
||||||
next(e)
|
next(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
admin.get("/server-info", async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const info = await ServerInfo.findOne({
|
||||||
|
where: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json(info)
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
admin.put(
|
||||||
|
"/server-info",
|
||||||
|
celebrate({
|
||||||
|
body: {
|
||||||
|
title: Joi.string().required(),
|
||||||
|
description: Joi.string().required()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { description, title } = req.body
|
||||||
|
const serverInfo = await ServerInfo.findOne({
|
||||||
|
where: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!serverInfo) {
|
||||||
|
return res.status(404).json({
|
||||||
|
error: "Server info not found"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await serverInfo.update({
|
||||||
|
welcomeMessage: description,
|
||||||
|
welcomeTitle: title
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -195,7 +195,8 @@ auth.post("/signout", secretKey, async (req, res, next) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
auth.put("/change-password",
|
auth.put(
|
||||||
|
"/change-password",
|
||||||
jwt,
|
jwt,
|
||||||
celebrate({
|
celebrate({
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -30,13 +30,14 @@ user.get("/self", jwt, async (req: UserJwtRequest, res, next) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
user.put("/profile",
|
user.put(
|
||||||
|
"/profile",
|
||||||
jwt,
|
jwt,
|
||||||
celebrate({
|
celebrate({
|
||||||
body: {
|
body: {
|
||||||
displayName: Joi.string().optional().allow(""),
|
displayName: Joi.string().optional().allow(""),
|
||||||
bio: Joi.string().optional().allow(""),
|
bio: Joi.string().optional().allow(""),
|
||||||
email: Joi.string().optional().email().allow(""),
|
email: Joi.string().optional().email().allow("")
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
async (req: UserJwtRequest, res, next) => {
|
async (req: UserJwtRequest, res, next) => {
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { createServer } from "http"
|
||||||
import { app } from "./app"
|
import { app } from "./app"
|
||||||
import config from "./lib/config"
|
import config from "./lib/config"
|
||||||
import "./database"
|
import "./database"
|
||||||
|
import { initServerInfo } from "./database"
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
// await sequelize.sync()
|
initServerInfo()
|
||||||
createServer(app).listen(config.port, () =>
|
createServer(app).listen(config.port, () =>
|
||||||
console.info(`Server running on port ${config.port}`)
|
console.info(`Server running on port ${config.port}`)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue