Added filterable stash pages.
5
.babelrc
|
@ -4,5 +4,8 @@
|
|||
"@babel/preset-env"
|
||||
]
|
||||
],
|
||||
"plugins": ["@babel/plugin-transform-optional-chaining"],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-optional-chaining",
|
||||
"@babel/plugin-syntax-import-attributes"
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16">
|
||||
<title>archive</title>
|
||||
<path d="M4 1h-3c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h3c0.55 0 1-0.45 1-1v-12c0-0.55-0.45-1-1-1zM3 12.625c0 0.206-0.169 0.375-0.375 0.375h-0.25c-0.206 0-0.375-0.169-0.375-0.375v-0.25c0-0.206 0.169-0.375 0.375-0.375h0.25c0.206 0 0.375 0.169 0.375 0.375v0.25zM4 10h-3v-5h3v5zM4 4h-3v-1h3v1zM10 1h-3c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h3c0.55 0 1-0.45 1-1v-12c0-0.55-0.45-1-1-1zM9 12.625c0 0.206-0.169 0.375-0.375 0.375h-0.25c-0.206 0-0.375-0.169-0.375-0.375v-0.25c0-0.206 0.169-0.375 0.375-0.375h0.25c0.206 0 0.375 0.169 0.375 0.375v0.25zM10 10h-3v-5h3v5zM10 4h-3v-1h3v1zM16 1h-3c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h3c0.55 0 1-0.45 1-1v-12c0-0.55-0.45-1-1-1zM15 12.625c0 0.206-0.169 0.375-0.375 0.375h-0.25c-0.206 0-0.375-0.169-0.375-0.375v-0.25c0-0.206 0.169-0.375 0.375-0.375h0.25c0.206 0 0.375 0.169 0.375 0.375v0.25zM16 10h-3v-5h3v5zM16 4h-3v-1h3v1z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 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>box</title>
|
||||
<path d="M1 2h14v2h-14zM2 14h12v-9h-12v9zM6 6h4v1h-4v-1z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 223 B |
|
@ -0,0 +1,7 @@
|
|||
<!-- 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>cabinet</title>
|
||||
<path d="M2 16h12v-4h-12v4zM6 13.5c0-0.276 0.224-0.5 0.5-0.5h3c0.276 0 0.5 0.224 0.5 0.5v1c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-0.5h-2v0.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-1z"></path>
|
||||
<path d="M2 11h12v-4h-12v4zM6 8.5c0-0.276 0.224-0.5 0.5-0.5h3c0.276 0 0.5 0.224 0.5 0.5v1c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-0.5h-2v0.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-1z"></path>
|
||||
<path d="M11 0h-6l-3 2v4h12v-4l-3-2zM10 4.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-0.5h-2v0.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-1c0-0.276 0.224-0.5 0.5-0.5h3c0.276 0 0.5 0.224 0.5 0.5v1z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 787 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>drawer3</title>
|
||||
<path d="M14.039 0l1.051 8.931-1.179 0.139-0.938-7.976h-9.944l-0.938 7.976-1.179-0.139 1.051-8.931zM4 2h8v1h-8zM4 4h8v1h-8zM4 6h8v1h-8zM4 8h8v1h-8zM15.5 10h-15c-0.275 0-0.429 0.213-0.342 0.474l1.684 5.051c0.087 0.261 0.383 0.474 0.658 0.474h11c0.275 0 0.571-0.213 0.658-0.474l1.684-5.051c0.087-0.261-0.067-0.474-0.342-0.474zM10 12h-4v-1h4v1z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 512 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>folder-open</title>
|
||||
<path d="M13 15l3-8h-13l-3 8zM2 6l-2 9v-13h4.5l2 2h6.5v2z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 232 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>folder-open2</title>
|
||||
<path d="M15.383 6c0.412 0 0.684 0.331 0.603 0.735l-1.839 6.529c-0.081 0.405-0.485 0.735-0.897 0.735h-10.5c-0.413 0-0.816-0.331-0.897-0.735l-1.839-6.529c-0.081-0.404 0.19-0.735 0.603-0.735h14.766zM14 2.75v2.25h-12v-3.25c0-0.414 0.336-0.75 0.75-0.75h3.25l0.5 1h6.75c0.414 0 0.75 0.336 0.75 0.75z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 470 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>folder</title>
|
||||
<path d="M0 5h16l-1 10h-14l-1-10zM14.5 3l0.5 1h-14l1-2h5.5l0.5 1h6.5z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 239 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>folder2</title>
|
||||
<path d="M7 2l2 2h7v11h-16v-13z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 202 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>folder5</title>
|
||||
<path d="M15.383 4h-14.766c-0.412 0-0.684 0.331-0.603 0.735l1.839 8.529c0.081 0.405 0.485 0.735 0.897 0.735h10.5c0.412 0 0.816-0.331 0.897-0.735l1.839-8.529c0.081-0.404-0.19-0.735-0.603-0.735zM14 2.75c0-0.414-0.336-0.75-0.75-0.75h-6.75l-0.5-1h-3.25c-0.414 0-0.75 0.336-0.75 0.75v1.25h12v-0.25z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 464 B |
|
@ -0,0 +1,7 @@
|
|||
<!-- 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>hipster</title>
|
||||
<path d="M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zM8 1.5c3.59 0 6.5 2.91 6.5 6.5s-2.91 6.5-6.5 6.5-6.5-2.91-6.5-6.5 2.91-6.5 6.5-6.5zM4 5c0-0.552 0.448-1 1-1s1 0.448 1 1-0.448 1-1 1-1-0.448-1-1zM10 5c0-0.552 0.448-1 1-1s1 0.448 1 1-0.448 1-1 1-1-0.448-1-1z"></path>
|
||||
<path d="M10.561 8.439c-0.586-0.586-1.536-0.586-2.121 0s-0.586 1.536 0 2.121c0.019 0.019 0.038 0.037 0.058 0.055 1.352 1.227 4.503-0.029 4.503-1.615-0.969 0.625-1.726 0.153-2.439-0.561z"></path>
|
||||
<path d="M5.439 8.439c0.586-0.586 1.536-0.586 2.121 0s0.586 1.536 0 2.121c-0.019 0.019-0.038 0.037-0.058 0.055-1.352 1.227-4.503-0.029-4.503-1.615 0.969 0.625 1.726 0.153 2.439-0.561z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 837 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>hipster2</title>
|
||||
<path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM11 4c0.552 0 1 0.448 1 1s-0.448 1-1 1-1-0.448-1-1 0.448-1 1-1zM5 4c0.552 0 1 0.448 1 1s-0.448 1-1 1-1-0.448-1-1 0.448-1 1-1zM8.497 10.615c-0.020-0.018-0.039-0.036-0.058-0.055-0.293-0.293-0.439-0.677-0.439-1.060-0 0.384-0.146 0.768-0.439 1.060-0.019 0.019-0.038 0.037-0.058 0.055-1.352 1.227-4.503-0.029-4.503-1.615 0.969 0.625 1.726 0.153 2.439-0.561 0.586-0.586 1.536-0.586 2.121 0 0.293 0.293 0.439 0.677 0.439 1.060 0-0.384 0.146-0.768 0.439-1.060 0.586-0.586 1.536-0.586 2.121 0 0.713 0.714 1.471 1.186 2.439 0.561 0 1.586-3.151 2.842-4.503 1.615z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 795 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>hour-glass</title>
|
||||
<path d="M11.39 8c2.152-1.365 3.61-3.988 3.61-7 0-0.339-0.019-0.672-0.054-1h-13.891c-0.036 0.328-0.054 0.661-0.054 1 0 3.012 1.457 5.635 3.609 7-2.152 1.365-3.609 3.988-3.609 7 0 0.339 0.019 0.672 0.054 1h13.891c0.036-0.328 0.054-0.661 0.054-1 0-3.012-1.457-5.635-3.609-7zM2.5 15c0-2.921 1.253-5.397 3.5-6.214v-1.572c-2.247-0.817-3.5-3.294-3.5-6.214v0h11c0 2.921-1.253 5.397-3.5 6.214v1.572c2.247 0.817 3.5 3.294 3.5 6.214h-11zM9.682 10.462c-1.12-0.635-1.181-1.459-1.182-1.959v-1.004c0-0.5 0.059-1.327 1.184-1.963 0.602-0.349 1.122-0.88 1.516-1.537h-6.4c0.395 0.657 0.916 1.188 1.518 1.538 1.12 0.635 1.181 1.459 1.182 1.959v1.004c0 0.5-0.059 1.327-1.184 1.963-1.135 0.659-1.98 1.964-2.236 3.537h7.839c-0.256-1.574-1.102-2.879-2.238-3.538z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 913 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>hour-glass2</title>
|
||||
<path d="M10.838 9.824c-1.169-0.761-1.838-1.188-1.838-1.824s0.67-1.064 1.838-1.824c1.882-1.183 3.162-3.505 3.162-6.176h-12c0 2.671 1.28 4.993 3.162 6.176 1.169 0.761 1.838 1.188 1.838 1.824s-0.67 1.064-1.838 1.824c-1.882 1.183-3.162 3.505-3.162 6.176h12c0-2.671-1.279-4.993-3.162-6.176zM4.057 3.028c-0.274-0.525-0.474-1.097-0.593-1.695h9.072c-0.119 0.598-0.318 1.17-0.593 1.695-0.184 0.352-0.399 0.678-0.64 0.972h-6.607c-0.241-0.294-0.455-0.619-0.64-0.972zM7.75 11.963c0 0.681-2.75 1.704-2.75 2.704h-1.536c0.119-0.598 0.318-1.17 0.593-1.695 0.447-0.855 1.074-1.553 1.814-2.018l0.009-0.006 0.009-0.006 0.048-0.031c0.73-0.475 1.376-0.896 1.813-1.399v2.451zM12.536 14.667h-1.536c0-1-2.75-2.023-2.75-2.704v-2.451c0.437 0.503 1.083 0.924 1.813 1.399l0.057 0.037 0.009 0.006c0.74 0.465 1.367 1.163 1.814 2.018 0.274 0.525 0.474 1.097 0.593 1.695z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1015 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>hour-glass3</title>
|
||||
<path d="M5.939 9.532c-1.49 0.81-2.591 2.472-2.87 4.468h9.862c-0.279-1.996-1.379-3.658-2.87-4.468-1.061-0.754-1.061-1.116-1.061-1.532s0-0.778 1.061-1.532c1.49-0.81 2.591-2.472 2.87-4.468h-9.862c0.279 1.996 1.379 3.658 2.87 4.468 1.061 0.754 1.061 1.116 1.061 1.532s-0 0.778-1.061 1.532zM2 15h12v1h-12zM2 0h12v1h-12z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 490 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>mustache2</title>
|
||||
<path d="M14.645 8.021c-0.211-0.064-0.439 0.018-0.561 0.201-0.327 0.491-0.734 0.761-1.146 0.761-0.408 0-0.793-0.271-0.982-0.689l-0.007-0.015c-0.229-0.509-0.466-1.035-0.815-1.462-0.442-0.542-0.992-0.817-1.634-0.817-0.817 0-1.544 0.394-2 1.002-0.456-0.608-1.183-1.002-2-1.002-0.642 0-1.192 0.275-1.634 0.817-0.348 0.427-0.586 0.953-0.815 1.462l-0.007 0.015c-0.189 0.419-0.574 0.689-0.982 0.689-0.412 0-0.819-0.27-1.146-0.761-0.122-0.183-0.35-0.265-0.561-0.201s-0.355 0.258-0.355 0.479c0 1.074 0.419 1.978 1.212 2.615 0.711 0.571 1.701 0.885 2.788 0.885 1.038 0 2.035-0.378 2.807-1.064 0.276-0.245 0.508-0.518 0.693-0.809 0.185 0.291 0.417 0.564 0.693 0.809 0.772 0.686 1.769 1.064 2.807 1.064 1.087 0 2.077-0.314 2.788-0.885 0.793-0.636 1.212-1.541 1.212-2.615 0-0.22-0.144-0.415-0.355-0.479zM6.143 10.189c-0.589 0.523-1.35 0.811-2.143 0.811-1.331 0-2.146-0.486-2.584-1.125 0.254 0.086 0.479 0.109 0.646 0.109 0.8-0 1.543-0.502 1.894-1.278l0.007-0.015c0.474-1.051 0.814-1.69 1.537-1.69 0.827 0 1.5 0.673 1.5 1.5 0 0.589-0.312 1.205-0.857 1.689zM11 11c-0.793 0-1.554-0.288-2.143-0.811-0.545-0.484-0.857-1.099-0.857-1.689 0-0.827 0.673-1.5 1.5-1.5 0.723 0 1.063 0.638 1.537 1.69l0.007 0.015c0.35 0.777 1.093 1.278 1.894 1.278 0.171 0 0.404-0.024 0.666-0.116-0.565 0.815-1.641 1.132-2.604 1.132z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -147,6 +147,7 @@ const {
|
|||
actor: pageActor,
|
||||
tag: pageTag,
|
||||
entity: pageEntity,
|
||||
stash: pageStash,
|
||||
} = pageProps;
|
||||
|
||||
const scenes = ref(pageProps.scenes);
|
||||
|
@ -229,6 +230,7 @@ async function search(options = {}) {
|
|||
...query,
|
||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||
stashId: pageStash?.id,
|
||||
e: entitySlug,
|
||||
scope: scope.value,
|
||||
page: currentPage.value, // client uses param rather than query pagination
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<template>
|
||||
<div class="tile">
|
||||
<div
|
||||
class="tile"
|
||||
:class="{
|
||||
unstashed: !favorited && pageStash && user && pageStash.id === user.primaryStash?.id
|
||||
}"
|
||||
>
|
||||
<div class="poster-container">
|
||||
<Link
|
||||
:href="`/scene/${scene.id}/${scene.slug}`"
|
||||
|
@ -112,6 +117,7 @@ const props = defineProps({
|
|||
|
||||
const pageContext = inject('pageContext');
|
||||
const user = pageContext.user;
|
||||
const pageStash = pageContext.pageProps.stash;
|
||||
|
||||
const favorited = ref(props.scene.stashes.some((sceneStash) => sceneStash.primary));
|
||||
|
||||
|
@ -125,10 +131,13 @@ async function stash() {
|
|||
}
|
||||
|
||||
async function unstash() {
|
||||
console.log('unstash!', user);
|
||||
|
||||
try {
|
||||
favorited.value = false;
|
||||
await del(`/stashes/${user.primaryStash.id}/scenes/${props.scene.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
favorited.value = true;
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +158,10 @@ async function unstash() {
|
|||
fill: var(--text-light);
|
||||
}
|
||||
}
|
||||
|
||||
&.unstashed {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-container {
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
host: '127.0.0.1',
|
||||
sqlPort: 9306,
|
||||
httpPort: 9308,
|
||||
forceSql: true,
|
||||
maxMatches: 2000, // high match count needed primarily for actor aggregations
|
||||
maxAggregateSize: 2000, // must be lower or equal to maxMatches
|
||||
maxQueryTime: 10000,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"@brillout/json-serializer": "^0.5.8",
|
||||
"@dicebear/collection": "^7.0.5",
|
||||
"@dicebear/core": "^7.0.5",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@resvg/resvg-js": "^2.6.0",
|
||||
|
@ -31,12 +32,15 @@
|
|||
"express-query-boolean": "^2.0.0",
|
||||
"express-session": "^1.18.0",
|
||||
"floating-vue": "^5.2.2",
|
||||
"ip-cidr": "^4.0.0",
|
||||
"knex": "^3.1.0",
|
||||
"manticoresearch": "^4.0.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"mathjs": "^12.2.1",
|
||||
"mitt": "^3.0.1",
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"object.omit": "^3.0.0",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.12",
|
||||
|
@ -54,6 +58,7 @@
|
|||
"@babel/cli": "^7.23.4",
|
||||
"@babel/core": "^7.23.6",
|
||||
"@babel/eslint-parser": "^7.23.3",
|
||||
"@babel/plugin-syntax-import-attributes": "^7.23.3",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.23.4",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@csstools/postcss-global-data": "^2.1.1",
|
||||
|
@ -2867,6 +2872,21 @@
|
|||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@faker-js/faker": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
|
||||
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=6.14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz",
|
||||
|
@ -4029,6 +4049,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -4527,6 +4555,11 @@
|
|||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
|
@ -6301,6 +6334,29 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
||||
"dependencies": {
|
||||
"jsbn": "1.1.0",
|
||||
"sprintf-js": "^1.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-cidr": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-cidr/-/ip-cidr-4.0.0.tgz",
|
||||
"integrity": "sha512-i1Jhb9sqm2+PuOHTfya3ekAUi+dadhgcEz+4FKKY1hXemocP4Xf7io8Xflc74/i2ejxe/5fp4z8z3BAsfAZ8sw==",
|
||||
"dependencies": {
|
||||
"ip-address": "^9.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
@ -6407,6 +6463,17 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
@ -6478,6 +6545,17 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"dependencies": {
|
||||
"isobject": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
|
@ -6590,6 +6668,14 @@
|
|||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
|
@ -6630,6 +6716,11 @@
|
|||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
|
@ -7069,6 +7160,47 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"dependencies": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/mysql/node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.4.tgz",
|
||||
|
@ -7267,6 +7399,17 @@
|
|||
"get-intrinsic": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/object.omit": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz",
|
||||
"integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==",
|
||||
"dependencies": {
|
||||
"is-extendable": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object.values": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
|
||||
|
@ -7765,6 +7908,11 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
@ -8498,6 +8646,19 @@
|
|||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
|
@ -11857,6 +12018,11 @@
|
|||
"integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@faker-js/faker": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
|
||||
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg=="
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz",
|
||||
|
@ -12657,6 +12823,11 @@
|
|||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -13006,6 +13177,11 @@
|
|||
"browserslist": "^4.22.2"
|
||||
}
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
|
@ -14317,6 +14493,23 @@
|
|||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
|
||||
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
|
||||
},
|
||||
"ip-address": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
||||
"requires": {
|
||||
"jsbn": "1.1.0",
|
||||
"sprintf-js": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"ip-cidr": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-cidr/-/ip-cidr-4.0.0.tgz",
|
||||
"integrity": "sha512-i1Jhb9sqm2+PuOHTfya3ekAUi+dadhgcEz+4FKKY1hXemocP4Xf7io8Xflc74/i2ejxe/5fp4z8z3BAsfAZ8sw==",
|
||||
"requires": {
|
||||
"ip-address": "^9.0.5"
|
||||
}
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
@ -14390,6 +14583,14 @@
|
|||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"requires": {
|
||||
"is-plain-object": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
@ -14434,6 +14635,14 @@
|
|||
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
|
@ -14510,6 +14719,11 @@
|
|||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
|
||||
},
|
||||
"jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
|
@ -14539,6 +14753,11 @@
|
|||
"argparse": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
|
@ -14860,6 +15079,46 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"requires": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.4.tgz",
|
||||
|
@ -15003,6 +15262,14 @@
|
|||
"get-intrinsic": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"object.omit": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz",
|
||||
"integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==",
|
||||
"requires": {
|
||||
"is-extendable": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"object.values": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
|
||||
|
@ -15338,6 +15605,11 @@
|
|||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
@ -15857,6 +16129,16 @@
|
|||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
|
||||
},
|
||||
"sqlstring": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="
|
||||
},
|
||||
"stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"@brillout/json-serializer": "^0.5.8",
|
||||
"@dicebear/collection": "^7.0.5",
|
||||
"@dicebear/core": "^7.0.5",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@resvg/resvg-js": "^2.6.0",
|
||||
|
@ -31,12 +32,15 @@
|
|||
"express-query-boolean": "^2.0.0",
|
||||
"express-session": "^1.18.0",
|
||||
"floating-vue": "^5.2.2",
|
||||
"ip-cidr": "^4.0.0",
|
||||
"knex": "^3.1.0",
|
||||
"manticoresearch": "^4.0.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"mathjs": "^12.2.1",
|
||||
"mitt": "^3.0.1",
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"object.omit": "^3.0.0",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.12",
|
||||
|
@ -55,6 +59,7 @@
|
|||
"@babel/cli": "^7.23.4",
|
||||
"@babel/core": "^7.23.6",
|
||||
"@babel/eslint-parser": "^7.23.3",
|
||||
"@babel/plugin-syntax-import-attributes": "^7.23.3",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.23.4",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@csstools/postcss-global-data": "^2.1.1",
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<div class="stash">
|
||||
<div class="header">
|
||||
<h2 class="title">
|
||||
<Icon
|
||||
v-if="stash.primary"
|
||||
icon="heart7"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="box"
|
||||
/>
|
||||
|
||||
{{ stash.name }}
|
||||
</h2>
|
||||
|
||||
<a
|
||||
:href="`/user/${stash.user.username}`"
|
||||
class="user nolink"
|
||||
>
|
||||
<img
|
||||
:src="stash.user.avatar"
|
||||
class="avatar"
|
||||
>{{ stash.user.username }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="scenes-container">
|
||||
<Scenes />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import Scenes from '#/components/scenes/scenes.vue';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const stash = pageContext.pageProps.stash;
|
||||
|
||||
console.log(stash);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stash {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
color: var(--text-light);
|
||||
background: var(--grey-dark-40);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
fill: var(--text-light);
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.scenes-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,51 @@
|
|||
import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */
|
||||
|
||||
import { fetchStashByUsernameAndSlug } from '#/src/stashes.js';
|
||||
import { fetchScenes } from '#/src/scenes.js';
|
||||
import { curateScenesQuery } from '#/src/web/scenes.js';
|
||||
import { HttpError } from '#/src/errors.js';
|
||||
|
||||
export async function onBeforeRender(pageContext) {
|
||||
try {
|
||||
const stash = await fetchStashByUsernameAndSlug(pageContext.routeParams.username, pageContext.routeParams.stashSlug, pageContext.user);
|
||||
|
||||
const stashScenes = await fetchScenes(await curateScenesQuery({
|
||||
...pageContext.urlQuery,
|
||||
scope: pageContext.routeParams.scope || 'latest',
|
||||
stashId: stash.id,
|
||||
}), {
|
||||
page: Number(pageContext.routeParams.page) || 1,
|
||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||
}, pageContext.user);
|
||||
|
||||
const {
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
total,
|
||||
limit,
|
||||
} = stashScenes;
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
title: `${stash.name} by ${stash.user.username}`,
|
||||
pageProps: {
|
||||
stash,
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
total,
|
||||
limit,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
throw render(error.httpCode, error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { match } from 'path-to-regexp';
|
||||
// import { resolveRoute } from 'vike/routing'; // eslint-disable-line import/extensions
|
||||
|
||||
const path = '/stash/:username/:stashSlug/:scope?/:page?';
|
||||
const urlMatch = match(path, { decode: decodeURIComponent });
|
||||
|
||||
export default (pageContext) => {
|
||||
const matched = urlMatch(pageContext.urlPathname);
|
||||
|
||||
if (matched) {
|
||||
return {
|
||||
routeParams: {
|
||||
username: matched.params.username,
|
||||
stashSlug: matched.params.stashSlug,
|
||||
scope: matched.params.scope || 'latest',
|
||||
page: matched.params.page || '1',
|
||||
path,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
25
src/auth.js
|
@ -8,7 +8,9 @@ import { shapes } from '@dicebear/collection';
|
|||
import { knexOwner as knex } from './knex.js';
|
||||
import { curateUser, fetchUser } from './users.js';
|
||||
import { HttpError } from './errors.js';
|
||||
import initLogger from './logger.js';
|
||||
|
||||
const logger = initLogger();
|
||||
const scrypt = util.promisify(crypto.scrypt);
|
||||
|
||||
async function verifyPassword(password, storedPassword) {
|
||||
|
@ -32,22 +34,21 @@ async function generateAvatar(user) {
|
|||
});
|
||||
|
||||
await fs.mkdir('media/avatars', { recursive: true });
|
||||
|
||||
await avatar.png().toFile(`media/avatars/${user.id}_${user.username}.png`);
|
||||
|
||||
logger.verbose(`Generated avatar for '${user.username}' (${user.id})`);
|
||||
}
|
||||
|
||||
export async function login(credentials) {
|
||||
export async function login(credentials, userIp) {
|
||||
if (!config.auth.login) {
|
||||
throw new HttpError('Logins are currently disabled', 405);
|
||||
}
|
||||
|
||||
const user = await fetchUser(credentials.username.trim(), {
|
||||
const { user, stashes } = await fetchUser(credentials.username.trim(), {
|
||||
email: true,
|
||||
raw: true,
|
||||
});
|
||||
|
||||
console.log('login user', user);
|
||||
|
||||
if (!user) {
|
||||
throw new HttpError('Username or password incorrect', 401);
|
||||
}
|
||||
|
@ -58,15 +59,21 @@ export async function login(credentials) {
|
|||
.update('last_login', 'NOW()')
|
||||
.where('id', user.id);
|
||||
|
||||
if (!user.avatar) {
|
||||
console.log('login user', user);
|
||||
|
||||
logger.verbose(`Login from '${user.username}' (${user.id}, ${userIp})`);
|
||||
|
||||
try {
|
||||
await fs.access(`media/avatars/${user.id}_${user.username}.png`);
|
||||
} catch (error) {
|
||||
await generateAvatar(user);
|
||||
}
|
||||
|
||||
// fetched the raw user for password verification, don't return directly to user
|
||||
return curateUser(user);
|
||||
return curateUser(user, { stashes });
|
||||
}
|
||||
|
||||
export async function signup(credentials) {
|
||||
export async function signup(credentials, userIp) {
|
||||
if (!config.auth.signup) {
|
||||
throw new HttpError('Sign-ups are currently disabled', 405);
|
||||
}
|
||||
|
@ -126,6 +133,8 @@ export async function signup(credentials) {
|
|||
primary: true,
|
||||
});
|
||||
|
||||
logger.verbose(`Signup from '${curatedUsername}' (${userId}, ${credentials.email}, ${userIp})`);
|
||||
|
||||
await generateAvatar({
|
||||
id: userId,
|
||||
username: curatedUsername,
|
||||
|
|
13
src/knex.js
|
@ -19,4 +19,17 @@ export const knexOwner = knex({
|
|||
// debug: process.env.NODE_ENV === 'development',
|
||||
});
|
||||
|
||||
export const knexManticore = knex({
|
||||
client: 'mysql',
|
||||
connection: {
|
||||
host: config.database.manticore.host,
|
||||
port: config.database.manticore.sqlPort,
|
||||
database: 'Manticore',
|
||||
},
|
||||
asyncStackTraces: process.env.NODE_ENV === 'development',
|
||||
wrapIdentifier(value, _original, _queryContext) {
|
||||
return value;
|
||||
},
|
||||
});
|
||||
|
||||
export default knexQuery;
|
||||
|
|
|
@ -6,3 +6,5 @@ const mantiClient = new manticore.ApiClient();
|
|||
mantiClient.basePath = `http://${config.database.manticore.host}:${config.database.manticore.httpPort}`;
|
||||
|
||||
export const searchApi = new manticore.SearchApi(mantiClient);
|
||||
export const indexApi = new manticore.IndexApi(mantiClient);
|
||||
export const utilsApi = new manticore.UtilsApi();
|
||||
|
|
204
src/scenes.js
|
@ -1,7 +1,8 @@
|
|||
import config from 'config';
|
||||
import util from 'util'; /* eslint-disable-line no-unused-vars */
|
||||
|
||||
import { knexOwner as knex } from './knex.js';
|
||||
import { searchApi } from './manticore.js';
|
||||
import { knexOwner as knex, knexManticore } from './knex.js';
|
||||
import { searchApi, utilsApi } from './manticore.js';
|
||||
import { HttpError } from './errors.js';
|
||||
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
|
||||
import { fetchTagsById } from './tags.js';
|
||||
|
@ -151,6 +152,8 @@ export async function fetchScenesById(sceneIds, reqUser) {
|
|||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
const sqlImplied = ['scenes_stashed'];
|
||||
|
||||
function curateOptions(options) {
|
||||
if (options?.limit > 100) {
|
||||
throw new HttpError('Limit must be <= 100', 400);
|
||||
|
@ -163,10 +166,12 @@ function curateOptions(options) {
|
|||
aggregateActors: (options.aggregate ?? true) && (options.aggregateActors ?? true),
|
||||
aggregateTags: (options.aggregate ?? true) && (options.aggregateTags ?? true),
|
||||
aggregateChannels: (options.aggregate ?? true) && (options.aggregateChannels ?? true),
|
||||
index: options.index || 'scenes',
|
||||
useSql: options.useSql || (typeof options.useSql === 'undefined' && sqlImplied.includes(options.index)) || false,
|
||||
};
|
||||
}
|
||||
|
||||
function buildQuery(filters = {}) {
|
||||
function buildQuery(filters = {}, options) {
|
||||
const query = {
|
||||
bool: {
|
||||
must: [],
|
||||
|
@ -210,6 +215,7 @@ function buildQuery(filters = {}) {
|
|||
}
|
||||
|
||||
if (filters.query) {
|
||||
/*
|
||||
query.bool.must.push({
|
||||
bool: {
|
||||
should: [
|
||||
|
@ -224,6 +230,9 @@ function buildQuery(filters = {}) {
|
|||
],
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
query.bool.must.push({ match: { '!title': filters.query } }); // title_filtered is matched instead of title
|
||||
}
|
||||
|
||||
if (filters.tagIds) {
|
||||
|
@ -249,6 +258,10 @@ function buildQuery(filters = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
if (filters.stashId && options.index === 'scenes_stashed') {
|
||||
query.bool.must.push({ equals: { stash_id: filters.stashId } });
|
||||
}
|
||||
|
||||
/* tag filter
|
||||
must_not: [
|
||||
{
|
||||
|
@ -281,6 +294,7 @@ function buildAggregates(options) {
|
|||
field: 'tag_ids',
|
||||
size: config.database.manticore.maxAggregateSize,
|
||||
},
|
||||
sort: [{ 'count(*)': { order: 'desc' } }],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -290,6 +304,7 @@ function buildAggregates(options) {
|
|||
field: 'channel_id',
|
||||
size: config.database.manticore.maxAggregateSize,
|
||||
},
|
||||
sort: [{ 'count(*)': { order: 'desc' } }],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -304,20 +319,11 @@ function countAggregations(buckets) {
|
|||
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
}
|
||||
|
||||
export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||
const options = curateOptions(rawOptions);
|
||||
const { query, sort } = buildQuery(filters);
|
||||
|
||||
console.log('filters', filters);
|
||||
console.log('options', options);
|
||||
console.log('query', query.bool.must);
|
||||
|
||||
console.log('request user', reqUser);
|
||||
|
||||
console.time('manticore');
|
||||
async function queryManticoreJson(filters, options, _reqUser) {
|
||||
const { query, sort } = buildQuery(filters, options);
|
||||
|
||||
const result = await searchApi.search({
|
||||
index: 'scenes',
|
||||
index: options.index,
|
||||
query,
|
||||
limit: options.limit,
|
||||
offset: (options.page - 1) * options.limit,
|
||||
|
@ -339,31 +345,181 @@ export async function fetchScenes(filters, rawOptions, reqUser) {
|
|||
},
|
||||
});
|
||||
|
||||
console.timeEnd('manticore');
|
||||
const scenes = result.hits.hits.map((hit) => ({
|
||||
id: hit._id,
|
||||
...hit._source,
|
||||
_score: hit._score,
|
||||
}));
|
||||
|
||||
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets);
|
||||
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds?.buckets);
|
||||
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds?.buckets);
|
||||
return {
|
||||
scenes,
|
||||
total: result.hits.total,
|
||||
aggregations: result.aggregations && Object.fromEntries(Object.entries(result.aggregations).map(([key, { buckets }]) => [key, buckets])),
|
||||
};
|
||||
}
|
||||
|
||||
async function queryManticoreSql(filters, options, _reqUser) {
|
||||
const aggSize = 10 || config.database.manticore.maxAggregateSize;
|
||||
|
||||
const sqlQuery = knexManticore.raw(`
|
||||
:query:
|
||||
OPTION field_weights=(
|
||||
title_filtered=7,
|
||||
actors=10,
|
||||
tags=9,
|
||||
meta=6,
|
||||
channel_name=2,
|
||||
channel_slug=3,
|
||||
network_name=1,
|
||||
network_slug=1
|
||||
),
|
||||
max_matches=:maxMatches:,
|
||||
max_query_time=:maxQueryTime:
|
||||
:actorsFacet:
|
||||
:tagsFacet:
|
||||
:channelsFacet:
|
||||
`, {
|
||||
query: knexManticore('scenes')
|
||||
.select(knex.raw('*, weight() as _score'))
|
||||
.modify((builder) => {
|
||||
if (filters.stashId) {
|
||||
builder
|
||||
.innerJoin('scenes_stashed', 'scenes.id', 'scenes_stashed.scene_id')
|
||||
.where('scenes_stashed.stash_id', filters.stashId);
|
||||
}
|
||||
|
||||
if (filters.query) {
|
||||
builder.whereRaw('match(\'@!title :query:\', scenes)', { query: filters.query });
|
||||
}
|
||||
|
||||
if (filters.tagIds?.length > 0) {
|
||||
builder.whereIn('any(tag_ids)', filters.tagIds);
|
||||
}
|
||||
|
||||
if (filters.entityId) {
|
||||
builder.where((whereBuilder) => {
|
||||
whereBuilder
|
||||
.where('channel_id', filters.entityId)
|
||||
.orWhere('network_id', filters.entityId);
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.actorIds?.length > 0) {
|
||||
builder.whereIn('any(actor_ids)', filters.actorIds);
|
||||
}
|
||||
|
||||
if (!filters.scope || filters.scope === 'latest') {
|
||||
builder
|
||||
.where('effective_date', '<=', Math.round(Date.now() / 1000))
|
||||
.orderBy('effective_date', 'desc');
|
||||
} else if (filters.scope === 'upcoming') {
|
||||
builder
|
||||
.where('effective_date', '>', Math.round(Date.now() / 1000))
|
||||
.orderBy('effective_date', 'asc');
|
||||
} else if (filters.scope === 'new') {
|
||||
builder.orderBy([
|
||||
{ column: 'created_at', order: 'desc' },
|
||||
{ column: 'effective_date', order: 'asc' },
|
||||
]);
|
||||
} else if (filters.scope === 'likes') {
|
||||
builder.orderBy([
|
||||
{ column: 'stashed', order: 'desc' },
|
||||
{ column: 'effective_date', order: 'desc' },
|
||||
]);
|
||||
} else if (filters.scope === 'results') {
|
||||
builder.orderBy([
|
||||
{ column: '_score', order: 'desc' },
|
||||
{ column: 'effective_date', order: 'desc' },
|
||||
]);
|
||||
} else {
|
||||
builder.orderBy('effective_date', 'desc');
|
||||
}
|
||||
})
|
||||
.limit(options.limit)
|
||||
.toString(),
|
||||
// option threads=1 fixes actors, but drastically slows down performance, wait for fix
|
||||
actorsFacet: options.aggregateActors ? knex.raw('facet actor_ids order by count(*) desc limit ?', [aggSize]) : null,
|
||||
tagsFacet: options.aggregateTags ? knex.raw('facet tag_ids order by count(*) desc limit ?', [aggSize]) : null,
|
||||
channelsFacet: options.aggregateChannels ? knex.raw('facet channel_id order by count(*) desc limit ?', [aggSize]) : null,
|
||||
maxMatches: config.database.manticore.maxMatches,
|
||||
maxQueryTime: config.database.manticore.maxQueryTime,
|
||||
}).toString();
|
||||
|
||||
console.log(sqlQuery);
|
||||
|
||||
const results = await utilsApi.sql(sqlQuery);
|
||||
|
||||
const actorIds = results
|
||||
.find((result) => result.columns[0].actor_ids && result.columns[1]['count(*)'])
|
||||
?.data.map((row) => ({ key: row.actor_ids, doc_count: row['count(*)'] }))
|
||||
|| [];
|
||||
|
||||
const tagIds = results
|
||||
.find((result) => result.columns[0].tag_ids && result.columns[1]['count(*)'])
|
||||
?.data.map((row) => ({ key: row.tag_ids, doc_count: row['count(*)'] }))
|
||||
|| [];
|
||||
|
||||
const channelIds = results
|
||||
.find((result) => result.columns[0].channel_id && result.columns[1]['count(*)'])
|
||||
?.data.map((row) => ({ key: row.channel_id, doc_count: row['count(*)'] }))
|
||||
|| [];
|
||||
|
||||
return {
|
||||
scenes: results[0].data,
|
||||
total: results[0].total,
|
||||
aggregations: {
|
||||
actorIds,
|
||||
tagIds,
|
||||
channelIds,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||
const options = curateOptions(rawOptions);
|
||||
|
||||
console.log('filters', filters);
|
||||
console.log('options', options);
|
||||
|
||||
/*
|
||||
const result = config.database.manticore.forceSql || filters.stashId
|
||||
? await queryManticoreSql(filters, options, reqUser)
|
||||
: await queryManticoreJson(filters, options, reqUser);
|
||||
*/
|
||||
|
||||
console.time('manticore sql');
|
||||
const result = await queryManticoreSql(filters, options, reqUser);
|
||||
console.timeEnd('manticore sql');
|
||||
|
||||
console.time('manticore json');
|
||||
await queryManticoreJson(filters, options, reqUser);
|
||||
console.timeEnd('manticore json');
|
||||
|
||||
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds);
|
||||
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds);
|
||||
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds);
|
||||
|
||||
console.time('fetch aggregations');
|
||||
|
||||
const [aggActors, aggTags, aggChannels] = await Promise.all([
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: actorCounts }) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: tagCounts }) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(result.aggregations.channelIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: channelCounts }) : [],
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.map((bucket) => bucket.key), { order: ['name', 'asc'], append: actorCounts }) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.map((bucket) => bucket.key), { order: ['name', 'asc'], append: tagCounts }) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(result.aggregations.channelIds.map((bucket) => bucket.key), { order: ['name', 'asc'], append: channelCounts }) : [],
|
||||
]);
|
||||
|
||||
console.timeEnd('fetch aggregations');
|
||||
|
||||
const sceneIds = result.hits.hits.map((hit) => Number(hit._id));
|
||||
console.time('fetch full');
|
||||
const sceneIds = result.scenes.map((scene) => Number(scene.id));
|
||||
const scenes = await fetchScenesById(sceneIds, reqUser);
|
||||
console.timeEnd('fetch full');
|
||||
|
||||
return {
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
total: result.hits.total,
|
||||
total: result.total,
|
||||
limit: options.limit,
|
||||
};
|
||||
}
|
||||
|
|
110
src/stashes.js
|
@ -1,6 +1,7 @@
|
|||
import config from 'config';
|
||||
|
||||
import { knexOwner as knex } from './knex.js';
|
||||
import { indexApi } from './manticore.js';
|
||||
import { HttpError } from './errors.js';
|
||||
import slugify from './utils/slugify.js';
|
||||
import initLogger from './logger.js';
|
||||
|
@ -9,7 +10,7 @@ const logger = initLogger();
|
|||
|
||||
let lastActorsViewRefresh = 0;
|
||||
|
||||
export function curateStash(stash) {
|
||||
export function curateStash(stash, assets = {}) {
|
||||
if (!stash) {
|
||||
return null;
|
||||
}
|
||||
|
@ -24,6 +25,12 @@ export function curateStash(stash) {
|
|||
stashedScenes: stash.stashed_scenes || null,
|
||||
stashedMovies: stash.stashed_movies || null,
|
||||
stashedActors: stash.stashed_actors || null,
|
||||
user: assets.user ? {
|
||||
id: assets.user.id,
|
||||
username: assets.user.username,
|
||||
avatar: `/media/avatars/${assets.user.id}_${assets.user.username}.png`,
|
||||
createdAt: assets.user.created_at,
|
||||
} : null,
|
||||
};
|
||||
|
||||
return curatedStash;
|
||||
|
@ -40,23 +47,39 @@ function curateStashEntry(stash, user) {
|
|||
return curatedStashEntry;
|
||||
}
|
||||
|
||||
export async function fetchStash(stashId, sessionUser) {
|
||||
if (!sessionUser) {
|
||||
throw new HttpError('You are not authenthicated', 401);
|
||||
function verifyStashAccess(stash, sessionUser) {
|
||||
if (!stash || (!stash.public && stash.user_id !== sessionUser?.id)) {
|
||||
throw new HttpError('This stash does not exist, or you are not allowed access.', 404);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchStashById(stashId, sessionUser) {
|
||||
const stash = await knex('stashes')
|
||||
.where('id', stashId)
|
||||
.first();
|
||||
|
||||
verifyStashAccess(stash, sessionUser);
|
||||
|
||||
return curateStash(stash);
|
||||
}
|
||||
|
||||
export async function fetchStashByUsernameAndSlug(username, stashSlug, sessionUser) {
|
||||
const user = await knex('users').where('username', username).first();
|
||||
|
||||
if (!user) {
|
||||
throw new HttpError('This user does not exist.', 404);
|
||||
}
|
||||
|
||||
const stash = await knex('stashes')
|
||||
.where({
|
||||
id: stashId,
|
||||
user_id: sessionUser.id,
|
||||
})
|
||||
.select('stashes.*', 'stashes_meta.*')
|
||||
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id')
|
||||
.where('slug', stashSlug)
|
||||
.where('user_id', user.id)
|
||||
.first();
|
||||
|
||||
if (!stash) {
|
||||
throw new HttpError('You are not authorized to access this stash', 403);
|
||||
}
|
||||
verifyStashAccess(stash, sessionUser);
|
||||
|
||||
return curateStash(stash);
|
||||
return curateStash(stash, { user });
|
||||
}
|
||||
|
||||
export async function fetchStashes(domain, itemId, sessionUser) {
|
||||
|
@ -145,7 +168,7 @@ export async function refreshActorsView() {
|
|||
}
|
||||
|
||||
export async function stashActor(actorId, stashId, sessionUser) {
|
||||
const stash = await fetchStash(stashId, sessionUser);
|
||||
const stash = await fetchStashById(stashId, sessionUser);
|
||||
|
||||
await knex('stashes_actors')
|
||||
.insert({
|
||||
|
@ -158,30 +181,6 @@ export async function stashActor(actorId, stashId, sessionUser) {
|
|||
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')
|
||||
|
@ -198,6 +197,18 @@ export async function unstashActor(actorId, stashId, sessionUser) {
|
|||
return fetchStashes('actor', actorId, sessionUser);
|
||||
}
|
||||
|
||||
export async function stashScene(sceneId, stashId, sessionUser) {
|
||||
const stash = await fetchStashById(stashId, sessionUser);
|
||||
|
||||
await knex('stashes_scenes')
|
||||
.insert({
|
||||
stash_id: stash.id,
|
||||
scene_id: sceneId,
|
||||
});
|
||||
|
||||
return fetchStashes('scene', sceneId, sessionUser);
|
||||
}
|
||||
|
||||
export async function unstashScene(sceneId, stashId, sessionUser) {
|
||||
await knex
|
||||
.from('stashes_scenes AS deletable')
|
||||
|
@ -209,9 +220,34 @@ export async function unstashScene(sceneId, stashId, sessionUser) {
|
|||
.where('stashes.user_id', sessionUser.id))
|
||||
.delete();
|
||||
|
||||
await indexApi.callDelete({
|
||||
index: 'scenes_stashed',
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ equals: { id: sceneId } },
|
||||
{ equals: { stash_id: stashId } },
|
||||
{ equals: { user_id: sessionUser.id } },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return fetchStashes('scene', sceneId, sessionUser);
|
||||
}
|
||||
|
||||
export async function stashMovie(movieId, stashId, sessionUser) {
|
||||
const stash = await fetchStashById(stashId, sessionUser);
|
||||
|
||||
await knex('stashes_movies')
|
||||
.insert({
|
||||
stash_id: stash.id,
|
||||
movie_id: movieId,
|
||||
});
|
||||
|
||||
return fetchStashes('movie', movieId, sessionUser);
|
||||
}
|
||||
|
||||
export async function unstashMovie(movieId, stashId, sessionUser) {
|
||||
await knex
|
||||
.from('stashes_movies AS deletable')
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { indexApi, utilsApi } from '../manticore.js';
|
||||
import rawMovies from './movies.json' with { type: 'json' };
|
||||
|
||||
async function fetchMovies() {
|
||||
const movies = rawMovies
|
||||
.filter((movie) => movie.cast.length > 0
|
||||
&& movie.genres.length > 0
|
||||
&& movie.cast.every((actor) => actor.charCodeAt(0) >= 65)) // throw out movies with non-alphanumerical actor names
|
||||
.map((movie, index) => ({ id: index, ...movie }));
|
||||
|
||||
const actors = Array.from(new Set(movies.flatMap((movie) => movie.cast))).sort();
|
||||
const genres = Array.from(new Set(movies.flatMap((movie) => movie.genres)));
|
||||
|
||||
return {
|
||||
movies,
|
||||
actors,
|
||||
genres,
|
||||
};
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await utilsApi.sql('drop table if exists movies');
|
||||
await utilsApi.sql('drop table if exists movies_liked');
|
||||
|
||||
await utilsApi.sql(`create table movies (
|
||||
id int,
|
||||
title text,
|
||||
actor_ids multi,
|
||||
actors text,
|
||||
genre_ids multi,
|
||||
genres text
|
||||
)`);
|
||||
|
||||
await utilsApi.sql(`create table movies_liked (
|
||||
id int,
|
||||
user_id int,
|
||||
movie_id int
|
||||
)`);
|
||||
|
||||
const { movies, actors, genres } = await fetchMovies();
|
||||
|
||||
const likedMovieIds = Array.from(new Set(Array.from({ length: 10.000 }, () => movies[Math.round(Math.random() * movies.length)].id)));
|
||||
|
||||
const docs = movies
|
||||
.map((movie) => ({
|
||||
replace: {
|
||||
index: 'movies',
|
||||
id: movie.id,
|
||||
doc: {
|
||||
title: movie.title,
|
||||
actor_ids: movie.cast.map((actor) => actors.indexOf(actor)),
|
||||
actors: movie.cast.join(','),
|
||||
genre_ids: movie.genres.map((genre) => genres.indexOf(genre)),
|
||||
genres: movie.genres.join(','),
|
||||
},
|
||||
},
|
||||
}))
|
||||
.concat(likedMovieIds.map((movieId, index) => ({
|
||||
replace: {
|
||||
index: 'movies_liked',
|
||||
id: index + 1,
|
||||
doc: {
|
||||
user_id: Math.floor(Math.random() * 51),
|
||||
movie_id: movieId,
|
||||
},
|
||||
},
|
||||
})));
|
||||
|
||||
const data = await indexApi.bulk(docs.map((doc) => JSON.stringify(doc)).join('\n'));
|
||||
|
||||
console.log('data', data);
|
||||
|
||||
const result = await utilsApi.sql(`
|
||||
select * from movies_liked
|
||||
limit 10
|
||||
`);
|
||||
|
||||
console.log(result[0].data);
|
||||
console.log(result[1]);
|
||||
}
|
||||
|
||||
init();
|
|
@ -0,0 +1,203 @@
|
|||
// import config from 'config';
|
||||
import { format } from 'date-fns';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { indexApi, utilsApi } from '../manticore.js';
|
||||
|
||||
import { knexOwner as knex } from '../knex.js';
|
||||
import slugify from '../utils/slugify.js';
|
||||
import chunk from '../utils/chunk.js';
|
||||
|
||||
async function fetchScenes() {
|
||||
const scenes = await knex.raw(`
|
||||
SELECT
|
||||
releases.id AS id,
|
||||
releases.title,
|
||||
releases.created_at,
|
||||
releases.date,
|
||||
releases.shoot_id,
|
||||
scenes_meta.stashed,
|
||||
entities.id as channel_id,
|
||||
entities.slug as channel_slug,
|
||||
entities.name as channel_name,
|
||||
parents.id as network_id,
|
||||
parents.slug as network_slug,
|
||||
parents.name as network_name,
|
||||
COALESCE(JSON_AGG(DISTINCT (actors.id, actors.name)) FILTER (WHERE actors.id IS NOT NULL), '[]') as actors,
|
||||
COALESCE(JSON_AGG(DISTINCT (tags.id, tags.name, tags.priority, tags_aliases.name)) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags
|
||||
FROM releases
|
||||
LEFT JOIN scenes_meta ON scenes_meta.scene_id = releases.id
|
||||
LEFT JOIN entities ON releases.entity_id = entities.id
|
||||
LEFT JOIN entities AS parents ON parents.id = entities.parent_id
|
||||
LEFT JOIN releases_actors AS local_actors ON local_actors.release_id = releases.id
|
||||
LEFT JOIN releases_directors AS local_directors ON local_directors.release_id = releases.id
|
||||
LEFT JOIN releases_tags AS local_tags ON local_tags.release_id = releases.id
|
||||
LEFT JOIN actors ON local_actors.actor_id = actors.id
|
||||
LEFT JOIN actors AS directors ON local_directors.director_id = directors.id
|
||||
LEFT JOIN tags ON local_tags.tag_id = tags.id
|
||||
LEFT JOIN tags as tags_aliases ON local_tags.tag_id = tags_aliases.alias_for AND tags_aliases.secondary = true
|
||||
GROUP BY
|
||||
releases.id,
|
||||
releases.title,
|
||||
releases.created_at,
|
||||
releases.date,
|
||||
releases.shoot_id,
|
||||
scenes_meta.stashed,
|
||||
entities.id,
|
||||
entities.name,
|
||||
entities.slug,
|
||||
entities.alias,
|
||||
parents.id,
|
||||
parents.name,
|
||||
parents.slug,
|
||||
parents.alias;
|
||||
`);
|
||||
|
||||
const actors = Object.fromEntries(scenes.rows.flatMap((row) => row.actors.map((actor) => [actor.f1, faker.person.fullName()])));
|
||||
const tags = Object.fromEntries(scenes.rows.flatMap((row) => row.tags.map((tag) => [tag.f1, faker.word.adjective()])));
|
||||
|
||||
return scenes.rows.map((row) => {
|
||||
const title = faker.lorem.lines(1);
|
||||
|
||||
const channelName = faker.company.name();
|
||||
const channelSlug = slugify(channelName, '');
|
||||
|
||||
const networkName = faker.company.name();
|
||||
const networkSlug = slugify(networkName, '');
|
||||
|
||||
const rowActors = row.actors.map((actor) => ({ f1: actor.f1, f2: actors[actor.f1] }));
|
||||
const rowTags = row.tags.map((tag) => ({ f1: tag.f1, f2: tags[tag.f1], f3: tag.f3 }));
|
||||
|
||||
return {
|
||||
...row,
|
||||
title,
|
||||
actors: rowActors,
|
||||
tags: rowTags,
|
||||
channel_name: channelName,
|
||||
channel_slug: channelSlug,
|
||||
network_name: networkName,
|
||||
network_slug: networkSlug,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function updateStashed(docs) {
|
||||
await chunk(docs, 1000).reduce(async (chain, docsChunk) => {
|
||||
await chain;
|
||||
|
||||
const sceneIds = docsChunk.map((doc) => doc.replace.id);
|
||||
|
||||
const stashes = await knex('stashes_scenes')
|
||||
.select('stashes_scenes.id as stashed_id', 'stashes_scenes.scene_id', 'stashes.id as stash_id', 'stashes.user_id as user_id')
|
||||
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
|
||||
.whereIn('scene_id', sceneIds);
|
||||
|
||||
if (stashes.length > 0) {
|
||||
console.log(stashes);
|
||||
}
|
||||
|
||||
const stashDocs = docsChunk.flatMap((doc) => {
|
||||
const sceneStashes = stashes.filter((stash) => stash.scene_id === doc.replace.id);
|
||||
|
||||
if (sceneStashes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const stashDoc = sceneStashes.map((stash) => ({
|
||||
replace: {
|
||||
index: 'movies_liked',
|
||||
id: stash.stashed_id,
|
||||
doc: {
|
||||
// ...doc.replace.doc,
|
||||
movie_id: doc.replace.id,
|
||||
user_id: stash.user_id,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
return stashDoc;
|
||||
});
|
||||
|
||||
console.log(stashDocs);
|
||||
|
||||
if (stashDocs.length > 0) {
|
||||
await indexApi.bulk(stashDocs.map((doc) => JSON.stringify(doc)).join('\n'));
|
||||
}
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await utilsApi.sql('drop table if exists movies');
|
||||
await utilsApi.sql('drop table if exists movies_liked');
|
||||
|
||||
await utilsApi.sql(`create table movies (
|
||||
id int,
|
||||
title text,
|
||||
title_filtered text,
|
||||
channel_id int,
|
||||
channel_name text,
|
||||
channel_slug text,
|
||||
network_id int,
|
||||
network_name text,
|
||||
network_slug text,
|
||||
actor_ids multi,
|
||||
actors text,
|
||||
tag_ids multi,
|
||||
tags text,
|
||||
meta text,
|
||||
date timestamp,
|
||||
created_at timestamp,
|
||||
effective_date timestamp,
|
||||
liked int
|
||||
)`);
|
||||
|
||||
await utilsApi.sql(`create table movies_liked (
|
||||
movie_id int,
|
||||
user_id int
|
||||
)`);
|
||||
|
||||
const scenes = await fetchScenes();
|
||||
|
||||
const docs = scenes.map((scene) => {
|
||||
const flatActors = scene.actors.flatMap((actor) => actor.f2.match(/[\w']+/g)); // match word characters to filter out brackets etc.
|
||||
const flatTags = scene.tags.filter((tag) => tag.f3 > 6).flatMap((tag) => (tag.f4 ? `${tag.f2} ${tag.f4}` : tag.f2).match(/[\w']+/g)); // only make top tags searchable to minimize cluttered results
|
||||
const filteredTitle = scene.title && [...flatActors, ...flatTags].reduce((accTitle, tag) => accTitle.replace(new RegExp(tag.replace(/[^\w\s]+/g, ''), 'i'), ''), scene.title).trim().replace(/\s{2,}/, ' ');
|
||||
|
||||
return {
|
||||
replace: {
|
||||
index: 'movies',
|
||||
id: scene.id,
|
||||
doc: {
|
||||
title: scene.title || undefined,
|
||||
title_filtered: filteredTitle || undefined,
|
||||
date: scene.date ? Math.round(scene.date.getTime() / 1000) : undefined,
|
||||
created_at: Math.round(scene.created_at.getTime() / 1000),
|
||||
effective_date: Math.round((scene.date || scene.created_at).getTime() / 1000),
|
||||
// shoot_id: scene.shoot_id || undefined,
|
||||
channel_id: scene.channel_id,
|
||||
channel_slug: scene.channel_slug,
|
||||
channel_name: scene.channel_name,
|
||||
network_id: scene.network_id || undefined,
|
||||
network_slug: scene.network_slug || undefined,
|
||||
network_name: scene.network_name || undefined,
|
||||
actor_ids: scene.actors.map((actor) => actor.f1),
|
||||
actors: scene.actors.map((actor) => actor.f2).join(),
|
||||
tag_ids: scene.tags.map((tag) => tag.f1),
|
||||
tags: flatTags.join(' '),
|
||||
meta: scene.date ? format(scene.date, 'y yy M MMM MMMM d') : undefined,
|
||||
liked: scene.stashed || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const data = await indexApi.bulk(docs.map((doc) => JSON.stringify(doc)).join('\n'));
|
||||
|
||||
await updateStashed(docs);
|
||||
|
||||
console.log('data', data);
|
||||
|
||||
knex.destroy();
|
||||
}
|
||||
|
||||
init();
|
10
src/users.js
|
@ -46,17 +46,17 @@ export async function fetchUser(userId, options = {}) {
|
|||
.groupBy('users.id', 'users_roles.role')
|
||||
.first();
|
||||
|
||||
const stashes = await knex('stashes')
|
||||
.where('user_id', user.id)
|
||||
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id');
|
||||
|
||||
if (!user) {
|
||||
throw HttpError(`User '${userId}' not found`, 404);
|
||||
}
|
||||
|
||||
if (options.raw) {
|
||||
return user;
|
||||
return { user, stashes };
|
||||
}
|
||||
|
||||
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,21 @@
|
|||
export function curateUser(user, assets = {}) {
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const curatedStashes = assets.stashes?.filter(Boolean).map((stash) => curateStash(stash)) || [];
|
||||
|
||||
const curatedUser = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
emailVerified: user.email_verified,
|
||||
identityVerified: user.identity_verified,
|
||||
avatar: `/media/avatars/${user.id}_${user.username}.png`,
|
||||
createdAt: user.created_at,
|
||||
stashes: curatedStashes,
|
||||
primaryStash: curatedStashes.find((stash) => stash.primary),
|
||||
};
|
||||
|
||||
return curatedUser;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export default function chunk(array, chunkSize = 1000) {
|
||||
return Array.from({ length: Math.ceil(array.length / chunkSize) })
|
||||
.map((value, index) => array.slice(index * chunkSize, (index * chunkSize) + chunkSize));
|
||||
}
|
|
@ -1,16 +1,42 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
import IPCIDR from 'ip-cidr';
|
||||
|
||||
import { login, signup } from '../auth.js';
|
||||
|
||||
function getIp(req) {
|
||||
const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress; // See src/ws
|
||||
|
||||
const unmappedIp = ip?.includes('.')
|
||||
? ip.slice(ip.lastIndexOf(':') + 1)
|
||||
: ip;
|
||||
|
||||
// ensure IP is in expanded notation for consistency and matching
|
||||
const expandedIp = unmappedIp.includes(':')
|
||||
? new IPCIDR(`${ip}/128`) // IPv6
|
||||
: new IPCIDR(`${ip}/32`); // IPv4
|
||||
|
||||
if (!expandedIp.addressStart?.addressMinusSuffix) {
|
||||
throw new Error(`Could not determine user IP from ${ip}`);
|
||||
}
|
||||
|
||||
return expandedIp.addressStart?.addressMinusSuffix || null;
|
||||
}
|
||||
|
||||
export async function setUserApi(req, res, next) {
|
||||
const ip = getIp(req);
|
||||
|
||||
req.userIp = ip;
|
||||
|
||||
if (req.session.user) {
|
||||
req.user = req.session.user;
|
||||
req.user.ip = ip;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function loginApi(req, res) {
|
||||
const user = await login(req.body);
|
||||
const user = await login(req.body, req.userIp);
|
||||
|
||||
req.session.user = user;
|
||||
res.send(user);
|
||||
|
@ -27,7 +53,7 @@ export async function logoutApi(req, res) {
|
|||
}
|
||||
|
||||
export async function signupApi(req, res) {
|
||||
const user = await signup(req.body);
|
||||
const user = await signup(req.body, req.userIp);
|
||||
|
||||
req.session.user = user;
|
||||
res.send(user);
|
||||
|
|
|
@ -11,6 +11,7 @@ export async function curateScenesQuery(query) {
|
|||
actorIds: [query.actorId, ...(query.actors?.split(',') || []).map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
||||
tagIds: await getIdsBySlug([query.tagSlug, ...(query.tags?.split(',') || [])], 'tags'),
|
||||
entityId: query.e ? await getIdsBySlug([query.e], 'entities').then(([id]) => id) : query.entityId,
|
||||
stashId: Number(query.stashId),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,14 @@ export default async function initServer() {
|
|||
const pageContextInit = {
|
||||
urlOriginal: req.originalUrl,
|
||||
urlQuery: req.query, // vike's own query does not apply boolean parser
|
||||
user: req.user,
|
||||
user: req.user && {
|
||||
id: req.user.id,
|
||||
username: req.user.username,
|
||||
email: req.user.email,
|
||||
avatar: req.user.avatar,
|
||||
stashes: req.user.stashes,
|
||||
primaryStash: req.user.primaryStash,
|
||||
},
|
||||
env: {
|
||||
maxAggregateSize: config.database.manticore.maxAggregateSize,
|
||||
},
|
||||
|
|
|
@ -53,7 +53,7 @@ export async function unstashActorApi(req, res) {
|
|||
}
|
||||
|
||||
export async function unstashSceneApi(req, res) {
|
||||
const stashes = await unstashScene(req.params.sceneId, req.params.stashId, req.user);
|
||||
const stashes = await unstashScene(Number(req.params.sceneId), Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stashes);
|
||||
}
|
||||
|
|