Added favorites button to actor page.
This commit is contained in:
parent
e371e9725a
commit
77b40817f2
|
@ -33,6 +33,20 @@
|
|||
:actor="actor"
|
||||
class="header-social"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && isStashed"
|
||||
icon="heart7"
|
||||
class="stash stashed noselect"
|
||||
@click="unstashActor"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && !isStashed"
|
||||
icon="heart8"
|
||||
class="stash unstashed noselect"
|
||||
@click="stashActor"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="content-inner actor-inner">
|
||||
|
@ -54,13 +68,6 @@
|
|||
>
|
||||
</a>
|
||||
|
||||
<Expand
|
||||
v-if="bioExpanded"
|
||||
:expanded="bioExpanded"
|
||||
class="expand expand-light"
|
||||
@expand="(state) => bioExpanded = state"
|
||||
/>
|
||||
|
||||
<ul class="bio nolist">
|
||||
<li
|
||||
v-if="actor.realName"
|
||||
|
@ -384,7 +391,7 @@ import Scroll from '../scroll/scroll.vue';
|
|||
import Gender from './gender.vue';
|
||||
import Social from './social.vue';
|
||||
|
||||
async function fetchActor() {
|
||||
async function fetchActor(scroll = true) {
|
||||
const { actor, releases, totalCount } = await this.$store.dispatch('fetchActorById', {
|
||||
actorId: Number(this.$route.params.actorId),
|
||||
limit: this.limit,
|
||||
|
@ -396,11 +403,37 @@ async function fetchActor() {
|
|||
this.releases = releases;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
if (this.$refs.filter) {
|
||||
if (this.$refs.filter && scroll) {
|
||||
this.$refs.filter.$el.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
async function stashActor() {
|
||||
this.$store.dispatch('stashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.fetchActor(false);
|
||||
}
|
||||
|
||||
async function unstashActor() {
|
||||
this.$store.dispatch('unstashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.fetchActor(false);
|
||||
}
|
||||
|
||||
function me() {
|
||||
return this.$store.state.auth.user;
|
||||
}
|
||||
|
||||
function isStashed() {
|
||||
return this.actor.stashes?.length > 0;
|
||||
}
|
||||
|
||||
function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
|
@ -447,6 +480,8 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
isStashed,
|
||||
me,
|
||||
sfw,
|
||||
showAlbum,
|
||||
},
|
||||
|
@ -457,6 +492,8 @@ export default {
|
|||
mounted,
|
||||
methods: {
|
||||
fetchActor,
|
||||
stashActor,
|
||||
unstashActor,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -477,11 +514,10 @@ export default {
|
|||
align-items: center;
|
||||
color: var(--lighten-extreme);
|
||||
background: var(--profile);
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
padding: 0;
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
|
@ -491,7 +527,7 @@ export default {
|
|||
.header-gender {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 .5rem;
|
||||
transform: translate(0, .1rem);
|
||||
transform: translate(0, .125rem);
|
||||
}
|
||||
|
||||
.header-social {
|
||||
|
@ -731,6 +767,22 @@ export default {
|
|||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
fill: var(--lighten);
|
||||
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint4) {
|
||||
.descriptions-container {
|
||||
display: none;
|
||||
|
@ -797,6 +849,14 @@ export default {
|
|||
.header-name {
|
||||
flex-grow: 1;
|
||||
font-size: 1.3rem;
|
||||
padding: .5rem .5rem .5rem 1rem;
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0 1rem 0 .25rem;
|
||||
transform: translate(0, -.1rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -81,8 +81,8 @@
|
|||
@input="(range) => updateValue('age', range, false)"
|
||||
@change="(range) => updateValue('age', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="flower" /></template>
|
||||
<template v-slot:end><Icon icon="pipe" /></template>
|
||||
<template v-slot:start><Icon icon="leaf" /></template>
|
||||
<template v-slot:end><Icon icon="tree3" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<div class="filter-section">
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
|
||||
<script>
|
||||
async function search() {
|
||||
if (this.query) {
|
||||
this.$router.push({ name: 'search', query: { q: this.query } });
|
||||
this.$emit('search');
|
||||
}
|
||||
}
|
||||
|
||||
function searching(to) {
|
||||
|
|
|
@ -8,31 +8,32 @@
|
|||
</div>
|
||||
|
||||
<section
|
||||
v-if="stashes.length > 0"
|
||||
v-if="user.stashes?.length > 0"
|
||||
class="section"
|
||||
>
|
||||
<h3 class="heading">Stashes</h3>
|
||||
|
||||
<ul class="stashes nolist">
|
||||
<li
|
||||
v-for="stash in stashes"
|
||||
v-for="stash in user.stashes"
|
||||
:key="stash.id"
|
||||
class="stash"
|
||||
>
|
||||
<h4 class="stash-name">{{ stash.name }}</h4>
|
||||
|
||||
<ul class="stash nolist actors">
|
||||
<li
|
||||
v-for="item in stash.actors"
|
||||
:key="item.id"
|
||||
><Actor :actor="item.actor" /></li>
|
||||
</ul>
|
||||
|
||||
<ul class="stash nolist scenes">
|
||||
<ul class="stash-section stash-scenes nolist">
|
||||
<li
|
||||
v-for="item in stash.scenes"
|
||||
:key="item.id"
|
||||
><Scene :release="item.scene" /></li>
|
||||
</ul>
|
||||
|
||||
<ul class="stash-section stash-actors nolist">
|
||||
<li
|
||||
v-for="item in stash.actors"
|
||||
:key="item.id"
|
||||
><Actor :actor="item.actor" /></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -44,8 +45,8 @@ import Actor from '../actors/tile.vue';
|
|||
import Scene from '../releases/scene-tile.vue';
|
||||
|
||||
async function mounted() {
|
||||
this.user = await this.$store.dispatch('fetchMe');
|
||||
this.stashes = await this.$store.dispatch('fetchUserStashes', this.user.id);
|
||||
this.user = await this.$store.dispatch('fetchUser', this.$route.params.username);
|
||||
this.pageTitle = this.user?.username;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -58,7 +59,7 @@ export default {
|
|||
user: this.$route.params.username === this.$store.state.auth.user?.username
|
||||
? this.$store.state.auth.user
|
||||
: null,
|
||||
stashes: [],
|
||||
pageTitle: null,
|
||||
};
|
||||
},
|
||||
mounted,
|
||||
|
@ -67,7 +68,7 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
padding: 1rem;
|
||||
padding: .5rem 1rem;
|
||||
background: var(--profile);
|
||||
}
|
||||
|
||||
|
@ -87,27 +88,38 @@ export default {
|
|||
}
|
||||
|
||||
.stash {
|
||||
background: var(--background);
|
||||
margin: 0 0 1rem 0;
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
}
|
||||
|
||||
.stash-name {
|
||||
color: var(--shadow-strong);
|
||||
margin: 0 0 1rem 0;
|
||||
padding: 1rem .5rem 0 .5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actors {
|
||||
.stash-section {
|
||||
padding: 1rem .5rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
}
|
||||
|
||||
.stash-actors,
|
||||
.stash-scenes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.scenes {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stash-actors {
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
}
|
||||
|
||||
.stash-scenes {
|
||||
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
color: var(--shadow-strong);
|
||||
background: var(--background);
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
|
||||
&:focus {
|
||||
border: solid 1px var(--primary);
|
||||
|
|
|
@ -12,7 +12,7 @@ $breakpoint4: 1500px;
|
|||
*/
|
||||
--primary: #f28;
|
||||
--primary-strong: #f90071;
|
||||
--primary-faded: #ff4e9f;
|
||||
--primary-faded: #ffcce4;
|
||||
|
||||
--text-dark: #222;
|
||||
--text-light: #fff;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>leaf2</title>
|
||||
<path d="M15.802 2.102c-1.73-1.311-4.393-2.094-7.124-2.094-3.377 0-6.129 1.179-7.549 3.235-0.667 0.965-1.036 2.109-1.097 3.398-0.065 1.375 0.225 2.925 0.859 4.606-0.586 1.428-0.904 2.898-0.904 4.254 0 0.276 0.224 0.5 0.5 0.5s0.5-0.224 0.5-0.5c0-1.198 0.293-2.535 0.818-3.835 1.472 0.272 2.712 0.405 3.776 0.405 1.839 0 3.146-0.398 4.115-1.252 0.868-0.765 1.347-1.794 1.854-2.882 0.774-1.663 1.651-3.547 4.198-5.002 0.146-0.083 0.24-0.234 0.251-0.402s-0.063-0.329-0.197-0.431zM10.644 7.515c-0.481 1.034-0.897 1.927-1.608 2.554-0.776 0.684-1.873 1.002-3.454 1.002-0.945 0-2.047-0.113-3.351-0.345 0.238-0.476 0.507-0.941 0.804-1.386 0.692-1.036 1.505-1.931 2.417-2.661 0.984-0.788 2.059-1.36 3.193-1.7 0.264-0.079 0.415-0.358 0.335-0.623s-0.358-0.415-0.623-0.335c-1.257 0.377-2.445 1.009-3.53 1.878-0.991 0.794-1.874 1.765-2.623 2.886-0.252 0.378-0.485 0.767-0.698 1.163-0.36-1.185-0.52-2.279-0.474-3.261 0.052-1.099 0.361-2.067 0.921-2.876 0.636-0.92 1.583-1.633 2.816-2.119 1.134-0.447 2.487-0.684 3.911-0.684 2.172 0 4.357 0.555 5.9 1.475-2.314 1.551-3.206 3.467-3.935 5.032z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,5 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>tree2</title>
|
||||
<path d="M13.887 13.182l-2.387-3.182h1c0 0 0.001 0 0.001 0 0.276 0 0.5-0.224 0.5-0.5 0-0.121-0.043-0.231-0.114-0.318l-2.387-3.182h1c0.192 0 0.367-0.11 0.451-0.283s0.060-0.379-0.060-0.529l-4-5c-0.095-0.119-0.239-0.188-0.39-0.188s-0.296 0.069-0.39 0.188l-4 5c-0.12 0.15-0.143 0.356-0.060 0.529s0.258 0.283 0.451 0.283h1l-2.4 3.2c-0.114 0.152-0.132 0.354-0.047 0.524s0.258 0.276 0.447 0.276h1l-2.4 3.2c-0.114 0.152-0.132 0.354-0.047 0.524s0.258 0.276 0.447 0.276h4.5v1.5c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-1.5h4.5c0 0 0 0 0.001 0 0.276 0 0.5-0.224 0.5-0.5 0-0.121-0.043-0.231-0.114-0.318zM8 15h-1v-1h1v1zM2.5 13l2.4-3.2c0.114-0.152 0.132-0.354 0.047-0.524s-0.258-0.276-0.447-0.276h-1l2.4-3.2c0.114-0.152 0.132-0.354 0.047-0.524s-0.258-0.276-0.447-0.276h-0.96l2.96-3.7 2.96 3.7h-0.96c-0.189 0-0.363 0.107-0.447 0.276s-0.066 0.372 0.047 0.524l2.4 3.2h-1c-0.189 0-0.363 0.107-0.447 0.276s-0.066 0.372 0.047 0.524l2.4 3.2h-10z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,5 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>tree3</title>
|
||||
<path d="M11.852 3.354c-0.508-1.928-2.266-3.354-4.352-3.354s-3.844 1.426-4.352 3.354c-1.891 0.754-3.148 2.596-3.148 4.646 0 2.757 2.243 5 5 5 0.34 0 0.674-0.035 1-0.101v2.601c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-2.601c0.327 0.066 0.661 0.101 1 0.101 2.757 0 5-2.243 5-5 0-2.060-1.254-3.892-3.148-4.646z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 489 B |
|
@ -0,0 +1,5 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>tree4</title>
|
||||
<path d="M11.852 3.354c-0.508-1.928-2.266-3.354-4.352-3.354s-3.844 1.426-4.352 3.354c-1.891 0.754-3.148 2.596-3.148 4.646 0 2.757 2.243 5 5 5h1v2.5c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-2.5h1c2.757 0 5-2.243 5-5 0-2.060-1.254-3.892-3.148-4.646zM8 15h-1v-2h1v2zM10 12h-5c-2.206 0-4-1.794-4-4 0-1.444 0.781-2.76 2.001-3.465 0.003 0.369 0.050 0.735 0.141 1.090 0.058 0.226 0.261 0.375 0.484 0.375 0.041 0 0.083-0.005 0.125-0.016 0.267-0.069 0.428-0.341 0.36-0.609-0.073-0.284-0.11-0.579-0.11-0.875 0-1.93 1.57-3.5 3.5-3.5s3.5 1.57 3.5 3.5c0 0.297-0.037 0.591-0.11 0.875-0.069 0.267 0.092 0.54 0.36 0.609s0.54-0.092 0.609-0.36c0.091-0.355 0.139-0.722 0.141-1.091 1.222 0.704 2.001 2.014 2.001 3.466 0 2.206-1.794 4-4 4z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 901 B |
|
@ -24,6 +24,8 @@ function initActorActions(store, router) {
|
|||
const { actor } = await graphql(`
|
||||
query Actor(
|
||||
$actorId: Int!
|
||||
$userId: Int,
|
||||
$hasAuth: Boolean!,
|
||||
$limit:Int = 10,
|
||||
$offset:Int = 0,
|
||||
$after:Datetime = "1900-01-01",
|
||||
|
@ -236,6 +238,21 @@ function initActorActions(store, router) {
|
|||
}
|
||||
totalCount
|
||||
}
|
||||
stashes: stashesActors(
|
||||
filter: {
|
||||
stash: {
|
||||
userId: {
|
||||
equalTo: $userId
|
||||
}
|
||||
}
|
||||
}
|
||||
) @include(if: $hasAuth) {
|
||||
stash {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
|
@ -253,6 +270,8 @@ function initActorActions(store, router) {
|
|||
includedEntities: getIncludedEntities(router),
|
||||
includedActors: getIncludedActors(router),
|
||||
mode,
|
||||
hasAuth: !!store.state.auth.user,
|
||||
userId: store.state.auth.user?.id,
|
||||
});
|
||||
|
||||
if (!actor) {
|
||||
|
|
|
@ -10,10 +10,16 @@ async function get(endpoint, query = {}) {
|
|||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentTypes = res.headers.get('content-type');
|
||||
|
||||
if (res.ok && contentTypes?.includes('application/json')) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorMsg = await res.text();
|
||||
|
||||
throw new Error(errorMsg);
|
||||
|
@ -30,10 +36,16 @@ async function post(endpoint, data) {
|
|||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentTypes = res.headers.get('content-type');
|
||||
|
||||
if (res.ok && contentTypes?.includes('application/json')) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorMsg = await res.text();
|
||||
|
||||
throw new Error(errorMsg);
|
||||
|
|
|
@ -2,11 +2,16 @@ import { get, post, del } from '../api';
|
|||
|
||||
function initAuthActions(_store, _router) {
|
||||
async function fetchMe({ commit }) {
|
||||
try {
|
||||
const user = await get('/session');
|
||||
|
||||
commit('setUser', user);
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
// continue as guest
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function login({ commit }, credentials) {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import state from './state';
|
||||
import mutations from './mutations';
|
||||
import getters from './getters';
|
||||
import actions from './actions';
|
||||
|
||||
function initAuthStore(store, router) {
|
||||
return {
|
||||
state,
|
||||
mutations,
|
||||
getters,
|
||||
actions: actions(store, router),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
function favoritesStash(state) {
|
||||
return state.user.stashes.find(stash => stash.slug === 'favorites');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
favoritesStash,
|
||||
favorites: favoritesStash,
|
||||
};
|
|
@ -56,6 +56,8 @@ function curateActor(actor, release) {
|
|||
curatedActor.aliasFor = curateActor(curatedActor.aliasFor);
|
||||
}
|
||||
|
||||
curatedActor.stashes = actor.stashes?.map(stash => stash.stash || stash) || [];
|
||||
|
||||
return curatedActor;
|
||||
}
|
||||
|
||||
|
@ -126,9 +128,7 @@ function curateTag(tag) {
|
|||
}
|
||||
|
||||
function curateStash(stash) {
|
||||
const curatedStash = {
|
||||
...stash,
|
||||
};
|
||||
const curatedStash = stash;
|
||||
|
||||
if (stash.scenes) {
|
||||
curatedStash.scenes = stash.scenes.map(item => ({
|
||||
|
@ -147,10 +147,21 @@ function curateStash(stash) {
|
|||
return curatedStash;
|
||||
}
|
||||
|
||||
function curateUser(user) {
|
||||
const curatedUser = user;
|
||||
|
||||
if (user.stashes) {
|
||||
curatedUser.stashes = user.stashes.map(stash => curateStash(stash));
|
||||
}
|
||||
|
||||
return curatedUser;
|
||||
}
|
||||
|
||||
export {
|
||||
curateActor,
|
||||
curateEntity,
|
||||
curateRelease,
|
||||
curateTag,
|
||||
curateStash,
|
||||
curateUser,
|
||||
};
|
||||
|
|
|
@ -64,8 +64,8 @@ async function init() {
|
|||
return `${path}/${filename}`;
|
||||
}
|
||||
|
||||
initUiObservers(store, router);
|
||||
initAuthObservers(store, router);
|
||||
await initAuthObservers(store, router);
|
||||
await initUiObservers(store, router);
|
||||
|
||||
if (window.env.sfw) {
|
||||
store.dispatch('setSfw', true);
|
||||
|
|
|
@ -1,101 +1,17 @@
|
|||
import { graphql } from '../api';
|
||||
import { curateStash } from '../curate';
|
||||
import { post, del } from '../api';
|
||||
|
||||
function initStashesActions(_store, _router) {
|
||||
async function fetchUserStashes(context, userId) {
|
||||
const { stashes } = await graphql(`
|
||||
query Stashes(
|
||||
$userId: Int!
|
||||
) {
|
||||
stashes(
|
||||
filter: {
|
||||
userId: {
|
||||
equalTo: $userId
|
||||
async function stashActor(context, { actorId, stashId }) {
|
||||
await post(`/stashes/${stashId}/actors`, { actorId });
|
||||
}
|
||||
}
|
||||
) {
|
||||
id
|
||||
name
|
||||
actors: stashesActors {
|
||||
comment
|
||||
actor {
|
||||
id
|
||||
name
|
||||
slug
|
||||
gender
|
||||
age
|
||||
ageFromBirth
|
||||
dateOfBirth
|
||||
birthCity
|
||||
birthState
|
||||
birthCountry: countryByBirthCountryAlpha2 {
|
||||
alpha2
|
||||
name
|
||||
alias
|
||||
}
|
||||
avatar: avatarMedia {
|
||||
id
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
}
|
||||
}
|
||||
}
|
||||
scenes: stashesScenes {
|
||||
comment
|
||||
scene {
|
||||
id
|
||||
title
|
||||
slug
|
||||
url
|
||||
date
|
||||
actors: releasesActors {
|
||||
actor {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
tags: releasesTags {
|
||||
tag {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
entity {
|
||||
id
|
||||
name
|
||||
slug
|
||||
independent
|
||||
parent {
|
||||
id
|
||||
name
|
||||
slug
|
||||
independent
|
||||
}
|
||||
}
|
||||
poster: releasesPosterByReleaseId {
|
||||
media {
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
isS3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
userId,
|
||||
});
|
||||
|
||||
return stashes.map(stash => curateStash(stash));
|
||||
async function unstashActor(context, { actorId, stashId }) {
|
||||
await del(`/stashes/${stashId}/actors/${actorId}`);
|
||||
}
|
||||
|
||||
return {
|
||||
fetchUserStashes,
|
||||
stashActor,
|
||||
unstashActor,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,95 @@
|
|||
import { get } from '../api';
|
||||
import { graphql } from '../api';
|
||||
|
||||
function initUsersActions(_store, _router) {
|
||||
async function fetchUser(context, username) {
|
||||
const user = await get(`/users/${username}`);
|
||||
const { user } = await graphql(`
|
||||
query User(
|
||||
$username: String!
|
||||
) {
|
||||
user: userByUsername(username: $username) {
|
||||
id
|
||||
role
|
||||
username
|
||||
stashes {
|
||||
id
|
||||
name
|
||||
slug
|
||||
public
|
||||
actors: stashesActors {
|
||||
comment
|
||||
actor {
|
||||
id
|
||||
name
|
||||
slug
|
||||
gender
|
||||
age
|
||||
ageFromBirth
|
||||
dateOfBirth
|
||||
birthCity
|
||||
birthState
|
||||
birthCountry: countryByBirthCountryAlpha2 {
|
||||
alpha2
|
||||
name
|
||||
alias
|
||||
}
|
||||
avatar: avatarMedia {
|
||||
id
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
}
|
||||
}
|
||||
}
|
||||
scenes: stashesScenes {
|
||||
comment
|
||||
scene {
|
||||
id
|
||||
title
|
||||
slug
|
||||
url
|
||||
date
|
||||
actors: releasesActors {
|
||||
actor {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
tags: releasesTags {
|
||||
tag {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
entity {
|
||||
id
|
||||
name
|
||||
slug
|
||||
independent
|
||||
parent {
|
||||
id
|
||||
name
|
||||
slug
|
||||
independent
|
||||
}
|
||||
}
|
||||
poster: releasesPosterByReleaseId {
|
||||
media {
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
isS3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
username,
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -1081,6 +1081,8 @@ exports.up = knex => Promise.resolve()
|
|||
.references('id')
|
||||
.inTable('releases');
|
||||
|
||||
table.unique(['stash_id', 'scene_id']);
|
||||
|
||||
table.string('comment');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('stashes_actors', (table) => {
|
||||
|
@ -1094,6 +1096,8 @@ exports.up = knex => Promise.resolve()
|
|||
.references('id')
|
||||
.inTable('actors');
|
||||
|
||||
table.unique(['stash_id', 'actor_id']);
|
||||
|
||||
table.string('comment');
|
||||
}))
|
||||
// SEARCH
|
||||
|
@ -1295,21 +1299,24 @@ exports.up = knex => Promise.resolve()
|
|||
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||
|
||||
REVOKE ALL ON users FROM :visitor;
|
||||
GRANT SELECT (id, username, role, identity_verified, created_at) ON users TO :visitor;
|
||||
|
||||
ALTER TABLE stashes ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE stashes_scenes ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE stashes_actors ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY stashes_policy_select ON stashes FOR SELECT USING (stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_update ON stashes FOR UPDATE USING (stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_delete ON stashes FOR DELETE USING (stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_insert ON stashes FOR INSERT WITH CHECK(true);
|
||||
CREATE POLICY stashes_policy_select ON stashes FOR SELECT USING (stashes.public OR stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_update ON stashes FOR UPDATE USING (stashes.public OR stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_delete ON stashes FOR DELETE USING (stashes.public OR stashes.user_id = current_user_id());
|
||||
CREATE POLICY stashes_policy_insert ON stashes FOR INSERT WITH CHECK (true);
|
||||
|
||||
CREATE POLICY stashes_policy ON stashes_scenes
|
||||
USING (EXISTS (
|
||||
SELECT *
|
||||
FROM stashes
|
||||
WHERE stashes.id = stashes_scenes.stash_id
|
||||
AND stashes.user_id = current_user_id()
|
||||
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||
));
|
||||
|
||||
CREATE POLICY stashes_policy ON stashes_actors
|
||||
|
@ -1317,7 +1324,7 @@ exports.up = knex => Promise.resolve()
|
|||
SELECT *
|
||||
FROM stashes
|
||||
WHERE stashes.id = stashes_actors.stash_id
|
||||
AND stashes.user_id = current_user_id()
|
||||
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||
));
|
||||
`, {
|
||||
visitor: knex.raw(config.database.query.user),
|
||||
|
@ -1328,8 +1335,10 @@ exports.up = knex => Promise.resolve()
|
|||
.then(() => { // eslint-disable-line arrow-body-style
|
||||
// allow vim fold
|
||||
return knex.raw(`
|
||||
COMMENT ON TABLE users IS E'@omit';
|
||||
COMMENT ON TABLE users_roles IS E'@omit';
|
||||
COMMENT ON COLUMN users.password IS E'@omit';
|
||||
COMMENT ON COLUMN users.email IS E'@omit';
|
||||
COMMENT ON COLUMN users.email_verified IS E'@omit';
|
||||
COMMENT ON COLUMN users.abilities IS E'@omit';
|
||||
|
||||
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
|
||||
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
|
||||
|
|
13
src/auth.js
13
src/auth.js
|
@ -4,7 +4,7 @@ const util = require('util');
|
|||
const crypto = require('crypto');
|
||||
|
||||
const knex = require('./knex');
|
||||
const { curateUser } = require('./users');
|
||||
const { curateUser, fetchUser } = require('./users');
|
||||
const { HttpError } = require('./errors');
|
||||
|
||||
const scrypt = util.promisify(crypto.scrypt);
|
||||
|
@ -21,12 +21,7 @@ async function verifyPassword(password, storedPassword) {
|
|||
}
|
||||
|
||||
async function login(credentials) {
|
||||
const user = await knex('users')
|
||||
.select('users.*', 'users_roles.abilities as role_abilities')
|
||||
.where('username', credentials.username)
|
||||
.orWhere('email', credentials.username)
|
||||
.leftJoin('users_roles', 'users_roles.role', 'users.role')
|
||||
.first();
|
||||
const user = await fetchUser(credentials.username, true);
|
||||
|
||||
if (!user) {
|
||||
throw new HttpError('Username or password incorrect', 401);
|
||||
|
@ -69,7 +64,7 @@ async function signup(credentials) {
|
|||
email: credentials.email,
|
||||
password: storedPassword,
|
||||
})
|
||||
.returning('*');
|
||||
.returning('id');
|
||||
|
||||
await knex('stashes').insert({
|
||||
user_id: user.id,
|
||||
|
@ -78,7 +73,7 @@ async function signup(credentials) {
|
|||
public: false,
|
||||
});
|
||||
|
||||
return curateUser(user);
|
||||
return fetchUser(user.id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('./knex');
|
||||
const { HttpError } = require('./errors');
|
||||
|
||||
function curateStash(stash) {
|
||||
const curatedStash = {
|
||||
id: stash.id,
|
||||
name: stash.name,
|
||||
slug: stash.slug,
|
||||
};
|
||||
|
||||
return curatedStash;
|
||||
}
|
||||
|
||||
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 modify this stash', 403);
|
||||
}
|
||||
|
||||
return stash;
|
||||
}
|
||||
|
||||
async function stashActor(actorId, stashId, sessionUser) {
|
||||
const stash = await fetchStash(stashId, sessionUser);
|
||||
|
||||
await knex('stashes_actors')
|
||||
.insert({
|
||||
stash_id: stash.id,
|
||||
actor_id: actorId,
|
||||
});
|
||||
}
|
||||
|
||||
async function stashScene(sceneId, stashId, sessionUser) {
|
||||
const stash = await fetchStash(stashId, sessionUser);
|
||||
|
||||
await knex('stashes_scenes')
|
||||
.insert({
|
||||
stash_id: stash.id,
|
||||
actor_id: sceneId,
|
||||
});
|
||||
}
|
||||
|
||||
async function unstashActor(actorId, stashId, sessionUser) {
|
||||
await knex
|
||||
.from('stashes_actors')
|
||||
.whereIn('stashes_actors.id', knex('stashes_actors')
|
||||
.select('stashes_actors.id')
|
||||
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
|
||||
.where('stashes.user_id', sessionUser.id) // verify user owns this stash
|
||||
.where('stashes_actors.actor_id', actorId)
|
||||
.where('stashes_actors.stash_id', stashId))
|
||||
.delete();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
curateStash,
|
||||
stashActor,
|
||||
stashScene,
|
||||
// unstashScene,
|
||||
unstashActor,
|
||||
};
|
26
src/users.js
26
src/users.js
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('./knex');
|
||||
const { curateStash } = require('./stashes');
|
||||
|
||||
function curateUser(user) {
|
||||
if (!user) {
|
||||
|
@ -17,20 +18,35 @@ function curateUser(user) {
|
|||
identityVerified: user.identity_verified,
|
||||
ability,
|
||||
createdAt: user.created_at,
|
||||
stashes: user.stashes?.map(stash => curateStash(stash)) || [],
|
||||
};
|
||||
|
||||
return curatedUser;
|
||||
}
|
||||
|
||||
async function fetchUser(userId) {
|
||||
async function fetchUser(userId, raw) {
|
||||
const user = await knex('users')
|
||||
.select('users.*', 'users_roles.abilities as role_abilities')
|
||||
.where('id', userId)
|
||||
.orWhere('username', userId)
|
||||
.orWhere('email', userId)
|
||||
.select(knex.raw('users.*, users_roles.abilities as role_abilities, json_agg(stashes) 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ async function fetchMeApi(req, res) {
|
|||
async function signupApi(req, res) {
|
||||
const user = await signup(req.body);
|
||||
|
||||
req.session.user = user;
|
||||
res.send(user);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ module.exports = postgraphile(
|
|||
'public',
|
||||
{
|
||||
// watchPg: true,
|
||||
disableDefaultMutations: true,
|
||||
dynamicJson: true,
|
||||
graphiql: true,
|
||||
enhanceGraphiql: true,
|
||||
|
|
|
@ -43,6 +43,13 @@ const {
|
|||
fetchTags,
|
||||
} = require('./tags');
|
||||
|
||||
const {
|
||||
stashActor,
|
||||
stashScene,
|
||||
unstashActor,
|
||||
unstashScene,
|
||||
} = require('./stashes');
|
||||
|
||||
async function initServer() {
|
||||
const app = express();
|
||||
const router = Router();
|
||||
|
@ -74,6 +81,12 @@ async function initServer() {
|
|||
|
||||
router.post('/api/users', signup);
|
||||
|
||||
router.post('/api/stashes/:stashId/actors', stashActor);
|
||||
router.post('/api/stashes/:stashId/scenes', stashScene);
|
||||
|
||||
router.delete('/api/stashes/:stashId/actors/:actorId', unstashActor);
|
||||
router.delete('/api/stashes/:stashId/scenes/:sceneId', unstashScene);
|
||||
|
||||
router.get('/api/scenes', fetchScenes);
|
||||
router.get('/api/scenes/:releaseId', fetchScene);
|
||||
router.get('/api/scenes/:releaseId/poster', fetchScenePoster);
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
'use strict';
|
||||
|
||||
const { stashActor, stashScene, unstashActor, unstashScene } = require('../stashes');
|
||||
|
||||
async function stashActorApi(req, res) {
|
||||
await stashActor(req.body.actorId, req.params.stashId, req.session.user);
|
||||
|
||||
res.status(201).send();
|
||||
}
|
||||
|
||||
async function stashSceneApi(req, res) {
|
||||
await stashScene(req.body.sceneId, req.params.stashId, req.session.user);
|
||||
|
||||
res.status(201).send();
|
||||
}
|
||||
|
||||
async function unstashActorApi(req, res) {
|
||||
await unstashActor(req.params.actorId, req.params.stashId, req.session.user);
|
||||
|
||||
res.status(204).send();
|
||||
}
|
||||
|
||||
async function unstashSceneApi(req, res) {
|
||||
await unstashScene(req.params.sceneId, req.params.stashId, req.session.user);
|
||||
|
||||
res.status(204).send();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stashActor: stashActorApi,
|
||||
stashScene: stashSceneApi,
|
||||
unstashActor: unstashActorApi,
|
||||
unstashScene: unstashSceneApi,
|
||||
};
|
Loading…
Reference in New Issue