Compare commits
No commits in common. "f32722ee7c152acdb58e91abdf64b1469dc48814" and "76a831eb5022cb712b2720f1855e3d45ecf48ab4" have entirely different histories.
f32722ee7c
...
76a831eb50
|
@ -2,5 +2,4 @@ node_modules/
|
||||||
dist/
|
dist/
|
||||||
config/*
|
config/*
|
||||||
!config/default.*js
|
!config/default.*js
|
||||||
log/
|
log
|
||||||
media/
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--primary-light-10);
|
border-color: var(--primary-faded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-submit {
|
.button-submit {
|
||||||
background: var(--primary-light-10);
|
background: var(--primary-light-30);
|
||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
:root {
|
:root {
|
||||||
--primary: #f65596;
|
--primary: #f65596;
|
||||||
--primary-light-10: #f075a6;
|
--primary-strong: #f90071;
|
||||||
|
--primary-faded: #ffcce4;
|
||||||
|
|
||||||
--grey-dark-50: #111;
|
--grey-dark-50: #111;
|
||||||
--grey-dark-40: #222;
|
--grey-dark-40: #222;
|
||||||
|
|
|
@ -1,194 +1,216 @@
|
||||||
/* Content */
|
.resize-observer[data-v-b329ee4c] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-observer[data-v-b329ee4c] object {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1
|
||||||
|
}
|
||||||
|
|
||||||
.v-popper__popper {
|
.v-popper__popper {
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper.v-popper__popper--hidden {
|
.v-popper__popper.v-popper__popper--hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity .15s, visibility .15s;
|
transition: opacity .15s, visibility .15s;
|
||||||
pointer-events: none;
|
pointer-events: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper.v-popper__popper--shown {
|
.v-popper__popper.v-popper__popper--shown {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity .15s;
|
transition: opacity .15s
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper.v-popper__popper--skip-transition,
|
.v-popper__popper.v-popper__popper--skip-transition,
|
||||||
.v-popper__popper.v-popper__popper--skip-transition > .v-popper__wrapper {
|
.v-popper__popper.v-popper__popper--skip-transition>.v-popper__wrapper {
|
||||||
transition: none !important;
|
transition: none !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__backdrop {
|
.v-popper__backdrop {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: none;
|
display: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__inner {
|
.v-popper__inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__inner > div {
|
.v-popper__inner>div {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
max-width: inherit;
|
max-width: inherit;
|
||||||
max-height: inherit;
|
max-height: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__arrow-container {
|
.v-popper__arrow-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper--arrow-overflow .v-popper__arrow-container,
|
.v-popper__popper--arrow-overflow .v-popper__arrow-container,
|
||||||
.v-popper__popper--no-positioning .v-popper__arrow-container {
|
.v-popper__popper--no-positioning .v-popper__arrow-container {
|
||||||
display: none;
|
display: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__arrow-inner,
|
.v-popper__arrow-inner,
|
||||||
.v-popper__arrow-outer {
|
.v-popper__arrow-outer {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__arrow-inner {
|
.v-popper__arrow-inner {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
border-width: 7px;
|
border-width: 7px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__arrow-outer {
|
.v-popper__arrow-outer {
|
||||||
border-width: 6px;
|
border-width: 6px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner {
|
||||||
left: -2px;
|
left: -2px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-outer,
|
.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer,
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer {
|
||||||
left: -1px;
|
left: -1px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer {
|
||||||
border-bottom-width: 0;
|
border-bottom-width: 0;
|
||||||
border-left-color: transparent !important;
|
border-left-color: transparent !important;
|
||||||
border-right-color: transparent !important;
|
border-right-color: transparent !important;
|
||||||
border-bottom-color: transparent !important;
|
border-bottom-color: transparent !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner {
|
||||||
top: -2px;
|
top: -2px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-container {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-container {
|
||||||
top: 0;
|
top: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer {
|
||||||
border-top-width: 0;
|
border-top-width: 0;
|
||||||
border-left-color: transparent !important;
|
border-left-color: transparent !important;
|
||||||
border-right-color: transparent !important;
|
border-right-color: transparent !important;
|
||||||
border-top-color: transparent !important;
|
border-top-color: transparent !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner {
|
||||||
top: -4px;
|
top: -4px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="bottom"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer {
|
||||||
top: -6px;
|
top: -6px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner {
|
||||||
top: -2px;
|
top: -2px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-outer,
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer,
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer {
|
||||||
top: -1px;
|
top: -1px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer {
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-left-color: transparent !important;
|
border-left-color: transparent !important;
|
||||||
border-top-color: transparent !important;
|
border-top-color: transparent !important;
|
||||||
border-bottom-color: transparent !important;
|
border-bottom-color: transparent !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner {
|
||||||
left: -4px;
|
left: -4px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="right"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer {
|
||||||
left: -6px;
|
left: -6px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-container {
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-container {
|
||||||
right: -10px;
|
right: -10px
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-inner,
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-outer {
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer {
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
border-top-color: transparent !important;
|
border-top-color: transparent !important;
|
||||||
border-right-color: transparent !important;
|
border-right-color: transparent !important;
|
||||||
border-bottom-color: transparent !important;
|
border-bottom-color: transparent !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper__popper[data-popper-placement^="left"] .v-popper__arrow-inner {
|
.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner {
|
||||||
left: -2px;
|
left: -2px
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltip */
|
|
||||||
|
|
||||||
.v-popper--theme-tooltip .v-popper__inner {
|
|
||||||
background: rgba(0, 0, 0, .8);
|
|
||||||
color: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 7px 12px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-popper--theme-tooltip .v-popper__arrow-outer {
|
|
||||||
border-color: rgba(0, 0, 0, .8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dropdown */
|
|
||||||
|
|
||||||
.v-popper--theme-dropdown .v-popper__inner {
|
.v-popper--theme-dropdown .v-popper__inner {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: black;
|
color: #000;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
box-shadow: 0 6px 30px rgba(0, 0, 0, .1);
|
box-shadow: 0 6px 30px #0000001a
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper--theme-dropdown .v-popper__arrow-inner {
|
.v-popper--theme-dropdown .v-popper__arrow-inner {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
border-color: #fff;
|
border-color: #fff
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-popper--theme-dropdown .v-popper__arrow-outer {
|
.v-popper--theme-dropdown .v-popper__arrow-outer {
|
||||||
border-color: #ddd;
|
border-color: #ddd
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-popper--theme-tooltip .v-popper__inner {
|
||||||
|
background: rgba(0, 0, 0, .8);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 12px 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-popper--theme-tooltip .v-popper__arrow-outer {
|
||||||
|
border-color: #000c
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<ul class="nav-list nolist">
|
<ul class="nav-list nolist">
|
||||||
<!--
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<Link
|
<Link
|
||||||
class="link"
|
class="link"
|
||||||
|
@ -19,7 +18,6 @@
|
||||||
href="/updates"
|
href="/updates"
|
||||||
>Updates</Link>
|
>Updates</Link>
|
||||||
</li>
|
</li>
|
||||||
-->
|
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<Link
|
<Link
|
||||||
|
@ -55,91 +53,36 @@
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="header-section">
|
<form
|
||||||
<form
|
class="search"
|
||||||
class="search"
|
@submit.prevent="search"
|
||||||
:class="{ focused: searchFocused }"
|
>
|
||||||
@submit.prevent="search"
|
<input
|
||||||
|
v-model="query"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search"
|
||||||
|
class="input"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
v-model="query"
|
|
||||||
type="search"
|
|
||||||
placeholder="Search"
|
|
||||||
class="input"
|
|
||||||
@focus="searchFocused = true"
|
|
||||||
@blur="searchFocused = false"
|
|
||||||
>
|
|
||||||
|
|
||||||
<button class="search-button">
|
<Icon icon="search" />
|
||||||
<Icon icon="search" />
|
</form>
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<VDropdown
|
|
||||||
v-if="user"
|
|
||||||
:triggers="['click']"
|
|
||||||
>
|
|
||||||
<div class="userpanel">
|
|
||||||
<img
|
|
||||||
:src="user.avatar"
|
|
||||||
class="avatar"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #popper>
|
|
||||||
<div class="menu">
|
|
||||||
<a
|
|
||||||
:href="`/user/${user.username}`"
|
|
||||||
class="menu-header"
|
|
||||||
>{{ user.username }}</a>
|
|
||||||
|
|
||||||
<ul class="menu-list nolist">
|
|
||||||
<li
|
|
||||||
class="menu-item logout"
|
|
||||||
@click="logout"
|
|
||||||
><Icon icon="exit2" />Log out</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VDropdown>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="userpanel"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
:href="`/login?r=${encodeURIComponent(currentPath)}`"
|
|
||||||
class="login button button-submit"
|
|
||||||
>Log in</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, inject } from 'vue';
|
import { ref, computed, inject } from 'vue';
|
||||||
import navigate from '#/src/navigate.js';
|
import navigate from '#/src/navigate.js';
|
||||||
import { del } from '#/src/api.js';
|
|
||||||
|
|
||||||
import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
|
|
||||||
const user = pageContext.user;
|
|
||||||
const query = ref(pageContext.urlParsed.search.q || '');
|
const query = ref(pageContext.urlParsed.search.q || '');
|
||||||
const searchFocused = ref(false);
|
|
||||||
|
|
||||||
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
|
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
|
||||||
const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`;
|
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
navigate('/search', { q: query.value }, { redirect: true });
|
navigate('/search', { q: query.value }, { redirect: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
del('/session');
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -191,105 +134,27 @@ async function logout() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-section {
|
.search {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
.search {
|
|
||||||
height: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: solid 1px var(--shadow-weak-20);
|
|
||||||
border-radius: 1rem;
|
|
||||||
background: var(--background);
|
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
padding: .5rem 0 .5rem 1rem;
|
height: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
border-radius: 0;
|
||||||
background: none;
|
border-left: solid 1px var(--shadow-weak-30);
|
||||||
}
|
background: var(--background);
|
||||||
|
|
||||||
.search-button {
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
height: 100%;
|
margin-right: .75rem;
|
||||||
padding: 0 .75rem 0 .5rem;
|
|
||||||
fill: var(--shadow-weak-10);
|
fill: var(--shadow-weak-10);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focused {
|
.input:focus + .icon {
|
||||||
border: solid 1px var(--primary-light-10);
|
fill: var(--primary);
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--primary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.userpanel {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 1rem 0 1.5rem;
|
|
||||||
font-size: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover .avatar {
|
|
||||||
box-shadow: 0 0 3px var(--shadow-weak-10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: .25rem;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: .75rem 1rem;
|
|
||||||
border-bottom: solid 1px var(--shadow-weak-30);
|
|
||||||
color: var(--shadow-strong-30);
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: .5rem;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--shadow);
|
|
||||||
margin-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--shadow-weak-30);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout {
|
|
||||||
color: var(--error);
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -8,11 +8,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brillout/json-serializer": "^0.5.8",
|
"@brillout/json-serializer": "^0.5.8",
|
||||||
"@dicebear/collection": "^7.0.5",
|
|
||||||
"@dicebear/core": "^7.0.5",
|
|
||||||
"@floating-ui/dom": "^1.5.3",
|
"@floating-ui/dom": "^1.5.3",
|
||||||
"@floating-ui/vue": "^1.0.2",
|
"@floating-ui/vue": "^1.0.2",
|
||||||
"@resvg/resvg-js": "^2.6.0",
|
|
||||||
"@toycode/markdown-it-class": "^1.2.4",
|
"@toycode/markdown-it-class": "^1.2.4",
|
||||||
"@vitejs/plugin-vue": "^4.5.2",
|
"@vitejs/plugin-vue": "^4.5.2",
|
||||||
"@vue/compiler-sfc": "^3.3.10",
|
"@vue/compiler-sfc": "^3.3.10",
|
||||||
|
@ -21,7 +18,6 @@
|
||||||
"@vueuse/core": "^10.7.1",
|
"@vueuse/core": "^10.7.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"config": "^3.3.9",
|
"config": "^3.3.9",
|
||||||
"connect-redis": "^7.1.1",
|
|
||||||
"convert": "^4.14.1",
|
"convert": "^4.14.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
|
@ -29,8 +25,6 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-promise-router": "^4.1.1",
|
"express-promise-router": "^4.1.1",
|
||||||
"express-query-boolean": "^2.0.0",
|
"express-query-boolean": "^2.0.0",
|
||||||
"express-session": "^1.18.0",
|
|
||||||
"floating-vue": "^5.2.2",
|
|
||||||
"knex": "^3.1.0",
|
"knex": "^3.1.0",
|
||||||
"manticoresearch": "^4.0.0",
|
"manticoresearch": "^4.0.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
|
@ -40,15 +34,13 @@
|
||||||
"path-to-regexp": "^6.2.1",
|
"path-to-regexp": "^6.2.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"redis": "^4.6.12",
|
"redis": "^4.6.12",
|
||||||
"sharp": "^0.32.6",
|
|
||||||
"sirv": "^2.0.3",
|
"sirv": "^2.0.3",
|
||||||
"vike": "^0.4.150",
|
"vike": "^0.4.150",
|
||||||
"vite": "^4.5.1",
|
"vite": "^4.5.1",
|
||||||
"vue": "^3.3.10",
|
"vue": "^3.3.10",
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.8",
|
"vue-virtual-scroller": "^2.0.0-beta.8",
|
||||||
"winston": "^3.11.0",
|
"winston": "^3.11.0",
|
||||||
"winston-daily-rotate-file": "^4.7.1",
|
"winston-daily-rotate-file": "^4.7.1"
|
||||||
"yargs": "^17.7.2"
|
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -65,5 +57,5 @@
|
||||||
"postcss-custom-media": "^10.0.2",
|
"postcss-custom-media": "^10.0.2",
|
||||||
"postcss-nesting": "^12.0.2"
|
"postcss-nesting": "^12.0.2"
|
||||||
},
|
},
|
||||||
"version": "0.8.1"
|
"version": "0.7.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="login-container">
|
|
||||||
<div v-if="user">
|
|
||||||
You are already logged in as {{ user.username }}.
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
:href="`/user/${user.username}`"
|
|
||||||
class="link"
|
|
||||||
>View my profile</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
:href="`/updates`"
|
|
||||||
class="link"
|
|
||||||
>Check out latest porn updates</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
:href="`/actors`"
|
|
||||||
class="link"
|
|
||||||
>Browse the hottest porn stars</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form
|
|
||||||
v-else
|
|
||||||
class="login-panel"
|
|
||||||
@submit.prevent="login"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="errorMsg"
|
|
||||||
class="error"
|
|
||||||
>{{ errorMsg }}</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
v-model="username"
|
|
||||||
placeholder="Username or e-mail"
|
|
||||||
class="input"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="password-container">
|
|
||||||
<input
|
|
||||||
v-model="password"
|
|
||||||
:type="showPassword ? 'input' : 'password'"
|
|
||||||
placeholder="Password"
|
|
||||||
class="password input"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="password-show"
|
|
||||||
@click="showPassword = !showPassword"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
v-show="!showPassword"
|
|
||||||
icon="eye"
|
|
||||||
class="password-show"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
v-show="showPassword"
|
|
||||||
icon="eye-blocked"
|
|
||||||
class="password-show"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="button button-submit">Log in</button>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="/signup"
|
|
||||||
class="link"
|
|
||||||
>Create an account</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, inject } from 'vue';
|
|
||||||
|
|
||||||
import { post } from '#/src/api.js';
|
|
||||||
import navigate from '#/src/navigate.js';
|
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
|
||||||
const user = pageContext.user;
|
|
||||||
|
|
||||||
const username = ref('');
|
|
||||||
const password = ref('');
|
|
||||||
|
|
||||||
const errorMsg = ref(null);
|
|
||||||
const showPassword = ref(false);
|
|
||||||
|
|
||||||
async function login() {
|
|
||||||
errorMsg.value = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await post('/session', {
|
|
||||||
username: username.value,
|
|
||||||
password: password.value,
|
|
||||||
redirect: pageContext.urlParsed.search.r,
|
|
||||||
});
|
|
||||||
|
|
||||||
navigate(decodeURIComponent(pageContext.urlParsed.search.r), null, { redirect: true });
|
|
||||||
} catch (error) {
|
|
||||||
errorMsg.value = error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.login-container {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--background-base-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-panel {
|
|
||||||
width: 20rem;
|
|
||||||
max-width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: .5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 1rem;
|
|
||||||
border-radius: .5rem;
|
|
||||||
box-shadow: 0 0 3px var(--shadow-weak-30);
|
|
||||||
background: var(--background-base);
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
.button {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
margin-top: .5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-container {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-show {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
padding: 0 1rem 0 .5rem;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
background: var(--error);
|
|
||||||
color: var(--text-light);
|
|
||||||
padding: .75rem 1rem;
|
|
||||||
border-radius: .25rem;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
font-size: .9rem;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1 +0,0 @@
|
||||||
export default '/login';
|
|
|
@ -1,43 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="profile">
|
|
||||||
<div class="profile-header">
|
|
||||||
<img
|
|
||||||
v-if="profile.avatar"
|
|
||||||
:src="profile.avatar"
|
|
||||||
class="avatar"
|
|
||||||
>
|
|
||||||
|
|
||||||
<h2 class="username">{{ profile.username }}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { inject } from 'vue';
|
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
|
||||||
const profile = pageContext.pageProps.profile;
|
|
||||||
|
|
||||||
console.log('profile', profile);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.profile-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: .5rem 1rem;
|
|
||||||
color: var(--highlight-strong-30);
|
|
||||||
background: var(--grey-dark-40);
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: .25rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { fetchUser } from '#/src/users.js';
|
|
||||||
|
|
||||||
export async function onBeforeRender(pageContext) {
|
|
||||||
const profile = await fetchUser(pageContext.routeParams.username);
|
|
||||||
|
|
||||||
return {
|
|
||||||
pageContext: {
|
|
||||||
title: profile.username,
|
|
||||||
pageProps: {
|
|
||||||
profile, // differentiate from authed 'user'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export default '/user/@username';
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default {
|
export default {
|
||||||
passToClient: ['pageProps', 'urlPathname', 'routeParams', 'urlParsed', 'env', 'user'],
|
passToClient: ['pageProps', 'urlPathname', 'routeParams', 'urlParsed', 'env'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createSSRApp, h } from 'vue';
|
import { createSSRApp, h } from 'vue';
|
||||||
import VueVirtualScroller from 'vue-virtual-scroller';
|
import VueVirtualScroller from 'vue-virtual-scroller';
|
||||||
import FloatingVue from 'floating-vue';
|
|
||||||
|
|
||||||
import { setPageContext } from './usePageContext.js';
|
import { setPageContext } from './usePageContext.js';
|
||||||
|
|
||||||
|
@ -32,7 +31,6 @@ function createApp(Page, pageProps, pageContext) {
|
||||||
|
|
||||||
app.provide('pageContext', pageContext);
|
app.provide('pageContext', pageContext);
|
||||||
|
|
||||||
app.use(FloatingVue);
|
|
||||||
app.use(VueVirtualScroller);
|
app.use(VueVirtualScroller);
|
||||||
|
|
||||||
app.component('Link', Link);
|
app.component('Link', Link);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { parse } from '@brillout/json-serializer/parse'; /* eslint-disable-line import/extensions */
|
import { parse } from '@brillout/json-serializer/parse';
|
||||||
|
|
||||||
const postHeaders = {
|
const postHeaders = {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
|
@ -26,7 +26,7 @@ export async function get(path, query = {}) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(body.statusMessage);
|
throw new Error(body.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(path, data, { query } = {}) {
|
export async function post(path, data, { query } = {}) {
|
||||||
|
@ -66,7 +66,7 @@ export async function patch(path, data, { query } = {}) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(body.statusMessage);
|
throw new Error(body.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(path, { data, query } = {}) {
|
export async function del(path, { data, query } = {}) {
|
||||||
|
@ -86,5 +86,5 @@ export async function del(path, { data, query } = {}) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(body.statusMessage);
|
throw new Error(body.message);
|
||||||
}
|
}
|
||||||
|
|
11
src/argv.js
11
src/argv.js
|
@ -1,11 +0,0 @@
|
||||||
import yargs from 'yargs';
|
|
||||||
|
|
||||||
const { argv } = yargs()
|
|
||||||
.command('npm start')
|
|
||||||
.option('debug', {
|
|
||||||
describe: 'Show error stack traces',
|
|
||||||
type: 'boolean',
|
|
||||||
default: process.env.NODE_ENV === 'development',
|
|
||||||
});
|
|
||||||
|
|
||||||
export default argv;
|
|
124
src/auth.js
124
src/auth.js
|
@ -1,124 +0,0 @@
|
||||||
import config from 'config';
|
|
||||||
import util from 'util';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import fs from 'fs/promises';
|
|
||||||
import { createAvatar } from '@dicebear/core';
|
|
||||||
import { shapes } from '@dicebear/collection';
|
|
||||||
|
|
||||||
import knex from './knex.js';
|
|
||||||
import { curateUser, fetchUser } from './users.js';
|
|
||||||
import { HttpError } from './errors.js';
|
|
||||||
|
|
||||||
const scrypt = util.promisify(crypto.scrypt);
|
|
||||||
|
|
||||||
async function verifyPassword(password, storedPassword) {
|
|
||||||
const [salt, hash] = storedPassword.split('/');
|
|
||||||
const hashedPassword = (await scrypt(password, salt, 64)).toString('hex');
|
|
||||||
|
|
||||||
if (hashedPassword === hash) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new HttpError('Username or password incorrect', 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateAvatar(user) {
|
|
||||||
const avatar = createAvatar(shapes, {
|
|
||||||
seed: user.username,
|
|
||||||
backgroundColor: ['f65596', '9b004b', '006b68', '5abab6'],
|
|
||||||
shape1Color: ['f65596', 'ff6d7e', 'ff8d69', 'ffb15b', 'ffd55c', 'f9f871'],
|
|
||||||
shape2Color: ['c162c6', '6074dd', '007dd2', '007ba9', '007170'],
|
|
||||||
shape3Color: ['f65596', 'ff6d7e', 'ff8d69', 'ffb15b', 'ffd55c', 'f9f871'],
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.mkdir('media/avatars', { recursive: true });
|
|
||||||
|
|
||||||
await avatar.png().toFile(`media/avatars/${user.id}_${user.username}.png`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function login(credentials) {
|
|
||||||
if (!config.auth.login) {
|
|
||||||
throw new HttpError('Authentication is disabled', 405);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await fetchUser(credentials.username.trim(), true);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new HttpError('Username or password incorrect', 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
await verifyPassword(credentials.password, user.password);
|
|
||||||
|
|
||||||
await knex('users')
|
|
||||||
.update('last_login', 'NOW()')
|
|
||||||
.where('id', user.id);
|
|
||||||
|
|
||||||
if (!user.avatar) {
|
|
||||||
await generateAvatar(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return curateUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signup(credentials) {
|
|
||||||
if (!config.auth.signup) {
|
|
||||||
throw new HttpError('Authentication is disabled', 405);
|
|
||||||
}
|
|
||||||
|
|
||||||
const curatedUsername = credentials.username.trim();
|
|
||||||
|
|
||||||
if (!curatedUsername) {
|
|
||||||
throw new HttpError('Username required', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curatedUsername.length < config.auth.usernameLength[0]) {
|
|
||||||
throw new HttpError('Username is too short', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curatedUsername.length > config.auth.usernameLength[1]) {
|
|
||||||
throw new HttpError('Username is too long', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.auth.usernamePattern.test(curatedUsername)) {
|
|
||||||
throw new HttpError('Username contains invalid characters', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!credentials.email) {
|
|
||||||
throw new HttpError('E-mail required', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credentials.password?.length < 3) {
|
|
||||||
throw new HttpError('Password must be 3 characters or longer', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingUser = await knex('users')
|
|
||||||
.where('username', curatedUsername)
|
|
||||||
.orWhere('email', credentials.email)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
throw new HttpError('Username or e-mail already in use', 409);
|
|
||||||
}
|
|
||||||
|
|
||||||
const salt = crypto.randomBytes(16).toString('hex');
|
|
||||||
const hashedPassword = (await scrypt(credentials.password, salt, 64)).toString('hex');
|
|
||||||
const storedPassword = `${salt}/${hashedPassword}`;
|
|
||||||
|
|
||||||
const [{ id: userId }] = await knex('users')
|
|
||||||
.insert({
|
|
||||||
username: curatedUsername,
|
|
||||||
email: credentials.email,
|
|
||||||
password: storedPassword,
|
|
||||||
})
|
|
||||||
.returning('id');
|
|
||||||
|
|
||||||
await knex('stashes').insert({
|
|
||||||
user_id: userId,
|
|
||||||
name: 'Favorites',
|
|
||||||
slug: 'favorites',
|
|
||||||
public: false,
|
|
||||||
primary: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchUser(userId);
|
|
||||||
}
|
|
50
src/users.js
50
src/users.js
|
@ -1,50 +0,0 @@
|
||||||
import knex from './knex.js';
|
|
||||||
// import { curateStash } from './stashes.js';
|
|
||||||
|
|
||||||
export function curateUser(user) {
|
|
||||||
if (!user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ability = [...(user.role_abilities || []), ...(user.abilities || [])];
|
|
||||||
|
|
||||||
const curatedUser = {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
emailVerified: user.email_verified,
|
|
||||||
identityVerified: user.identity_verified,
|
|
||||||
ability,
|
|
||||||
avatar: `/media/avatars/${user.id}_${user.username}.png`,
|
|
||||||
createdAt: user.created_at,
|
|
||||||
// stashes: user.stashes?.filter(Boolean).map((stash) => curateStash(stash)) || [],
|
|
||||||
};
|
|
||||||
|
|
||||||
return curatedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchUser(userId, raw) {
|
|
||||||
const user = await knex('users')
|
|
||||||
.select(knex.raw('users.*, users_roles.abilities as role_abilities, COALESCE(json_agg(stashes ORDER BY stashes.created_at) FILTER (WHERE stashes.id IS NOT NULL), \'[]\') as stashes'))
|
|
||||||
.modify((builder) => {
|
|
||||||
if (typeof userId === 'number') {
|
|
||||||
builder.where('users.id', userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof userId === 'string') {
|
|
||||||
builder
|
|
||||||
.where('users.username', userId)
|
|
||||||
.orWhere('users.email', userId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.leftJoin('users_roles', 'users_roles.role', 'users.role')
|
|
||||||
.leftJoin('stashes', 'stashes.user_id', 'users.id')
|
|
||||||
.groupBy('users.id', 'users_roles.role')
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (raw) {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return curateUser(user);
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
import { login, signup } from '../auth.js';
|
|
||||||
import { fetchUser } from '../users.js';
|
|
||||||
|
|
||||||
export async function setUserApi(req, res, next) {
|
|
||||||
if (req.session.user) {
|
|
||||||
req.user = req.session.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loginApi(req, res) {
|
|
||||||
console.log('login!', req.body);
|
|
||||||
|
|
||||||
const user = await login(req.body);
|
|
||||||
|
|
||||||
req.session.user = user;
|
|
||||||
res.send(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function logoutApi(req, res) {
|
|
||||||
req.session.destroy((error) => {
|
|
||||||
if (error) {
|
|
||||||
res.status(500).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(204).send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchMeApi(req, res) {
|
|
||||||
if (req.session.user) {
|
|
||||||
req.session.user = await fetchUser(req.session.user.id, false, req.session.user);
|
|
||||||
|
|
||||||
res.send(req.session.user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(401).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signupApi(req, res) {
|
|
||||||
const user = await signup(req.body);
|
|
||||||
|
|
||||||
req.session.user = user;
|
|
||||||
res.send(user);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
|
@ -1,26 +0,0 @@
|
||||||
import argv from '../argv.js';
|
|
||||||
import initLogger from '../logger.js';
|
|
||||||
|
|
||||||
const logger = initLogger();
|
|
||||||
|
|
||||||
export default function errorHandler(error, req, res, _next) {
|
|
||||||
logger.warn(`Failed to fulfill request to ${req.path} (${error.httpCode || 500}): ${error.message}`);
|
|
||||||
|
|
||||||
if (argv.debug) {
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.httpCode) {
|
|
||||||
res.status(error.httpCode).send({
|
|
||||||
statusCode: error.httpCode,
|
|
||||||
statusMessage: error.message,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(500).send({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: 'Oops... our server messed up. We will be investigating this incident, our apologies for the inconvenience.',
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -15,27 +15,15 @@ import config from 'config';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import boolParser from 'express-query-boolean';
|
import boolParser from 'express-query-boolean';
|
||||||
import Router from 'express-promise-router';
|
import Router from 'express-promise-router';
|
||||||
import session from 'express-session';
|
|
||||||
import RedisStore from 'connect-redis';
|
|
||||||
import compression from 'compression';
|
import compression from 'compression';
|
||||||
import { renderPage } from 'vike/server'; // eslint-disable-line import/extensions
|
import { renderPage } from 'vike/server'; // eslint-disable-line import/extensions
|
||||||
|
|
||||||
// import root from './root.js';
|
// import root from './root.js';
|
||||||
|
|
||||||
import redis from '../redis.js';
|
|
||||||
|
|
||||||
import errorHandler from './error.js';
|
|
||||||
|
|
||||||
import { fetchScenesApi } from './scenes.js';
|
import { fetchScenesApi } from './scenes.js';
|
||||||
import { fetchActorsApi } from './actors.js';
|
import { fetchActorsApi } from './actors.js';
|
||||||
import { fetchMoviesApi } from './movies.js';
|
import { fetchMoviesApi } from './movies.js';
|
||||||
|
|
||||||
import {
|
|
||||||
setUserApi,
|
|
||||||
loginApi,
|
|
||||||
logoutApi,
|
|
||||||
} from './auth.js';
|
|
||||||
|
|
||||||
import initLogger from '../logger.js';
|
import initLogger from '../logger.js';
|
||||||
|
|
||||||
const logger = initLogger();
|
const logger = initLogger();
|
||||||
|
@ -54,20 +42,6 @@ export default async function initServer() {
|
||||||
router.use('/', express.static('static'));
|
router.use('/', express.static('static'));
|
||||||
router.use('/media', express.static(config.media.path));
|
router.use('/media', express.static(config.media.path));
|
||||||
|
|
||||||
router.use(express.json());
|
|
||||||
|
|
||||||
const redisStore = new RedisStore({
|
|
||||||
client: redis,
|
|
||||||
prefix: 'traxxx:session:',
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use(session({
|
|
||||||
...config.web.session,
|
|
||||||
store: redisStore,
|
|
||||||
}));
|
|
||||||
|
|
||||||
router.use(setUserApi);
|
|
||||||
|
|
||||||
// Vite integration
|
// Vite integration
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
// In production, we need to serve our static assets ourselves.
|
// In production, we need to serve our static assets ourselves.
|
||||||
|
@ -95,9 +69,6 @@ export default async function initServer() {
|
||||||
|
|
||||||
router.get('/api/movies', fetchMoviesApi);
|
router.get('/api/movies', fetchMoviesApi);
|
||||||
|
|
||||||
router.post('/api/session', loginApi);
|
|
||||||
router.delete('/api/session', logoutApi);
|
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
// Other middlewares (e.g. some RPC middleware such as Telefunc)
|
// Other middlewares (e.g. some RPC middleware such as Telefunc)
|
||||||
// ...
|
// ...
|
||||||
|
@ -108,7 +79,6 @@ export default async function initServer() {
|
||||||
const pageContextInit = {
|
const pageContextInit = {
|
||||||
urlOriginal: req.originalUrl,
|
urlOriginal: req.originalUrl,
|
||||||
urlQuery: req.query, // vike's own query does not apply boolean parser
|
urlQuery: req.query, // vike's own query does not apply boolean parser
|
||||||
user: req.user,
|
|
||||||
env: {
|
env: {
|
||||||
maxAggregateSize: config.database.manticore.maxAggregateSize,
|
maxAggregateSize: config.database.manticore.maxAggregateSize,
|
||||||
},
|
},
|
||||||
|
@ -140,7 +110,6 @@ export default async function initServer() {
|
||||||
res.send(body);
|
res.send(body);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use(errorHandler);
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
const port = process.env.PORT || config.web.port || 3000;
|
const port = process.env.PORT || config.web.port || 3000;
|
||||||
|
|
Loading…
Reference in New Issue