Compare commits
4 Commits
bc9fec207b
...
754a89b913
Author | SHA1 | Date |
---|---|---|
|
754a89b913 | |
|
0d5744e3ff | |
|
9a9b92a6b1 | |
|
de757efc6e |
|
@ -1,6 +1,7 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
config/
|
config/
|
||||||
|
log/
|
||||||
!config/default.js
|
!config/default.js
|
||||||
assets/js/config/
|
assets/js/config/
|
||||||
!assets/js/config/default.js
|
!assets/js/config/default.js
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
.input {
|
.input {
|
||||||
|
box-sizing: border-box;
|
||||||
padding: .5rem .75rem;
|
padding: .5rem .75rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
border: solid 1px var(--grey-light-30);
|
border: solid 1px var(--grey-light-30);
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
|
background: var(--grey-light-60);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--primary-light-30);
|
border-color: var(--primary-light-50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
--primary-light-10: hsl(300, 50%, 40%);
|
--primary-light-10: hsl(300, 50%, 40%);
|
||||||
--primary-light-20: hsl(300, 50%, 50%);
|
--primary-light-20: hsl(300, 50%, 50%);
|
||||||
--primary-light-30: hsl(300, 50%, 60%);
|
--primary-light-30: hsl(300, 50%, 60%);
|
||||||
|
--primary-light-40: hsl(300, 50%, 75%);
|
||||||
|
--primary-light-50: hsl(300, 50%, 80%);
|
||||||
|
|
||||||
--grey-dark-40: #222;
|
--grey-dark-40: #222;
|
||||||
--grey-dark-30: #444;
|
--grey-dark-30: #444;
|
||||||
|
@ -13,6 +15,8 @@
|
||||||
--grey-light-20: #ccc;
|
--grey-light-20: #ccc;
|
||||||
--grey-light-30: #ddd;
|
--grey-light-30: #ddd;
|
||||||
--grey-light-40: #eee;
|
--grey-light-40: #eee;
|
||||||
|
--grey-light-50: #fafafa;
|
||||||
|
--grey-light-60: #fcfcfc;
|
||||||
|
|
||||||
--background-dark-20: #eee;
|
--background-dark-20: #eee;
|
||||||
--background-dark-10: #f8f8f8;
|
--background-dark-10: #f8f8f8;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||||
|
<title>blocked</title>
|
||||||
|
<path d="M27.314 4.686c-3.022-3.022-7.040-4.686-11.314-4.686s-8.292 1.664-11.314 4.686c-3.022 3.022-4.686 7.040-4.686 11.314s1.664 8.292 4.686 11.314c3.022 3.022 7.040 4.686 11.314 4.686s8.292-1.664 11.314-4.686c3.022-3.022 4.686-7.040 4.686-11.314s-1.664-8.292-4.686-11.314zM28 16c0 2.588-0.824 4.987-2.222 6.949l-16.727-16.727c1.962-1.399 4.361-2.222 6.949-2.222 6.617 0 12 5.383 12 12zM4 16c0-2.588 0.824-4.987 2.222-6.949l16.727 16.727c-1.962 1.399-4.361 2.222-6.949 2.222-6.617 0-12-5.383-12-12z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 671 B |
|
@ -15,7 +15,7 @@ function getQuery(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(path, query = {}) {
|
export async function get(path, query = {}) {
|
||||||
const res = await fetch(`${path}${getQuery(query)}`);
|
const res = await fetch(`/api${path}${getQuery(query)}`);
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
@ -26,7 +26,7 @@ export async function get(path, query = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(path, data, { query } = {}) {
|
export async function post(path, data, { query } = {}) {
|
||||||
const res = await fetch(`${path}${getQuery(query)}`, {
|
const res = await fetch(`/api${path}${getQuery(query)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
...postHeaders,
|
...postHeaders,
|
||||||
|
@ -42,11 +42,11 @@ export async function post(path, data, { query } = {}) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(body.message);
|
throw new Error(body.statusMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patch(path, data, { query } = {}) {
|
export async function patch(path, data, { query } = {}) {
|
||||||
const res = await fetch(`${path}${getQuery(query)}`, {
|
const res = await fetch(`/api${path}${getQuery(query)}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
...postHeaders,
|
...postHeaders,
|
||||||
|
@ -66,7 +66,7 @@ export async function patch(path, data, { query } = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(path, { data, query } = {}) {
|
export async function del(path, { data, query } = {}) {
|
||||||
const res = await fetch(`${path}${getQuery(query)}`, {
|
const res = await fetch(`/api${path}${getQuery(query)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
...postHeaders,
|
...postHeaders,
|
||||||
|
|
|
@ -2,3 +2,7 @@
|
||||||
export default function navigate(path) {
|
export default function navigate(path) {
|
||||||
window.location.href = path;
|
window.location.href = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reload() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="comment">
|
||||||
|
<div class="header">
|
||||||
|
<a
|
||||||
|
:href="`/user/${comment.user.username}`"
|
||||||
|
class="username link"
|
||||||
|
>u/{{ comment.user.username }}</a>
|
||||||
|
|
||||||
|
<span
|
||||||
|
:title="format(comment.createdAt, 'MMM d, yyyy hh:mm:ss')"
|
||||||
|
class="timestamp"
|
||||||
|
>{{ formatDistance(comment.createdAt, now, { includeSeconds: true }) }} ago</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="body">{{ comment.body }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { format, formatDistance } from 'date-fns';
|
||||||
|
import { usePageContext } from '../../renderer/usePageContext';
|
||||||
|
|
||||||
|
const { now } = usePageContext();
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
comment: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.comment {
|
||||||
|
background: var(--background);
|
||||||
|
padding: .5rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: .9rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,158 @@
|
||||||
|
<template>
|
||||||
|
<div class="post">
|
||||||
|
<a
|
||||||
|
:href="post.link || `/s/${post.shelf.slug}/post/${post.id}`"
|
||||||
|
target="_blank"
|
||||||
|
class="title-link"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="thumbnail"
|
||||||
|
:src="blockedIcon"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="header">
|
||||||
|
<h2 class="title">
|
||||||
|
<a
|
||||||
|
:href="`/s/${post.shelf.slug}/post/${post.id}`"
|
||||||
|
class="title-link"
|
||||||
|
>{{ post.title }}</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<a
|
||||||
|
v-if="post.link"
|
||||||
|
:href="post.link"
|
||||||
|
target="_blank"
|
||||||
|
class="link"
|
||||||
|
>{{ post.link }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<a
|
||||||
|
:href="`/s/${post.shelf.slug}`"
|
||||||
|
class="shelf link"
|
||||||
|
>s/{{ post.shelf.slug }}</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`/user/${post.user.username}`"
|
||||||
|
class="username link"
|
||||||
|
>u/{{ post.user.username }}</a>
|
||||||
|
|
||||||
|
<span
|
||||||
|
:title="format(post.createdAt, 'MMMM d, yyyy hh:mm:ss')"
|
||||||
|
class="timestamp"
|
||||||
|
>{{ formatDistance(post.createdAt, now, { includeSeconds: true }) }} ago</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a
|
||||||
|
:href="`/s/shack/post/${post.id}`"
|
||||||
|
class="link comments"
|
||||||
|
>{{ post.commentCount }} comments</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`/s/shack/post/${post.id}`"
|
||||||
|
class="fill"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { format, formatDistance } from 'date-fns';
|
||||||
|
import blockedIcon from '../../assets/icons/blocked.svg?url'; // eslint-disable-line import/no-unresolved
|
||||||
|
import { usePageContext } from '../../renderer/usePageContext';
|
||||||
|
|
||||||
|
const { now } = usePageContext();
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
post: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.post {
|
||||||
|
display: flex;
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
background: var(--background);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
& :hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .25rem 0;
|
||||||
|
margin: .25rem 1rem 0 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--grey-dark-30);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
width: 7rem;
|
||||||
|
height: 4rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin: .5rem;
|
||||||
|
background: var(--grey-light-10);
|
||||||
|
opacity: .25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shelf {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: var(--grey-dark-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments {
|
||||||
|
color: inherit;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
const config = require('config');
|
import config from 'config';
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
client: 'pg',
|
client: 'pg',
|
||||||
connection: config.database,
|
connection: config.database,
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,3 +26,61 @@
|
||||||
2023-05-29 00:35:22 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
2023-05-29 00:35:22 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
2023-05-29 00:42:59 [32minfo[39m [redis] Redis module initialized
|
2023-05-29 00:42:59 [32minfo[39m [redis] Redis module initialized
|
||||||
2023-05-29 00:42:59 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
2023-05-29 00:42:59 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 00:55:18 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 00:55:18 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 02:07:51 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 02:07:51 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 02:08:43 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 02:08:43 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:17:42 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:17:43 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:18:29 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:18:29 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:18:40 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:18:40 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:20:45 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:20:45 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:26:36 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:26:36 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:27:24 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:27:25 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:28:06 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:28:07 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:28:49 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:28:49 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 16:28:52 [33mwarn[39m [error] Failed to fulfill request to /: [vite-plugin-ssr@0.4.126][Wrong Usage] pageContext.httpResponse.body can't be used because the render() hook defined by /renderer/_default.page.server.js > `export { render }` provides a Readable Node.js Stream. Use `pageContext.httpResponse.pipe()` or `pageContext.httpResponse.getBody()` instead. See https://vite-plugin-ssr.com/stream for more information.
|
||||||
|
2023-05-29 16:28:52 [31merror[39m [error] Error: [vite-plugin-ssr@0.4.126][Wrong Usage] pageContext.httpResponse.body can't be used because the render() hook defined by /renderer/_default.page.server.js > `export { render }` provides a Readable Node.js Stream. Use `pageContext.httpResponse.pipe()` or `pageContext.httpResponse.getBody()` instead. See https://vite-plugin-ssr.com/stream for more information.
|
||||||
|
at get body [as body] (/home/niels/Projects/shack/node_modules/vite-plugin-ssr/dist/cjs/node/runtime/renderPage/createHttpResponseObject.js:51:41)
|
||||||
|
at defaultHandler (file:///home/niels/Projects/shack/src/web/default.js:19:4)
|
||||||
|
2023-05-29 16:42:10 [33mwarn[39m [error] Failed to fulfill request to /: [vite-plugin-ssr@0.4.126][Wrong Usage] pageContext.httpResponse.body can't be used because the render() hook defined by /renderer/_default.page.server.js > `export { render }` provides a Readable Node.js Stream. Use `pageContext.httpResponse.pipe()` or `pageContext.httpResponse.getBody()` instead. See https://vite-plugin-ssr.com/stream for more information.
|
||||||
|
2023-05-29 16:42:10 [31merror[39m [error] Error: [vite-plugin-ssr@0.4.126][Wrong Usage] pageContext.httpResponse.body can't be used because the render() hook defined by /renderer/_default.page.server.js > `export { render }` provides a Readable Node.js Stream. Use `pageContext.httpResponse.pipe()` or `pageContext.httpResponse.getBody()` instead. See https://vite-plugin-ssr.com/stream for more information.
|
||||||
|
at get body [as body] (/home/niels/Projects/shack/node_modules/vite-plugin-ssr/dist/cjs/node/runtime/renderPage/createHttpResponseObject.js:51:41)
|
||||||
|
at defaultHandler (file:///home/niels/Projects/shack/src/web/default.js:19:4)
|
||||||
|
2023-05-29 16:43:01 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 16:43:01 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:00:19 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:00:20 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:00:45 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:00:45 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:01:19 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:01:19 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:03:49 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:03:49 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:14:16 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:14:17 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:15:52 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:15:52 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:27:43 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:27:44 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:29:14 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:29:14 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:29:33 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:29:33 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:32:52 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:32:52 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:37:32 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:37:32 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:45:26 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:45:26 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
2023-05-29 17:49:05 [32minfo[39m [redis] Redis module initialized
|
||||||
|
2023-05-29 17:49:05 [32minfo[39m [server] Server running at 0.0.0.0:7477
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
exports.up = async function(knex) {
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export async function up(knex) {
|
||||||
|
const nanoidFn = await fs.promises.readFile('./migrations/nanoid.sql', 'utf8'); // from https://github.com/viascom/nanoid-postgres
|
||||||
|
|
||||||
|
await knex.raw(nanoidFn);
|
||||||
|
|
||||||
|
await knex.raw(`
|
||||||
|
CREATE FUNCTION shack_id(length smallint DEFAULT 8) RETURNS TEXT AS $$
|
||||||
|
SELECT nanoid(length, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
||||||
|
$$ LANGUAGE SQL STABLE;
|
||||||
|
`);
|
||||||
|
|
||||||
await knex.schema.createTable('users', (table) => {
|
await knex.schema.createTable('users', (table) => {
|
||||||
table.increments('id');
|
table.increments('id');
|
||||||
|
|
||||||
|
@ -63,13 +75,15 @@ exports.up = async function(knex) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('posts', (table) => {
|
await knex.schema.createTable('posts', (table) => {
|
||||||
table.increments('id');
|
table.text('id', 8)
|
||||||
|
.primary()
|
||||||
|
.defaultTo(knex.raw('shack_id()'));
|
||||||
|
|
||||||
table.text('title')
|
table.text('title')
|
||||||
.notNullable();
|
.notNullable();
|
||||||
|
|
||||||
table.text('body');
|
table.text('body');
|
||||||
table.text('url');
|
table.text('link');
|
||||||
|
|
||||||
table.integer('shelf_id')
|
table.integer('shelf_id')
|
||||||
.notNullable()
|
.notNullable()
|
||||||
|
@ -86,12 +100,43 @@ exports.up = async function(knex) {
|
||||||
.defaultTo(knex.fn.now());
|
.defaultTo(knex.fn.now());
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.raw(`ALTER TABLE posts ADD CONSTRAINT post_content CHECK (body IS NOT NULL OR url IS NOT NULL)`);
|
await knex.raw('ALTER TABLE posts ADD CONSTRAINT post_content CHECK (body IS NOT NULL OR link IS NOT NULL)');
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = async function(knex) {
|
await knex.schema.createTable('comments', (table) => {
|
||||||
|
table.text('id', 8)
|
||||||
|
.primary()
|
||||||
|
.defaultTo(knex.raw('shack_id()'));
|
||||||
|
|
||||||
|
table.text('post_id', 8)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('posts');
|
||||||
|
|
||||||
|
table.integer('parent_comment_id')
|
||||||
|
.references('id')
|
||||||
|
.inTable('comments');
|
||||||
|
|
||||||
|
table.integer('user_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('users');
|
||||||
|
|
||||||
|
table.text('body');
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.dropTable('comments');
|
||||||
await knex.schema.dropTable('posts');
|
await knex.schema.dropTable('posts');
|
||||||
await knex.schema.dropTable('shelves_settings');
|
await knex.schema.dropTable('shelves_settings');
|
||||||
await knex.schema.dropTable('shelves');
|
await knex.schema.dropTable('shelves');
|
||||||
await knex.schema.dropTable('users');
|
await knex.schema.dropTable('users');
|
||||||
};
|
|
||||||
|
await knex.raw(`
|
||||||
|
DROP FUNCTION IF EXISTS shack_id;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Viascom Ltd liab. Co
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION nanoid(
|
||||||
|
size int DEFAULT 21,
|
||||||
|
alphabet text DEFAULT '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
)
|
||||||
|
RETURNS text
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
volatile
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
idBuilder text := '';
|
||||||
|
counter int := 0;
|
||||||
|
bytes bytea;
|
||||||
|
alphabetIndex int;
|
||||||
|
alphabetArray text[];
|
||||||
|
alphabetLength int;
|
||||||
|
mask int;
|
||||||
|
step int;
|
||||||
|
BEGIN
|
||||||
|
alphabetArray := regexp_split_to_array(alphabet, '');
|
||||||
|
alphabetLength := array_length(alphabetArray, 1);
|
||||||
|
mask := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
|
||||||
|
step := cast(ceil(1.6 * mask * size / alphabetLength) AS int);
|
||||||
|
|
||||||
|
while true
|
||||||
|
loop
|
||||||
|
bytes := gen_random_bytes(step);
|
||||||
|
while counter < step
|
||||||
|
loop
|
||||||
|
alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
|
||||||
|
if alphabetIndex <= alphabetLength then
|
||||||
|
idBuilder := idBuilder || alphabetArray[alphabetIndex];
|
||||||
|
if length(idBuilder) = size then
|
||||||
|
return idBuilder;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
counter := counter + 1;
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
counter := 0;
|
||||||
|
end loop;
|
||||||
|
END
|
||||||
|
$$;
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "shack",
|
"name": "shack",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"version": "0.1.0",
|
"name": "shack",
|
||||||
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/cli": "^7.21.5",
|
"@babel/cli": "^7.21.5",
|
||||||
"@babel/core": "^7.21.8",
|
"@babel/core": "^7.21.8",
|
||||||
|
@ -21,6 +22,8 @@
|
||||||
"config": "^3.3.9",
|
"config": "^3.3.9",
|
||||||
"connect-redis": "^7.1.0",
|
"connect-redis": "^7.1.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"error-stack-parser": "^2.1.4",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-plugin-vue": "^9.14.0",
|
"eslint-plugin-vue": "^9.14.0",
|
||||||
|
@ -34,6 +37,7 @@
|
||||||
"pg": "^8.11.0",
|
"pg": "^8.11.0",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
"redis": "^4.6.6",
|
"redis": "^4.6.6",
|
||||||
|
"short-uuid": "^4.2.2",
|
||||||
"sirv": "^2.0.2",
|
"sirv": "^2.0.2",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.3",
|
||||||
"vite-plugin-ssr": "^0.4.126",
|
"vite-plugin-ssr": "^0.4.126",
|
||||||
|
@ -2621,6 +2625,11 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/any-base": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
|
||||||
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
|
@ -3379,6 +3388,21 @@
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "2.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||||
|
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.21.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
@ -3530,6 +3554,14 @@
|
||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/error-stack-parser": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"stackframe": "^1.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/errors": {
|
"node_modules/errors": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/errors/-/errors-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/errors/-/errors-0.2.0.tgz",
|
||||||
|
@ -6737,6 +6769,26 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/short-uuid": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||||
|
"dependencies": {
|
||||||
|
"any-base": "^1.1.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/short-uuid/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
|
@ -6808,6 +6860,11 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stackframe": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
@ -9555,6 +9612,11 @@
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"any-base": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
|
||||||
|
},
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
|
@ -10120,6 +10182,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||||
},
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"version": "2.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||||
|
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
@ -10228,6 +10298,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
|
||||||
},
|
},
|
||||||
|
"error-stack-parser": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
|
||||||
|
"requires": {
|
||||||
|
"stackframe": "^1.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/errors/-/errors-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/errors/-/errors-0.2.0.tgz",
|
||||||
|
@ -12511,6 +12589,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||||
},
|
},
|
||||||
|
"short-uuid": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||||
|
"requires": {
|
||||||
|
"any-base": "^1.1.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"side-channel": {
|
"side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
|
@ -12564,6 +12658,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||||
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
|
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
|
||||||
},
|
},
|
||||||
|
"stackframe": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
||||||
|
},
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
|
24
package.json
24
package.json
|
@ -1,27 +1,28 @@
|
||||||
{
|
{
|
||||||
"name": "shack",
|
"name": "shack",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "Shack is a self-hosted social news aggregate",
|
"description": "Shack is a self-hosted social news aggregate",
|
||||||
"main": "src/web/server.js",
|
"main": "src/web/server.js",
|
||||||
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.unknown.name/DebaucheryLibrarian/shack"
|
"url": "https://gitea.unknown.name/DebaucheryLibrarian/shack"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"aggregate",
|
"aggregate",
|
||||||
"social",
|
"social",
|
||||||
"communities"
|
"communities"
|
||||||
],
|
],
|
||||||
"author": "DebaucheryLibrarian",
|
"author": "DebaucheryLibrarian",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run server",
|
"dev": "npm run server",
|
||||||
"prod": "npm run build && npm run server:prod",
|
"prod": "npm run build && npm run server:prod",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"server": "node ./src/web/server",
|
"server": "node --experimental-specifier-resolution=node ./src/web/server",
|
||||||
"server:prod": "cross-env NODE_ENV=production node ./src/web/server",
|
"server:prod": "cross-env NODE_ENV=production node ./src/web/server",
|
||||||
"migrate-make": "knex-migrate generate",
|
"migrate-make": "knex migrate:make",
|
||||||
"migrate": "knex-migrate up",
|
"migrate": "knex migrate:latest",
|
||||||
"rollback": "knex-migrate down"
|
"rollback": "knex migrate:rollback"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/cli": "^7.21.5",
|
"@babel/cli": "^7.21.5",
|
||||||
|
@ -38,6 +39,8 @@
|
||||||
"config": "^3.3.9",
|
"config": "^3.3.9",
|
||||||
"connect-redis": "^7.1.0",
|
"connect-redis": "^7.1.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"error-stack-parser": "^2.1.4",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-plugin-vue": "^9.14.0",
|
"eslint-plugin-vue": "^9.14.0",
|
||||||
|
@ -51,6 +54,7 @@
|
||||||
"pg": "^8.11.0",
|
"pg": "^8.11.0",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
"redis": "^4.6.6",
|
"redis": "^4.6.6",
|
||||||
|
"short-uuid": "^4.2.2",
|
||||||
"sirv": "^2.0.2",
|
"sirv": "^2.0.2",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.3",
|
||||||
"vite-plugin-ssr": "^0.4.126",
|
"vite-plugin-ssr": "^0.4.126",
|
||||||
|
|
|
@ -104,7 +104,7 @@ const errorMsg = ref(null);
|
||||||
|
|
||||||
async function signup() {
|
async function signup() {
|
||||||
try {
|
try {
|
||||||
await post('/api/users', {
|
await post('/users', {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
email: email.value,
|
email: email.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
|
|
|
@ -33,21 +33,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
|
||||||
<div
|
<div
|
||||||
v-if="$config.public.captchaEnabled"
|
v-if="config.captchaEnabled"
|
||||||
class="form-row captcha"
|
class="form-row captcha"
|
||||||
>
|
>
|
||||||
<VueHcaptcha
|
<VueHcaptcha
|
||||||
:sitekey="$config.public.captchaKey"
|
:sitekey="config.captchaKey"
|
||||||
@verify="(token) => captchaToken = token"
|
@verify="(token) => captchaToken = token"
|
||||||
@expired="() => captchaToken = null"
|
@expired="() => captchaToken = null"
|
||||||
@error="() => captchaToken = null"
|
@error="() => captchaToken = null"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="form-row form-actions">
|
<div class="form-row form-actions">
|
||||||
|
<a
|
||||||
|
href="/account/create"
|
||||||
|
class="link"
|
||||||
|
>Sign up</a>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
:disabled="!username || !password"
|
:disabled="!username || !password"
|
||||||
class="button button-submit"
|
class="button button-submit"
|
||||||
|
@ -59,11 +62,13 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
// import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
|
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
|
||||||
|
|
||||||
import { post } from '../../assets/js/api';
|
import { post } from '../../assets/js/api';
|
||||||
import navigate from '../../assets/js/navigate';
|
import navigate from '../../assets/js/navigate';
|
||||||
|
|
||||||
|
const config = CONFIG;
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
|
|
||||||
|
@ -71,14 +76,14 @@ const errorMsg = ref(null);
|
||||||
|
|
||||||
async function signup() {
|
async function signup() {
|
||||||
try {
|
try {
|
||||||
await post('/api/session', {
|
await post('/session', {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMsg.value = error.statusMessage;
|
errorMsg.value = error.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -137,6 +142,7 @@ async function signup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { fetchShelves } from '../../src/shelves';
|
||||||
|
|
||||||
|
async function getPageData() {
|
||||||
|
const shelves = await fetchShelves();
|
||||||
|
|
||||||
|
return {
|
||||||
|
shelves,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPageData };
|
|
@ -1,17 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a
|
|
||||||
href="/shelf/1"
|
|
||||||
class="link"
|
|
||||||
>Go to shelf</a></li>
|
|
||||||
|
|
||||||
<li><a
|
<li><a
|
||||||
href="/shelf/create"
|
href="/shelf/create"
|
||||||
class="link"
|
class="link"
|
||||||
>Create new shelf</a></li>
|
>Create new shelf</a></li>
|
||||||
|
|
||||||
<li><a
|
<li v-if="!user"><a
|
||||||
href="/account/login"
|
href="/account/login"
|
||||||
class="link"
|
class="link"
|
||||||
>Log in</a></li>
|
>Log in</a></li>
|
||||||
|
@ -21,5 +16,22 @@
|
||||||
class="link"
|
class="link"
|
||||||
>Sign up</a></li>
|
>Sign up</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="shelf in shelves"
|
||||||
|
:key="shelf.id"
|
||||||
|
><a
|
||||||
|
:href="`/s/${shelf.slug}`"
|
||||||
|
class="link"
|
||||||
|
>{{ shelf.slug }}</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { usePageContext } from '../../renderer/usePageContext';
|
||||||
|
|
||||||
|
const { user, pageData } = usePageContext();
|
||||||
|
const { shelves } = pageData;
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export default '/s/@id/post/@postId';
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { RenderErrorPage } from 'vite-plugin-ssr/RenderErrorPage';
|
||||||
|
import { fetchShelf } from '../../src/shelves';
|
||||||
|
import { fetchPost } from '../../src/posts';
|
||||||
|
import { fetchPostComments } from '../../src/comments';
|
||||||
|
|
||||||
|
async function getPageData(pageContext) {
|
||||||
|
const [shelf, post, comments] = await Promise.all([
|
||||||
|
fetchShelf(pageContext.routeParams.id),
|
||||||
|
fetchPost(pageContext.routeParams.postId),
|
||||||
|
fetchPostComments(pageContext.routeParams.postId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!shelf) {
|
||||||
|
throw RenderErrorPage({
|
||||||
|
pageContext: {
|
||||||
|
pageProps: {
|
||||||
|
errorInfo: 'This shelf does not exist',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
throw RenderErrorPage({
|
||||||
|
pageContext: {
|
||||||
|
pageProps: {
|
||||||
|
errorInfo: 'This post does not exist',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shelf,
|
||||||
|
post,
|
||||||
|
comments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPageData };
|
|
@ -0,0 +1,107 @@
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<div class="body">
|
||||||
|
<Post :post="post" />
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="writer"
|
||||||
|
@submit.prevent="addComment"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="newComment"
|
||||||
|
placeholder="Write a new comment"
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button">Comment</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul class="comments nolist">
|
||||||
|
<li
|
||||||
|
v-for="comment in comments"
|
||||||
|
:key="comment.id"
|
||||||
|
><Comment :comment="comment" /></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar">
|
||||||
|
{{ shelf.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Post from '../../components/posts/post.vue';
|
||||||
|
import Comment from '../../components/comments/comment.vue';
|
||||||
|
import * as api from '../../assets/js/api';
|
||||||
|
import { reload } from '../../assets/js/navigate';
|
||||||
|
|
||||||
|
import { usePageContext } from '../../renderer/usePageContext';
|
||||||
|
|
||||||
|
const { pageData } = usePageContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
shelf,
|
||||||
|
post,
|
||||||
|
comments,
|
||||||
|
} = pageData;
|
||||||
|
|
||||||
|
const newComment = ref('');
|
||||||
|
|
||||||
|
async function addComment() {
|
||||||
|
await api.post(`/posts/${post.id}/comments`, {
|
||||||
|
body: newComment.value,
|
||||||
|
parentId: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writer {
|
||||||
|
width: 40rem;
|
||||||
|
background: var(--background);
|
||||||
|
padding: .5rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: var(--background);
|
||||||
|
width: 20rem;
|
||||||
|
padding: .5rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { RenderErrorPage } from 'vite-plugin-ssr/RenderErrorPage';
|
||||||
|
import { fetchShelf } from '../../../src/shelves';
|
||||||
|
import { fetchShelfPosts } from '../../../src/posts';
|
||||||
|
|
||||||
|
async function getPageData(pageContext) {
|
||||||
|
const shelf = await fetchShelf(pageContext.routeParams.id);
|
||||||
|
const posts = await fetchShelfPosts(pageContext.routeParams.id, { limit: 50 });
|
||||||
|
|
||||||
|
if (!shelf) {
|
||||||
|
throw RenderErrorPage({
|
||||||
|
pageContext: {
|
||||||
|
pageProps: {
|
||||||
|
errorInfo: 'No shelf with this name exists',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shelf,
|
||||||
|
posts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPageData };
|
|
@ -1,11 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<a
|
<h3>{{ shelf.slug }}</h3>
|
||||||
href="/"
|
|
||||||
class="link"
|
|
||||||
>Go back home</a>
|
|
||||||
|
|
||||||
<h3>{{ shuck }}</h3>
|
<ul class="posts nolist">
|
||||||
|
<li
|
||||||
|
v-for="post in posts"
|
||||||
|
:key="post.id"
|
||||||
|
><Post :post="post" /></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="form compose"
|
class="form compose"
|
||||||
|
@ -44,28 +46,33 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import Post from '../../../components/posts/post.vue';
|
||||||
|
|
||||||
const shuck = 'Eratic';
|
import * as api from '../../../assets/js/api';
|
||||||
|
import { usePageContext } from '../../../renderer/usePageContext';
|
||||||
|
|
||||||
// const route = useRoute();
|
const { pageData, routeParams } = usePageContext();
|
||||||
// const res = await useFetch(`/api/shucks/${route.params.id}/posts`);
|
|
||||||
|
const {
|
||||||
|
shelf,
|
||||||
|
posts,
|
||||||
|
} = pageData;
|
||||||
|
|
||||||
const title = ref();
|
const title = ref();
|
||||||
const link = ref();
|
const link = ref();
|
||||||
const body = ref();
|
const body = ref();
|
||||||
|
|
||||||
async function submitPost() {
|
async function submitPost() {
|
||||||
console.log('POST', link.value);
|
await api.post(`/api/shelves/${routeParams.id}/posts`, {
|
||||||
|
title: title.value,
|
||||||
/*
|
link: link.value,
|
||||||
await useFetch(`/api/shucks/${route.params.id}/posts`, {
|
body: body.value,
|
||||||
method: 'post',
|
|
||||||
body: {
|
|
||||||
title,
|
|
||||||
link,
|
|
||||||
body,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.posts {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -137,7 +137,7 @@ const postAccess = ref('registered');
|
||||||
const isNsfw = ref(false);
|
const isNsfw = ref(false);
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
await post('/api/shelves', {
|
await post('/shelves', {
|
||||||
slug: slug.value,
|
slug: slug.value,
|
||||||
title: title.value,
|
title: title.value,
|
||||||
description: description.value,
|
description: description.value,
|
||||||
|
|
|
@ -1,35 +1,20 @@
|
||||||
import { renderToString as renderToString_ } from '@vue/server-renderer';
|
// import { renderToString as renderToString_ } from '@vue/server-renderer';
|
||||||
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server';
|
import { renderToNodeStream } from '@vue/server-renderer';
|
||||||
|
import { escapeInject } from 'vite-plugin-ssr/server';
|
||||||
|
|
||||||
import { createApp } from './app';
|
import { createApp } from './app';
|
||||||
import { useUser } from '../stores/user';
|
import { useUser } from '../stores/user';
|
||||||
import logoUrl from './logo.svg';
|
import logoUrl from './logo.svg';
|
||||||
|
|
||||||
async function renderToString(app) {
|
|
||||||
let err;
|
|
||||||
|
|
||||||
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
|
|
||||||
app.config.errorHandler = (err_) => { // eslint-disable-line no-param-reassign
|
|
||||||
err = err_;
|
|
||||||
};
|
|
||||||
|
|
||||||
const appHtml = await renderToString_(app);
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return appHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function render(pageContext) {
|
async function render(pageContext) {
|
||||||
const appHtml = await renderToString(pageContext.app);
|
|
||||||
|
|
||||||
// See https://vite-plugin-ssr.com/head
|
// See https://vite-plugin-ssr.com/head
|
||||||
const { documentProps } = pageContext.exports;
|
const { documentProps } = pageContext.exports;
|
||||||
const title = (documentProps && documentProps.title) || 'shack';
|
const title = (documentProps && documentProps.title) || 'shack';
|
||||||
const desc = (documentProps && documentProps.description) || 'Shack';
|
const desc = (documentProps && documentProps.description) || 'Shack';
|
||||||
|
|
||||||
|
const { app } = createApp(pageContext);
|
||||||
|
const stream = renderToNodeStream(app);
|
||||||
|
|
||||||
const documentHtml = escapeInject`
|
const documentHtml = escapeInject`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -41,33 +26,33 @@ async function render(pageContext) {
|
||||||
<title>${title}</title>
|
<title>${title}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">${dangerouslySkipEscape(appHtml)}</div>
|
<div id="app">${stream}</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return {
|
|
||||||
documentHtml,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onBeforeRender(pageContext) {
|
|
||||||
if (!pageContext.Page) {
|
|
||||||
throw new Error('My render() hook expects pageContext.Page to be defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app, store } = createApp(pageContext);
|
|
||||||
const userStore = useUser();
|
const userStore = useUser();
|
||||||
|
|
||||||
userStore.user = pageContext.session.user;
|
userStore.user = pageContext.session.user;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
documentHtml,
|
||||||
pageContext: {
|
pageContext: {
|
||||||
app,
|
// initialState: store.state.value,
|
||||||
initialState: store.state.value,
|
enableEagerStreaming: true,
|
||||||
pageData: {
|
},
|
||||||
user: pageContext.session.user,
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
|
async function onBeforeRender(pageContext) {
|
||||||
|
const pageData = await pageContext.exports.getPageData?.(pageContext, pageContext.session);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pageContext: {
|
||||||
|
// initialState: store.state.value,
|
||||||
|
pageData,
|
||||||
|
user: pageContext.session.user,
|
||||||
|
now: new Date(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -77,4 +62,4 @@ export {
|
||||||
onBeforeRender,
|
onBeforeRender,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const passToClient = ['urlPathname', 'initialState', 'pageData'];
|
export const passToClient = ['urlPathname', 'initialState', 'pageData', 'pageProps', 'routeParams', 'user', 'now'];
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="is404">
|
<div class="content">
|
||||||
<h1>404 Page Not Found</h1>
|
<div v-if="is404">
|
||||||
<p>This page could not be found.</p>
|
<h1>404 Page Not Found</h1>
|
||||||
</div>
|
|
||||||
<div v-else>
|
<p v-if="errorInfo">{{ errorInfo }}</p>
|
||||||
<h1>500 Internal Error</h1>
|
<p v-else>This page could not be found.</p>
|
||||||
<p>Something went wrong.</p>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div v-else>
|
||||||
|
<h1>500 Internal Error</h1>
|
||||||
|
|
||||||
|
<p v-if="errorInfo">{{ errorInfo }}</p>
|
||||||
|
<p v-else>Something went wrong.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps(['is404'])
|
import { usePageContext } from './usePageContext';
|
||||||
|
|
||||||
|
const { pageProps } = usePageContext();
|
||||||
|
const { is404, errorInfo } = pageProps;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,29 +27,22 @@
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<slot />
|
<slot />
|
||||||
<footer class="footer">shuck {{ version }}</footer>
|
<footer class="footer">shack {{ version }}</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// import { onMounted } from 'vue';
|
import logo from '../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
||||||
// import { usePageContext } from './usePageContext';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import logo from '../assets/img/logo.svg?raw';
|
|
||||||
|
|
||||||
import { del } from '../assets/js/api';
|
import { del } from '../assets/js/api';
|
||||||
import { useUser } from '../stores/user';
|
import { usePageContext } from './usePageContext';
|
||||||
|
|
||||||
// const pageContext = usePageContext();
|
const { user } = usePageContext();
|
||||||
const version = CLIENT_VERSION;
|
const version = CLIENT_VERSION;
|
||||||
|
|
||||||
const userStore = useUser();
|
|
||||||
const { user } = storeToRefs(userStore);
|
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await del('/api/session');
|
await del('/api/session');
|
||||||
window.location.reload();
|
window.location.href = '/account/login';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "@babel/eslint-parser",
|
"parser": "@babel/eslint-parser",
|
||||||
"ecmaVersion": 2019,
|
"ecmaVersion": 2019,
|
||||||
"sourceType": "script",
|
"sourceType": "module",
|
||||||
"requireConfigFile": false
|
"requireConfigFile": false
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// const config = require('config');
|
// import config from 'config';
|
||||||
const yargs = require('yargs');
|
import yargs from 'yargs';
|
||||||
|
|
||||||
const { argv } = yargs
|
const { argv } = yargs
|
||||||
.command('npm start')
|
.command('npm start')
|
||||||
|
@ -8,4 +8,4 @@ const { argv } = yargs
|
||||||
type: 'string',
|
type: 'string',
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = argv;
|
export default argv;
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import knex from './knex';
|
||||||
|
import { fetchUsers } from './users';
|
||||||
|
import { verifyPrivilege } from './privileges';
|
||||||
|
|
||||||
|
function curateDatabaseComment(comment, { users }) {
|
||||||
|
return {
|
||||||
|
id: comment.id,
|
||||||
|
body: comment.body,
|
||||||
|
userId: comment.user_id,
|
||||||
|
user: users.find((user) => user.id === comment.user_id),
|
||||||
|
createdAt: comment.created_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPostComments(postId, { limit = 100 } = {}) {
|
||||||
|
const comments = await knex('comments')
|
||||||
|
.where('post_id', postId)
|
||||||
|
.orderBy('created_at', 'asc')
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
const users = await fetchUsers(comments.map((comment) => comment.user_id));
|
||||||
|
|
||||||
|
return comments.map((comment) => curateDatabaseComment(comment, { users }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addComment(comment, postId, user) {
|
||||||
|
await verifyPrivilege('addComment', user);
|
||||||
|
|
||||||
|
const commentEntry = await knex('comments')
|
||||||
|
.insert({
|
||||||
|
body: comment.body,
|
||||||
|
post_id: postId,
|
||||||
|
user_id: user.id,
|
||||||
|
})
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
|
console.log(comment, user);
|
||||||
|
return curateDatabaseComment(commentEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchPostComments,
|
||||||
|
addComment,
|
||||||
|
};
|
|
@ -9,4 +9,4 @@ class HttpError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { HttpError };
|
export { HttpError };
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const config = require('config');
|
import config from 'config';
|
||||||
const knex = require('knex');
|
import knex from 'knex';
|
||||||
|
|
||||||
module.exports = knex({
|
export default knex({
|
||||||
client: 'pg',
|
client: 'pg',
|
||||||
connection: config.database,
|
connection: config.database,
|
||||||
// performance overhead, don't use asyncStackTraces in production
|
// performance overhead, don't use asyncStackTraces in production
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
const util = require('util');
|
import util from 'util';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const winston = require('winston');
|
import * as winston from 'winston';
|
||||||
|
import 'winston-daily-rotate-file';
|
||||||
require('winston-daily-rotate-file');
|
import stackParser from 'error-stack-parser';
|
||||||
|
|
||||||
// import args from './args';
|
// import args from './args';
|
||||||
|
|
||||||
module.exports = function initLogger(filepath) {
|
export default function initLogger(customLabel) {
|
||||||
const contextLabel = path.basename(filepath, '.js');
|
const filepath = stackParser.parse(new Error())[1]?.fileName;
|
||||||
|
const contextLabel = customLabel || path.basename(filepath, '.js');
|
||||||
|
|
||||||
return winston.createLogger({
|
return winston.createLogger({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
|
@ -32,4 +33,4 @@ module.exports = function initLogger(filepath) {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// import knex from './knex';
|
||||||
|
import { verifyPrivilege } from './privileges';
|
||||||
|
import knex from './knex';
|
||||||
|
import { HttpError } from './errors';
|
||||||
|
|
||||||
|
import { fetchShelf } from './shelves';
|
||||||
|
import { fetchUsers } from './users';
|
||||||
|
|
||||||
|
function curatePost(post, { shelf, users }) {
|
||||||
|
const curatedPost = {
|
||||||
|
id: post.id,
|
||||||
|
title: post.title,
|
||||||
|
body: post.body,
|
||||||
|
link: post.link,
|
||||||
|
shelfId: post.shelf_id,
|
||||||
|
createdAt: post.created_at,
|
||||||
|
shelf,
|
||||||
|
user: users.find((user) => user.id === post.user_id),
|
||||||
|
commentCount: Number(post.comment_count),
|
||||||
|
};
|
||||||
|
|
||||||
|
return curatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchShelfPosts(shelfId, { limit = 100 } = {}) {
|
||||||
|
const shelf = await fetchShelf(shelfId);
|
||||||
|
|
||||||
|
const posts = await knex('posts')
|
||||||
|
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
|
||||||
|
.leftJoin('comments', 'comments.post_id', 'posts.id')
|
||||||
|
.where('shelf_id', shelf.id)
|
||||||
|
.orderBy('created_at', 'desc')
|
||||||
|
.groupBy('posts.id')
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
const users = await fetchUsers(posts.map((post) => post.user_id));
|
||||||
|
|
||||||
|
return posts.map((post) => curatePost(post, { shelf, users }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPost(postId) {
|
||||||
|
const post = await knex('posts')
|
||||||
|
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
|
||||||
|
.leftJoin('comments', 'comments.post_id', 'posts.id')
|
||||||
|
.where('posts.id', postId)
|
||||||
|
.groupBy('posts.id')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusMessage: 'This post does not exist',
|
||||||
|
statusCode: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [shelf, users] = await Promise.all([
|
||||||
|
fetchShelf(post.shelf_id),
|
||||||
|
fetchUsers([post.user_id]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return curatePost(post, { shelf, users });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPost(post, shelfId, user) {
|
||||||
|
await verifyPrivilege('createPost', user);
|
||||||
|
|
||||||
|
const shelf = await fetchShelf(shelfId);
|
||||||
|
|
||||||
|
if (!shelf) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusMessage: 'The target shelf does not exist',
|
||||||
|
statusCode: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(post);
|
||||||
|
|
||||||
|
const postId = await knex('posts')
|
||||||
|
.insert({
|
||||||
|
title: post.title,
|
||||||
|
body: post.body,
|
||||||
|
link: post.link,
|
||||||
|
shelf_id: shelf.id,
|
||||||
|
user_id: user.id,
|
||||||
|
})
|
||||||
|
.returning('id');
|
||||||
|
|
||||||
|
return postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createPost,
|
||||||
|
fetchPost,
|
||||||
|
fetchShelfPosts,
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { HttpError } from './errors';
|
||||||
|
|
||||||
|
function verifyPrivilege(privilege, user, context) {
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusMessage: 'You are not authenticated',
|
||||||
|
statusCode: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('verify privilege', privilege, user, context);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
verifyPrivilege,
|
||||||
|
};
|
13
src/redis.js
13
src/redis.js
|
@ -1,6 +1,9 @@
|
||||||
const config = require('config');
|
import config from 'config';
|
||||||
const redis = require('redis');
|
import redis from 'redis';
|
||||||
const logger = require('./logger')(__filename);
|
|
||||||
|
import initLogger from './logger';
|
||||||
|
|
||||||
|
const logger = initLogger();
|
||||||
|
|
||||||
const client = redis.createClient({
|
const client = redis.createClient({
|
||||||
socket: config.redis,
|
socket: config.redis,
|
||||||
|
@ -9,6 +12,4 @@ const client = redis.createClient({
|
||||||
client.connect();
|
client.connect();
|
||||||
logger.info('Redis module initialized');
|
logger.info('Redis module initialized');
|
||||||
|
|
||||||
module.exports = {
|
export { client };
|
||||||
client,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,17 +1,56 @@
|
||||||
const knex = require('./knex');
|
import knex from './knex';
|
||||||
|
|
||||||
async function createShelf(shelf) {
|
function curateDatabaseShelf(shelf) {
|
||||||
console.log('create', shelf);
|
if (!shelf) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const shelfEntry = await knex('shelves').insert({
|
return {
|
||||||
|
id: shelf.id,
|
||||||
slug: shelf.slug,
|
slug: shelf.slug,
|
||||||
});
|
name: shelf.slug,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchShelf(shelfId) {
|
||||||
|
const shelfEntry = await knex('shelves')
|
||||||
|
.where((builder) => {
|
||||||
|
const id = Number(shelfId);
|
||||||
|
|
||||||
|
if (Number.isNaN(id)) {
|
||||||
|
builder.where('slug', shelfId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.where('id', shelfId);
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return curateDatabaseShelf(shelfEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchShelves({ limit = 10 } = {}) {
|
||||||
|
const shelfEntries = await knex('shelves').limit(limit);
|
||||||
|
|
||||||
|
return shelfEntries.map((shelfEntry) => curateDatabaseShelf(shelfEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createShelf(shelf, user) {
|
||||||
|
const shelfEntry = await knex('shelves')
|
||||||
|
.insert({
|
||||||
|
slug: shelf.slug,
|
||||||
|
founder_id: user.id,
|
||||||
|
})
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
console.log('entry', shelfEntry);
|
console.log('entry', shelfEntry);
|
||||||
|
|
||||||
return true;
|
return curateDatabaseShelf(shelfEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
|
fetchShelf,
|
||||||
|
fetchShelves,
|
||||||
createShelf,
|
createShelf,
|
||||||
|
curateDatabaseShelf,
|
||||||
};
|
};
|
||||||
|
|
25
src/users.js
25
src/users.js
|
@ -1,12 +1,13 @@
|
||||||
const config = require('config');
|
import config from 'config';
|
||||||
const util = require('util');
|
import util from 'util';
|
||||||
const crypto = require('crypto');
|
import crypto from 'crypto';
|
||||||
const bhttp = require('bhttp');
|
import bhttp from 'bhttp';
|
||||||
|
|
||||||
const logger = require('./logger')(__filename);
|
import initLogger from './logger';
|
||||||
const { HttpError } = require('./errors');
|
import { HttpError } from './errors';
|
||||||
const knex = require('./knex');
|
import knex from './knex';
|
||||||
|
|
||||||
|
const logger = initLogger();
|
||||||
const scrypt = util.promisify(crypto.scrypt);
|
const scrypt = util.promisify(crypto.scrypt);
|
||||||
|
|
||||||
function curateDatabaseUser(user) {
|
function curateDatabaseUser(user) {
|
||||||
|
@ -88,6 +89,12 @@ async function login(credentials) {
|
||||||
return curateDatabaseUser(user);
|
return curateDatabaseUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchUsers(userIds) {
|
||||||
|
const users = await knex('users').whereIn('id', userIds);
|
||||||
|
|
||||||
|
return users.map((user) => curateDatabaseUser(user));
|
||||||
|
}
|
||||||
|
|
||||||
async function createUser(credentials, context) {
|
async function createUser(credentials, context) {
|
||||||
if (!credentials.username) {
|
if (!credentials.username) {
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
|
@ -141,7 +148,9 @@ async function createUser(credentials, context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
|
curateDatabaseUser,
|
||||||
createUser,
|
createUser,
|
||||||
|
fetchUsers,
|
||||||
login,
|
login,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { addComment } from '../comments';
|
||||||
|
|
||||||
|
async function addCommentApi(req, res) {
|
||||||
|
const comment = await addComment(req.body, req.params.postId, req.user);
|
||||||
|
|
||||||
|
res.send(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
addCommentApi as addComment,
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
const { renderPage } = require('vite-plugin-ssr/server');
|
import { renderPage } from 'vite-plugin-ssr/server';
|
||||||
|
|
||||||
async function initDefaultHandler() {
|
export default async function initDefaultHandler() {
|
||||||
async function defaultHandler(req, res, next) {
|
async function defaultHandler(req, res, next) {
|
||||||
const pageContextInit = {
|
const pageContextInit = {
|
||||||
urlOriginal: req.originalUrl,
|
urlOriginal: req.originalUrl,
|
||||||
|
@ -16,9 +16,11 @@ async function initDefaultHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
body, statusCode, contentType, earlyHints,
|
statusCode, contentType, earlyHints,
|
||||||
} = httpResponse;
|
} = httpResponse;
|
||||||
|
|
||||||
|
const body = await httpResponse.getBody();
|
||||||
|
|
||||||
if (res.writeEarlyHints) {
|
if (res.writeEarlyHints) {
|
||||||
res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
|
res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
|
||||||
}
|
}
|
||||||
|
@ -28,5 +30,3 @@ async function initDefaultHandler() {
|
||||||
|
|
||||||
return defaultHandler;
|
return defaultHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = initDefaultHandler;
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const logger = require('../logger')(__filename);
|
import initLogger from '../logger';
|
||||||
|
|
||||||
function errorHandler(error, req, res, _next) {
|
const logger = initLogger();
|
||||||
|
|
||||||
|
export default function errorHandler(error, req, res, _next) {
|
||||||
logger.warn(`Failed to fulfill request to ${req.path}: ${error.message}`);
|
logger.warn(`Failed to fulfill request to ${req.path}: ${error.message}`);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
@ -10,7 +12,7 @@ function errorHandler(error, req, res, _next) {
|
||||||
if (error.statusCode) {
|
if (error.statusCode) {
|
||||||
res.status(error.statusCode).send({
|
res.status(error.statusCode).send({
|
||||||
statusCode: error.statusCode,
|
statusCode: error.statusCode,
|
||||||
message: error.statusMessage,
|
statusMessage: error.statusMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -18,5 +20,3 @@ function errorHandler(error, req, res, _next) {
|
||||||
|
|
||||||
res.status(500).send('Something didn\'t quite go as expected... Our apologies for the inconvenience.');
|
res.status(500).send('Something didn\'t quite go as expected... Our apologies for the inconvenience.');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = errorHandler;
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const IPCIDR = require('ip-cidr');
|
import IPCIDR from 'ip-cidr';
|
||||||
|
|
||||||
function setIp(req, res, next) {
|
export default function setIp(req, res, next) {
|
||||||
const ip = req.headers['x-forwarded-for']
|
const ip = req.headers['x-forwarded-for']
|
||||||
? req.headers['x-forwarded-for'].split(',')[0]
|
? req.headers['x-forwarded-for'].split(',')[0]
|
||||||
: req.connection.remoteAddress;
|
: req.connection.remoteAddress;
|
||||||
|
@ -14,5 +14,3 @@ function setIp(req, res, next) {
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = setIp;
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { createPost } from '../posts';
|
||||||
|
|
||||||
|
async function createPostApi(req, res) {
|
||||||
|
const post = await createPost(req.body, req.params.shelfId, req.user);
|
||||||
|
|
||||||
|
res.send(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createPostApi as createPost,
|
||||||
|
};
|
|
@ -1,32 +1,34 @@
|
||||||
// Note that this file isn't processed by Vite, see https://github.com/brillout/vite-plugin-ssr/issues/562
|
import config from 'config';
|
||||||
const config = require('config');
|
import express from 'express';
|
||||||
const express = require('express');
|
import Router from 'express-promise-router';
|
||||||
const Router = require('express-promise-router');
|
import bodyParser from 'body-parser';
|
||||||
const session = require('express-session');
|
import session from 'express-session';
|
||||||
const RedisStore = require('connect-redis').default;
|
import RedisStore from 'connect-redis';
|
||||||
const bodyParser = require('body-parser');
|
import compression from 'compression';
|
||||||
const compression = require('compression');
|
import sirv from 'sirv';
|
||||||
const sirv = require('sirv');
|
import * as vite from 'vite';
|
||||||
const vite = require('vite');
|
|
||||||
|
|
||||||
const logger = require('../logger')(__filename);
|
import { client as redis } from '../redis';
|
||||||
const redis = require('../redis').client;
|
|
||||||
|
|
||||||
const initDefaultHandler = require('./default');
|
import initLogger from '../logger';
|
||||||
|
import initDefaultHandler from './default';
|
||||||
|
import setIp from './ip';
|
||||||
|
import errorHandler from './error';
|
||||||
|
|
||||||
const setIp = require('./ip');
|
import {
|
||||||
const errorHandler = require('./error');
|
setUser,
|
||||||
|
|
||||||
const {
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
createUser,
|
createUser,
|
||||||
} = require('./users');
|
} from './users';
|
||||||
|
|
||||||
const { createShelf } = require('./shelves');
|
import { createShelf } from './shelves';
|
||||||
|
|
||||||
const root = `${__dirname}/../..`;
|
import { createPost } from './posts';
|
||||||
|
import { addComment } from './comments';
|
||||||
|
|
||||||
|
const logger = initLogger();
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
@ -38,29 +40,39 @@ async function startServer() {
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
|
|
||||||
router.use(session({ ...config.web.session, store: sessionStore }));
|
|
||||||
router.use(setIp);
|
|
||||||
router.use(bodyParser.json({ strict: false }));
|
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
|
|
||||||
|
router.use(session({ ...config.web.session, store: sessionStore }));
|
||||||
|
router.use(setIp);
|
||||||
|
router.use(setUser);
|
||||||
|
router.use(bodyParser.json({ strict: false }));
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
app.use(sirv(`${root}/dist/client`));
|
app.use(sirv('dist/client'));
|
||||||
} else {
|
} else {
|
||||||
const viteDevMiddleware = (await vite.createServer({
|
const viteDevMiddleware = (await vite.createServer({
|
||||||
root,
|
|
||||||
server: { middlewareMode: true },
|
server: { middlewareMode: true },
|
||||||
})).middlewares;
|
})).middlewares;
|
||||||
|
|
||||||
app.use(viteDevMiddleware);
|
app.use(viteDevMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SESSIONS
|
||||||
router.get('/api/session', fetchUser);
|
router.get('/api/session', fetchUser);
|
||||||
router.post('/api/session', login);
|
router.post('/api/session', login);
|
||||||
router.delete('/api/session', logout);
|
router.delete('/api/session', logout);
|
||||||
|
|
||||||
|
// USERS
|
||||||
|
router.post('/api/users', createUser);
|
||||||
|
|
||||||
|
// SHELVES
|
||||||
router.post('/api/shelves', createShelf);
|
router.post('/api/shelves', createShelf);
|
||||||
|
|
||||||
router.post('/api/users', createUser);
|
// POSTS
|
||||||
|
router.post('/api/shelves/:shelfId/posts', createPost);
|
||||||
|
|
||||||
|
// COMMENTS
|
||||||
|
router.post('/api/posts/:postId/comments', addComment);
|
||||||
|
|
||||||
router.get('*', defaultHandler);
|
router.get('*', defaultHandler);
|
||||||
router.use(errorHandler);
|
router.use(errorHandler);
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
const { createShelf } = require('../shelves');
|
import { createShelf } from '../shelves';
|
||||||
|
|
||||||
async function createShelfApi(req) {
|
async function createShelfApi(req) {
|
||||||
console.log('create shelf', req.body);
|
const shelf = await createShelf(req.body, req.user);
|
||||||
|
|
||||||
const shelf = await createShelf(req.body);
|
|
||||||
|
|
||||||
return shelf;
|
return shelf;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
createShelf: createShelfApi,
|
createShelfApi as createShelf,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
const {
|
import { login, createUser } from '../users';
|
||||||
login,
|
|
||||||
createUser,
|
async function setUser(req, res, next) {
|
||||||
} = require('../users');
|
req.user = req.session.user; // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchUserApi(req, res) {
|
async function fetchUserApi(req, res) {
|
||||||
res.send(req.session.user);
|
res.send(req.session.user);
|
||||||
|
@ -25,9 +28,10 @@ async function createUserApi(req, res) {
|
||||||
res.send(user);
|
res.send(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
login: loginApi,
|
setUser,
|
||||||
logout: logoutApi,
|
loginApi as login,
|
||||||
fetchUser: fetchUserApi,
|
logoutApi as logout,
|
||||||
createUser: createUserApi,
|
fetchUserApi as fetchUser,
|
||||||
|
createUserApi as createUser,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue