Added functional stash button on scene tiles.
This commit is contained in:
parent
082d4fc154
commit
f56e22230b
|
@ -78,6 +78,7 @@
|
||||||
<VDropdown
|
<VDropdown
|
||||||
v-if="user"
|
v-if="user"
|
||||||
:triggers="['click']"
|
:triggers="['click']"
|
||||||
|
:prevent-overflow="true"
|
||||||
>
|
>
|
||||||
<div class="userpanel">
|
<div class="userpanel">
|
||||||
<img
|
<img
|
||||||
|
@ -90,12 +91,22 @@
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a
|
<a
|
||||||
:href="`/user/${user.username}`"
|
:href="`/user/${user.username}`"
|
||||||
class="menu-header"
|
class="menu-header ellipsis"
|
||||||
>{{ user.username }}</a>
|
>{{ user.username }}</a>
|
||||||
|
|
||||||
<ul class="menu-list nolist">
|
<ul class="menu-list nolist">
|
||||||
|
<li class="menu-item">
|
||||||
|
<a
|
||||||
|
:href="`/user/${user.username}`"
|
||||||
|
class="menu-button nolink"
|
||||||
|
>
|
||||||
|
<Icon icon="vcard" />
|
||||||
|
View profile
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="menu-item logout"
|
class="menu-button menu-item logout"
|
||||||
@click="logout"
|
@click="logout"
|
||||||
><Icon icon="exit2" />Log out</li>
|
><Icon icon="exit2" />Log out</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -201,9 +212,9 @@ async function logout() {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: solid 1px var(--shadow-weak-20);
|
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background: var(--background);
|
background: var(--background-dark-10);
|
||||||
|
box-shadow: inset 0 0 3px var(--shadow-weak-40);
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
padding: .5rem 0 .5rem 1rem;
|
padding: .5rem 0 .5rem 1rem;
|
||||||
|
@ -225,7 +236,10 @@ async function logout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
|
/*
|
||||||
border: solid 1px var(--primary-light-10);
|
border: solid 1px var(--primary-light-10);
|
||||||
|
*/
|
||||||
|
box-shadow: inset 0 0 3px var(--shadow-weak-30);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: var(--primary);
|
fill: var(--primary);
|
||||||
|
@ -258,25 +272,32 @@ async function logout() {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-header {
|
.menu-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
padding: .75rem 1rem;
|
padding: .75rem 1rem;
|
||||||
border-bottom: solid 1px var(--shadow-weak-30);
|
border-bottom: solid 1px var(--shadow-weak-30);
|
||||||
color: var(--shadow-strong-30);
|
color: var(--shadow-strong-30);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem;
|
padding: .5rem .5rem .5rem .75rem;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: var(--shadow);
|
fill: var(--shadow);
|
||||||
margin-right: .5rem;
|
margin-right: .75rem;
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -286,8 +307,6 @@ async function logout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout {
|
.logout {
|
||||||
color: var(--error);
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: var(--error);
|
fill: var(--error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tile">
|
<div class="tile">
|
||||||
|
<div class="poster-container">
|
||||||
<Link
|
<Link
|
||||||
:href="`/scene/${scene.id}/${scene.slug}`"
|
:href="`/scene/${scene.id}/${scene.slug}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -14,6 +15,21 @@
|
||||||
>
|
>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-show="favorited"
|
||||||
|
icon="heart7"
|
||||||
|
class="heart favorited"
|
||||||
|
@click.native.stop="unstash"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-show="!favorited"
|
||||||
|
icon="heart8"
|
||||||
|
class="heart"
|
||||||
|
@click.native.stop="stash"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<div class="channel">
|
<div class="channel">
|
||||||
<Link
|
<Link
|
||||||
|
@ -80,14 +96,42 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
defineProps({
|
import { post, del } from '#/src/api.js';
|
||||||
|
|
||||||
|
import Icon from '../icon/icon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
scene: {
|
scene: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageContext = inject('pageContext');
|
||||||
|
const user = pageContext.user;
|
||||||
|
|
||||||
|
const favorited = ref(props.scene.stashes.some((sceneStash) => sceneStash.primary));
|
||||||
|
|
||||||
|
async function stash() {
|
||||||
|
try {
|
||||||
|
favorited.value = true;
|
||||||
|
await post(`/stashes/${user.primaryStash.id}/scenes`, { sceneId: props.scene.id });
|
||||||
|
} catch (error) {
|
||||||
|
favorited.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unstash() {
|
||||||
|
try {
|
||||||
|
favorited.value = false;
|
||||||
|
await del(`/stashes/${user.primaryStash.id}/scenes/${props.scene.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
favorited.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -100,8 +144,16 @@ defineProps({
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 0 3px var(--shadow-weak-20);
|
box-shadow: 0 0 3px var(--shadow-weak-20);
|
||||||
|
|
||||||
|
.heart {
|
||||||
|
fill: var(--text-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -118,6 +170,26 @@ defineProps({
|
||||||
background-position: center;
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon.heart {
|
||||||
|
width: 2rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: .5rem .5rem 1rem 1rem;
|
||||||
|
fill: var(--highlight-strong-10);
|
||||||
|
filter: drop-shadow(0 0 3px var(--shadow));
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.favorited {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -7,7 +7,7 @@ export async function onBeforeRender(pageContext) {
|
||||||
page: Number(pageContext.routeParams.page) || 1,
|
page: Number(pageContext.routeParams.page) || 1,
|
||||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||||
aggregate: false,
|
aggregate: false,
|
||||||
});
|
}, pageContext.user);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageContext: {
|
pageContext: {
|
||||||
|
|
|
@ -9,6 +9,34 @@
|
||||||
|
|
||||||
<h2 class="username">{{ profile.username }}</h2>
|
<h2 class="username">{{ profile.username }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ul class="stashes nolist">
|
||||||
|
<li
|
||||||
|
v-for="stash in profile.stashes"
|
||||||
|
:key="`stash-${stash.id}`"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="`/stash/${profile.username}/${stash.slug}`"
|
||||||
|
class="stash nolink"
|
||||||
|
>
|
||||||
|
<div class="stash-name">
|
||||||
|
{{ stash.name }}
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-if="stash.primary"
|
||||||
|
icon="heart7"
|
||||||
|
class="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stash-counts">
|
||||||
|
<div class="stash-count"><Icon icon="clapboard-play" />{{ abbreviateNumber(stash.stashedScenes) }}</div>
|
||||||
|
<div class="stash-count"><Icon icon="movie" />{{ abbreviateNumber(stash.stashedMovies) }}</div>
|
||||||
|
<div class="stash-count"><Icon icon="star" />{{ abbreviateNumber(stash.stashedActors) }}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,6 +45,12 @@ import { inject } from 'vue';
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
const profile = pageContext.pageProps.profile;
|
const profile = pageContext.pageProps.profile;
|
||||||
|
|
||||||
|
function abbreviateNumber(number) {
|
||||||
|
return number.toLocaleString('en-US', { notation: 'compact' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(profile.stashes);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -38,4 +72,49 @@ const profile = pageContext.pageProps.profile;
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stashes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background);
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-name {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: .5rem;
|
||||||
|
border-bottom: solid 1px var(--shadow-weak-30);
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.icon.primary {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-counts {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: .5rem;
|
||||||
|
font-size: .9rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: .5rem;
|
||||||
|
fill: var(--shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import fs from 'fs/promises';
|
||||||
import { createAvatar } from '@dicebear/core';
|
import { createAvatar } from '@dicebear/core';
|
||||||
import { shapes } from '@dicebear/collection';
|
import { shapes } from '@dicebear/collection';
|
||||||
|
|
||||||
import knex from './knex.js';
|
import { knexOwner as knex } from './knex.js';
|
||||||
import { curateUser, fetchUser } from './users.js';
|
import { curateUser, fetchUser } from './users.js';
|
||||||
import { HttpError } from './errors.js';
|
import { HttpError } from './errors.js';
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ async function generateAvatar(user) {
|
||||||
|
|
||||||
export async function login(credentials) {
|
export async function login(credentials) {
|
||||||
if (!config.auth.login) {
|
if (!config.auth.login) {
|
||||||
throw new HttpError('Authentication is disabled', 405);
|
throw new HttpError('Logins are currently disabled', 405);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await fetchUser(credentials.username.trim(), {
|
const user = await fetchUser(credentials.username.trim(), {
|
||||||
|
@ -46,6 +46,8 @@ export async function login(credentials) {
|
||||||
raw: true,
|
raw: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('login user', user);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpError('Username or password incorrect', 401);
|
throw new HttpError('Username or password incorrect', 401);
|
||||||
}
|
}
|
||||||
|
@ -60,12 +62,13 @@ export async function login(credentials) {
|
||||||
await generateAvatar(user);
|
await generateAvatar(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetched the raw user for password verification, don't return directly to user
|
||||||
return curateUser(user);
|
return curateUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signup(credentials) {
|
export async function signup(credentials) {
|
||||||
if (!config.auth.signup) {
|
if (!config.auth.signup) {
|
||||||
throw new HttpError('Authentication is disabled', 405);
|
throw new HttpError('Sign-ups are currently disabled', 405);
|
||||||
}
|
}
|
||||||
|
|
||||||
const curatedUsername = credentials.username.trim();
|
const curatedUsername = credentials.username.trim();
|
||||||
|
|
13
src/knex.js
13
src/knex.js
|
@ -1,7 +1,16 @@
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
import knex from 'knex';
|
import knex from 'knex';
|
||||||
|
|
||||||
export default knex({
|
export const knexQuery = knex({
|
||||||
|
client: 'pg',
|
||||||
|
connection: config.database.query,
|
||||||
|
pool: config.database.pool,
|
||||||
|
// performance overhead, don't use asyncStackTraces in production
|
||||||
|
asyncStackTraces: process.env.NODE_ENV === 'development',
|
||||||
|
// debug: process.env.NODE_ENV === 'development',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const knexOwner = knex({
|
||||||
client: 'pg',
|
client: 'pg',
|
||||||
connection: config.database.owner,
|
connection: config.database.owner,
|
||||||
pool: config.database.pool,
|
pool: config.database.pool,
|
||||||
|
@ -9,3 +18,5 @@ export default knex({
|
||||||
asyncStackTraces: process.env.NODE_ENV === 'development',
|
asyncStackTraces: process.env.NODE_ENV === 'development',
|
||||||
// debug: process.env.NODE_ENV === 'development',
|
// debug: process.env.NODE_ENV === 'development',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default knexQuery;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
|
|
||||||
import knex from './knex.js';
|
import { knexOwner as knex } from './knex.js';
|
||||||
import { searchApi } from './manticore.js';
|
import { searchApi } from './manticore.js';
|
||||||
import { HttpError } from './errors.js';
|
import { HttpError } from './errors.js';
|
||||||
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
|
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
|
||||||
import { fetchTagsById } from './tags.js';
|
import { fetchTagsById } from './tags.js';
|
||||||
import { fetchEntitiesById } from './entities.js';
|
import { fetchEntitiesById } from './entities.js';
|
||||||
|
import { curateStash } from './stashes.js';
|
||||||
|
|
||||||
function curateMedia(media) {
|
function curateMedia(media) {
|
||||||
if (!media) {
|
if (!media) {
|
||||||
|
@ -66,14 +67,15 @@ function curateScene(rawScene, assets) {
|
||||||
})),
|
})),
|
||||||
poster: curateMedia(assets.poster),
|
poster: curateMedia(assets.poster),
|
||||||
photos: assets.photos.map((photo) => curateMedia(photo)),
|
photos: assets.photos.map((photo) => curateMedia(photo)),
|
||||||
|
stashes: assets.stashes?.map((stash) => curateStash(stash)) || [],
|
||||||
createdBatchId: rawScene.created_batch_id,
|
createdBatchId: rawScene.created_batch_id,
|
||||||
updatedBatchId: rawScene.updated_batch_id,
|
updatedBatchId: rawScene.updated_batch_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchScenesById(sceneIds) {
|
export async function fetchScenesById(sceneIds, reqUser) {
|
||||||
const [scenes, channels, actors, directors, tags, posters, photos] = await Promise.all([
|
const [scenes, channels, actors, directors, tags, posters, photos, stashes] = await Promise.all([
|
||||||
knex('releases').whereIn('id', sceneIds),
|
knex('releases').whereIn('releases.id', sceneIds),
|
||||||
knex('releases')
|
knex('releases')
|
||||||
.select('channels.*', 'networks.id as network_id', 'networks.slug as network_slug', 'networks.name as network_name', 'networks.type as network_type')
|
.select('channels.*', 'networks.id as network_id', 'networks.slug as network_slug', 'networks.name as network_name', 'networks.type as network_type')
|
||||||
.whereIn('releases.id', sceneIds)
|
.whereIn('releases.id', sceneIds)
|
||||||
|
@ -92,9 +94,9 @@ export async function fetchScenesById(sceneIds) {
|
||||||
knex.raw('COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name'),
|
knex.raw('COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name'),
|
||||||
*/
|
*/
|
||||||
)
|
)
|
||||||
.whereIn('release_id', sceneIds)
|
|
||||||
.leftJoin('actors', 'actors.id', 'releases_actors.actor_id')
|
.leftJoin('actors', 'actors.id', 'releases_actors.actor_id')
|
||||||
.leftJoin('actors_meta', 'actors_meta.actor_id', 'actors.id'),
|
.leftJoin('actors_meta', 'actors_meta.actor_id', 'actors.id')
|
||||||
|
.whereIn('release_id', sceneIds),
|
||||||
/*
|
/*
|
||||||
.leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors_meta.birth_country_alpha2')
|
.leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors_meta.birth_country_alpha2')
|
||||||
.leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors_meta.residence_country_alpha2'),
|
.leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors_meta.residence_country_alpha2'),
|
||||||
|
@ -104,9 +106,9 @@ export async function fetchScenesById(sceneIds) {
|
||||||
.leftJoin('actors as directors', 'directors.id', 'releases_directors.director_id'),
|
.leftJoin('actors as directors', 'directors.id', 'releases_directors.director_id'),
|
||||||
knex('releases_tags')
|
knex('releases_tags')
|
||||||
.select('id', 'slug', 'name', 'release_id')
|
.select('id', 'slug', 'name', 'release_id')
|
||||||
|
.leftJoin('tags', 'tags.id', 'releases_tags.tag_id')
|
||||||
.whereNotNull('tags.id')
|
.whereNotNull('tags.id')
|
||||||
.whereIn('release_id', sceneIds)
|
.whereIn('release_id', sceneIds)
|
||||||
.leftJoin('tags', 'tags.id', 'releases_tags.tag_id')
|
|
||||||
.orderBy('priority', 'desc'),
|
.orderBy('priority', 'desc'),
|
||||||
knex('releases_posters')
|
knex('releases_posters')
|
||||||
.whereIn('release_id', sceneIds)
|
.whereIn('release_id', sceneIds)
|
||||||
|
@ -114,6 +116,12 @@ export async function fetchScenesById(sceneIds) {
|
||||||
knex('releases_photos')
|
knex('releases_photos')
|
||||||
.whereIn('release_id', sceneIds)
|
.whereIn('release_id', sceneIds)
|
||||||
.leftJoin('media', 'media.id', 'releases_photos.media_id'),
|
.leftJoin('media', 'media.id', 'releases_photos.media_id'),
|
||||||
|
reqUser
|
||||||
|
? knex('stashes_scenes')
|
||||||
|
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
|
||||||
|
.where('stashes.user_id', reqUser.id)
|
||||||
|
.whereIn('stashes_scenes.scene_id', sceneIds)
|
||||||
|
: [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return sceneIds.map((sceneId) => {
|
return sceneIds.map((sceneId) => {
|
||||||
|
@ -129,6 +137,7 @@ export async function fetchScenesById(sceneIds) {
|
||||||
const sceneTags = tags.filter((tag) => tag.release_id === sceneId);
|
const sceneTags = tags.filter((tag) => tag.release_id === sceneId);
|
||||||
const scenePoster = posters.find((poster) => poster.release_id === sceneId);
|
const scenePoster = posters.find((poster) => poster.release_id === sceneId);
|
||||||
const scenePhotos = photos.filter((photo) => photo.release_id === sceneId);
|
const scenePhotos = photos.filter((photo) => photo.release_id === sceneId);
|
||||||
|
const sceneStashes = stashes.filter((stash) => stash.scene_id === sceneId);
|
||||||
|
|
||||||
return curateScene(scene, {
|
return curateScene(scene, {
|
||||||
channel: sceneChannel,
|
channel: sceneChannel,
|
||||||
|
@ -137,6 +146,7 @@ export async function fetchScenesById(sceneIds) {
|
||||||
tags: sceneTags,
|
tags: sceneTags,
|
||||||
poster: scenePoster,
|
poster: scenePoster,
|
||||||
photos: scenePhotos,
|
photos: scenePhotos,
|
||||||
|
stashes: sceneStashes,
|
||||||
});
|
});
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
@ -294,7 +304,7 @@ function countAggregations(buckets) {
|
||||||
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchScenes(filters, rawOptions) {
|
export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||||
const options = curateOptions(rawOptions);
|
const options = curateOptions(rawOptions);
|
||||||
const { query, sort } = buildQuery(filters);
|
const { query, sort } = buildQuery(filters);
|
||||||
|
|
||||||
|
@ -302,6 +312,8 @@ export async function fetchScenes(filters, rawOptions) {
|
||||||
console.log('options', options);
|
console.log('options', options);
|
||||||
console.log('query', query.bool.must);
|
console.log('query', query.bool.must);
|
||||||
|
|
||||||
|
console.log('request user', reqUser);
|
||||||
|
|
||||||
console.time('manticore');
|
console.time('manticore');
|
||||||
|
|
||||||
const result = await searchApi.search({
|
const result = await searchApi.search({
|
||||||
|
@ -329,8 +341,6 @@ export async function fetchScenes(filters, rawOptions) {
|
||||||
|
|
||||||
console.timeEnd('manticore');
|
console.timeEnd('manticore');
|
||||||
|
|
||||||
console.log('hits', result.hits.hits.length);
|
|
||||||
|
|
||||||
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets);
|
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets);
|
||||||
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds?.buckets);
|
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds?.buckets);
|
||||||
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds?.buckets);
|
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds?.buckets);
|
||||||
|
@ -346,7 +356,7 @@ export async function fetchScenes(filters, rawOptions) {
|
||||||
console.timeEnd('fetch aggregations');
|
console.timeEnd('fetch aggregations');
|
||||||
|
|
||||||
const sceneIds = result.hits.hits.map((hit) => Number(hit._id));
|
const sceneIds = result.hits.hits.map((hit) => Number(hit._id));
|
||||||
const scenes = await fetchScenesById(sceneIds);
|
const scenes = await fetchScenesById(sceneIds, reqUser);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scenes,
|
scenes,
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
import config from 'config';
|
||||||
|
|
||||||
|
import { knexOwner as knex } from './knex.js';
|
||||||
|
import { HttpError } from './errors.js';
|
||||||
|
import slugify from './utils/slugify.js';
|
||||||
|
import initLogger from './logger.js';
|
||||||
|
|
||||||
|
const logger = initLogger();
|
||||||
|
|
||||||
|
let lastActorsViewRefresh = 0;
|
||||||
|
|
||||||
|
export function curateStash(stash) {
|
||||||
|
if (!stash) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const curatedStash = {
|
||||||
|
id: stash.id,
|
||||||
|
name: stash.name,
|
||||||
|
slug: stash.slug,
|
||||||
|
primary: stash.primary,
|
||||||
|
public: stash.public,
|
||||||
|
createdAt: stash.created_at,
|
||||||
|
stashedScenes: stash.stashed_scenes || null,
|
||||||
|
stashedMovies: stash.stashed_movies || null,
|
||||||
|
stashedActors: stash.stashed_actors || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return curatedStash;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateStashEntry(stash, user) {
|
||||||
|
const curatedStashEntry = {
|
||||||
|
user_id: user.id,
|
||||||
|
name: stash.name,
|
||||||
|
slug: slugify(stash.name),
|
||||||
|
public: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return curatedStashEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStash(stashId, sessionUser) {
|
||||||
|
if (!sessionUser) {
|
||||||
|
throw new HttpError('You are not authenthicated', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stash = await knex('stashes')
|
||||||
|
.where({
|
||||||
|
id: stashId,
|
||||||
|
user_id: sessionUser.id,
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!stash) {
|
||||||
|
throw new HttpError('You are not authorized to access this stash', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curateStash(stash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStashes(domain, itemId, sessionUser) {
|
||||||
|
const stashes = await knex(`stashes_${domain}s`)
|
||||||
|
.select('stashes.*')
|
||||||
|
.where({
|
||||||
|
[`${domain}_id`]: itemId,
|
||||||
|
user_id: sessionUser.id,
|
||||||
|
})
|
||||||
|
.leftJoin('stashes', 'stashes.id', `stashes_${domain}s.stash_id`);
|
||||||
|
|
||||||
|
return stashes.map((stash) => curateStash(stash));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStash(newStash, sessionUser) {
|
||||||
|
if (!sessionUser) {
|
||||||
|
throw new HttpError('You are not authenthicated', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stash = await knex('stashes')
|
||||||
|
.insert(curateStashEntry(newStash, sessionUser))
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
|
return curateStash(stash);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.routine === '_bt_check_unique') {
|
||||||
|
throw new HttpError('Stash name should be unique', 409);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateStash(stashId, newStash, sessionUser) {
|
||||||
|
if (!sessionUser) {
|
||||||
|
throw new HttpError('You are not authenthicated', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stash = await knex('stashes')
|
||||||
|
.where({
|
||||||
|
id: stashId,
|
||||||
|
user_id: sessionUser.id,
|
||||||
|
})
|
||||||
|
.update(newStash)
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
|
if (!stash) {
|
||||||
|
throw new HttpError('You are not authorized to modify this stash', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curateStash(stash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeStash(stashId, sessionUser) {
|
||||||
|
if (!sessionUser) {
|
||||||
|
throw new HttpError('You are not authenthicated', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const removed = await knex('stashes')
|
||||||
|
.where({
|
||||||
|
id: stashId,
|
||||||
|
user_id: sessionUser.id,
|
||||||
|
primary: false,
|
||||||
|
})
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
if (removed === 0) {
|
||||||
|
throw new HttpError('Unable to remove this stash', 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshActorsView() {
|
||||||
|
if (new Date() - lastActorsViewRefresh > config.stashes.viewRefreshCooldown * 60000) {
|
||||||
|
// don't refresh actors view more than once an hour
|
||||||
|
lastActorsViewRefresh = new Date();
|
||||||
|
|
||||||
|
logger.debug('Refreshing actors view');
|
||||||
|
|
||||||
|
return knex.schema.refreshMaterializedView('actors_meta');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.silly('Skipping actors view refresh');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashActor(actorId, stashId, sessionUser) {
|
||||||
|
const stash = await fetchStash(stashId, sessionUser);
|
||||||
|
|
||||||
|
await knex('stashes_actors')
|
||||||
|
.insert({
|
||||||
|
stash_id: stash.id,
|
||||||
|
actor_id: actorId,
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshActorsView();
|
||||||
|
|
||||||
|
return fetchStashes('actor', actorId, sessionUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashScene(sceneId, stashId, sessionUser) {
|
||||||
|
const stash = await fetchStash(stashId, sessionUser);
|
||||||
|
|
||||||
|
await knex('stashes_scenes')
|
||||||
|
.insert({
|
||||||
|
stash_id: stash.id,
|
||||||
|
scene_id: sceneId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetchStashes('scene', sceneId, sessionUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashMovie(movieId, stashId, sessionUser) {
|
||||||
|
const stash = await fetchStash(stashId, sessionUser);
|
||||||
|
|
||||||
|
await knex('stashes_movies')
|
||||||
|
.insert({
|
||||||
|
stash_id: stash.id,
|
||||||
|
movie_id: movieId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetchStashes('movie', movieId, sessionUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashActor(actorId, stashId, sessionUser) {
|
||||||
|
await knex
|
||||||
|
.from('stashes_actors AS deletable')
|
||||||
|
.where('deletable.actor_id', actorId)
|
||||||
|
.where('deletable.stash_id', stashId)
|
||||||
|
.whereExists(knex('stashes_actors') // verify user owns this stash, complimentary to row-level security
|
||||||
|
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
|
||||||
|
.where('stashes_actors.stash_id', knex.raw('deletable.stash_id'))
|
||||||
|
.where('stashes.user_id', sessionUser.id))
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
refreshActorsView();
|
||||||
|
|
||||||
|
return fetchStashes('actor', actorId, sessionUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashScene(sceneId, stashId, sessionUser) {
|
||||||
|
await knex
|
||||||
|
.from('stashes_scenes AS deletable')
|
||||||
|
.where('deletable.scene_id', sceneId)
|
||||||
|
.where('deletable.stash_id', stashId)
|
||||||
|
.whereExists(knex('stashes_scenes') // verify user owns this stash, complimentary to row-level security
|
||||||
|
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
|
||||||
|
.where('stashes_scenes.stash_id', knex.raw('deletable.stash_id'))
|
||||||
|
.where('stashes.user_id', sessionUser.id))
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
return fetchStashes('scene', sceneId, sessionUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashMovie(movieId, stashId, sessionUser) {
|
||||||
|
await knex
|
||||||
|
.from('stashes_movies AS deletable')
|
||||||
|
.where('deletable.movie_id', movieId)
|
||||||
|
.where('deletable.stash_id', stashId)
|
||||||
|
.whereExists(knex('stashes_movies') // verify user owns this stash, complimentary to row-level security
|
||||||
|
.leftJoin('stashes', 'stashes.id', 'stashes_movies.stash_id')
|
||||||
|
.where('stashes_movies.stash_id', knex.raw('deletable.stash_id'))
|
||||||
|
.where('stashes.user_id', sessionUser.id))
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
return fetchStashes('movie', movieId, sessionUser);
|
||||||
|
}
|
36
src/users.js
36
src/users.js
|
@ -1,12 +1,13 @@
|
||||||
import knex from './knex.js';
|
import { knexOwner as knex } from './knex.js';
|
||||||
// import { curateStash } from './stashes.js';
|
import { curateStash } from './stashes.js';
|
||||||
|
import { HttpError } from './errors.js';
|
||||||
|
|
||||||
export function curateUser(user) {
|
export function curateUser(user, assets = {}) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ability = [...(user.role_abilities || []), ...(user.abilities || [])];
|
const curatedStashes = assets.stashes?.filter(Boolean).map((stash) => curateStash(stash)) || [];
|
||||||
|
|
||||||
const curatedUser = {
|
const curatedUser = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
@ -14,19 +15,16 @@ export function curateUser(user) {
|
||||||
email: user.email,
|
email: user.email,
|
||||||
emailVerified: user.email_verified,
|
emailVerified: user.email_verified,
|
||||||
identityVerified: user.identity_verified,
|
identityVerified: user.identity_verified,
|
||||||
ability,
|
|
||||||
avatar: `/media/avatars/${user.id}_${user.username}.png`,
|
avatar: `/media/avatars/${user.id}_${user.username}.png`,
|
||||||
createdAt: user.created_at,
|
createdAt: user.created_at,
|
||||||
// stashes: user.stashes?.filter(Boolean).map((stash) => curateStash(stash)) || [],
|
stashes: curatedStashes,
|
||||||
|
primaryStash: curatedStashes.find((stash) => stash.primary),
|
||||||
};
|
};
|
||||||
|
|
||||||
return curatedUser;
|
return curatedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUser(userId, options = {}) {
|
function whereUser(builder, userId, options = {}) {
|
||||||
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') {
|
if (typeof userId === 'number') {
|
||||||
builder.where('users.id', userId);
|
builder.where('users.id', userId);
|
||||||
}
|
}
|
||||||
|
@ -38,15 +36,27 @@ export async function fetchUser(userId, options = {}) {
|
||||||
builder.orWhere(knex.raw('lower(users.email)'), userId.toLowerCase());
|
builder.orWhere(knex.raw('lower(users.email)'), userId.toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
export async function fetchUser(userId, options = {}) {
|
||||||
|
const user = await knex('users')
|
||||||
|
.select(knex.raw('users.*, users_roles.abilities as role_abilities'))
|
||||||
|
.modify((builder) => whereUser(builder, userId, options))
|
||||||
.leftJoin('users_roles', 'users_roles.role', 'users.role')
|
.leftJoin('users_roles', 'users_roles.role', 'users.role')
|
||||||
.leftJoin('stashes', 'stashes.user_id', 'users.id')
|
|
||||||
.groupBy('users.id', 'users_roles.role')
|
.groupBy('users.id', 'users_roles.role')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw HttpError(`User '${userId}' not found`, 404);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.raw) {
|
if (options.raw) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
return curateUser(user);
|
const stashes = await knex('stashes')
|
||||||
|
.where('user_id', user.id)
|
||||||
|
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id');
|
||||||
|
|
||||||
|
return curateUser(user, { stashes });
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
const substitutes = {
|
||||||
|
à: 'a',
|
||||||
|
á: 'a',
|
||||||
|
ä: 'a',
|
||||||
|
å: 'a',
|
||||||
|
ã: 'a',
|
||||||
|
æ: 'ae',
|
||||||
|
ç: 'c',
|
||||||
|
è: 'e',
|
||||||
|
é: 'e',
|
||||||
|
ë: 'e',
|
||||||
|
ẽ: 'e',
|
||||||
|
ì: 'i',
|
||||||
|
í: 'i',
|
||||||
|
ï: 'i',
|
||||||
|
ĩ: 'i',
|
||||||
|
ǹ: 'n',
|
||||||
|
ń: 'n',
|
||||||
|
ñ: 'n',
|
||||||
|
ò: 'o',
|
||||||
|
ó: 'o',
|
||||||
|
ö: 'o',
|
||||||
|
õ: 'o',
|
||||||
|
ø: 'o',
|
||||||
|
œ: 'oe',
|
||||||
|
ß: 'ss',
|
||||||
|
ù: 'u',
|
||||||
|
ú: 'u',
|
||||||
|
ü: 'u',
|
||||||
|
ũ: 'u',
|
||||||
|
ỳ: 'y',
|
||||||
|
ý: 'y',
|
||||||
|
ÿ: 'y',
|
||||||
|
ỹ: 'y',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function slugify(strings, delimiter = '-', {
|
||||||
|
encode = false,
|
||||||
|
removeAccents = true,
|
||||||
|
removePunctuation = false,
|
||||||
|
limit = 1000,
|
||||||
|
} = {}) {
|
||||||
|
if (!strings || (typeof strings !== 'string' && !Array.isArray(strings))) {
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugComponents = []
|
||||||
|
.concat(strings)
|
||||||
|
.filter(Boolean)
|
||||||
|
.flatMap((string) => string
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(removePunctuation && /[.,:;'"_-]/g, '')
|
||||||
|
.match(/[A-Za-zÀ-ÖØ-öø-ÿ0-9]+/g));
|
||||||
|
|
||||||
|
if (!slugComponents) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = slugComponents.reduce((acc, component, index) => {
|
||||||
|
const accSlug = `${acc}${index > 0 ? delimiter : ''}${component}`;
|
||||||
|
|
||||||
|
if (accSlug.length < limit) {
|
||||||
|
if (removeAccents) {
|
||||||
|
return accSlug.replace(/[à-ÿ]/g, (match) => substitutes[match] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return accSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
return encode ? encodeURI(slug) : slug;
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { login, signup } from '../auth.js';
|
import { login, signup } from '../auth.js';
|
||||||
import { fetchUser } from '../users.js';
|
|
||||||
|
|
||||||
export async function setUserApi(req, res, next) {
|
export async function setUserApi(req, res, next) {
|
||||||
if (req.session.user) {
|
if (req.session.user) {
|
||||||
|
@ -27,17 +26,6 @@ export async function logoutApi(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
export async function signupApi(req, res) {
|
||||||
const user = await signup(req.body);
|
const user = await signup(req.body);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,18 @@ import {
|
||||||
signupApi,
|
signupApi,
|
||||||
} from './auth.js';
|
} from './auth.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createStashApi,
|
||||||
|
removeStashApi,
|
||||||
|
stashActorApi,
|
||||||
|
stashSceneApi,
|
||||||
|
stashMovieApi,
|
||||||
|
unstashActorApi,
|
||||||
|
unstashSceneApi,
|
||||||
|
unstashMovieApi,
|
||||||
|
updateStashApi,
|
||||||
|
} from './stashes.js';
|
||||||
|
|
||||||
import initLogger from '../logger.js';
|
import initLogger from '../logger.js';
|
||||||
|
|
||||||
const logger = initLogger();
|
const logger = initLogger();
|
||||||
|
@ -97,6 +109,19 @@ export default async function initServer() {
|
||||||
// USERS
|
// USERS
|
||||||
router.post('/api/users', signupApi);
|
router.post('/api/users', signupApi);
|
||||||
|
|
||||||
|
// STASHES
|
||||||
|
router.post('/api/stashes', createStashApi);
|
||||||
|
router.patch('/api/stashes/:stashId', updateStashApi);
|
||||||
|
router.delete('/api/stashes/:stashId', removeStashApi);
|
||||||
|
|
||||||
|
router.post('/api/stashes/:stashId/actors', stashActorApi);
|
||||||
|
router.post('/api/stashes/:stashId/scenes', stashSceneApi);
|
||||||
|
router.post('/api/stashes/:stashId/movies', stashMovieApi);
|
||||||
|
|
||||||
|
router.delete('/api/stashes/:stashId/actors/:actorId', unstashActorApi);
|
||||||
|
router.delete('/api/stashes/:stashId/scenes/:sceneId', unstashSceneApi);
|
||||||
|
router.delete('/api/stashes/:stashId/movies/:movieId', unstashMovieApi);
|
||||||
|
|
||||||
// SCENES
|
// SCENES
|
||||||
router.get('/api/scenes', fetchScenesApi);
|
router.get('/api/scenes', fetchScenesApi);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
createStash,
|
||||||
|
removeStash,
|
||||||
|
stashActor,
|
||||||
|
stashScene,
|
||||||
|
stashMovie,
|
||||||
|
unstashActor,
|
||||||
|
unstashScene,
|
||||||
|
unstashMovie,
|
||||||
|
updateStash,
|
||||||
|
} from '../stashes.js';
|
||||||
|
|
||||||
|
export async function createStashApi(req, res) {
|
||||||
|
const stash = await createStash(req.body, req.session.user);
|
||||||
|
|
||||||
|
res.send(stash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateStashApi(req, res) {
|
||||||
|
const stash = await updateStash(req.params.stashId, req.body, req.session.user);
|
||||||
|
|
||||||
|
res.send(stash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeStashApi(req, res) {
|
||||||
|
await removeStash(req.params.stashId, req.session.user);
|
||||||
|
|
||||||
|
res.status(204).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashActorApi(req, res) {
|
||||||
|
const stashes = await stashActor(req.body.actorId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashSceneApi(req, res) {
|
||||||
|
const stashes = await stashScene(req.body.sceneId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashMovieApi(req, res) {
|
||||||
|
const stashes = await stashMovie(req.body.movieId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashActorApi(req, res) {
|
||||||
|
const stashes = await unstashActor(req.params.actorId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashSceneApi(req, res) {
|
||||||
|
const stashes = await unstashScene(req.params.sceneId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unstashMovieApi(req, res) {
|
||||||
|
const stashes = await unstashMovie(req.params.movieId, req.params.stashId, req.user);
|
||||||
|
|
||||||
|
res.send(stashes);
|
||||||
|
}
|
Loading…
Reference in New Issue