Exposing stashes on scenes GraphQL object.

This commit is contained in:
DebaucheryLibrarian 2025-04-01 00:55:41 +02:00
parent acef14b02c
commit f5d8c30ff3
5 changed files with 206 additions and 93 deletions

View File

@ -21,78 +21,80 @@
</div> </div>
</div> </div>
<div <div class="manager">
v-if="newKey" <div
class="newkey" v-if="newKey"
> class="newkey"
<p class="key-info">Successfully generated key with identifier <strong class="newkey-identifier ellipsis">{{ newKey.identifier }}</strong>:</p>
<input
:value="newKey.key"
class="input ellipsis"
@click="copyKey"
> >
<p class="key-info">Successfully generated key with identifier <strong class="newkey-identifier ellipsis">{{ newKey.identifier }}</strong>:</p>
<p class="key-info">Please store this key securely, you will <strong>not</strong> be able to retrieve it later. If you lose it, you must generate a new key.</p> <input
</div> :value="newKey.key"
class="input ellipsis"
@click="copyKey"
>
<ul <p class="key-info">Please store this key securely, you will <strong>not</strong> be able to retrieve it later. If you lose it, you must generate a new key.</p>
v-if="keys.length > 0" </div>
class="keys nolist"
> <ul
<li v-if="keys.length > 0"
v-for="key in keys" class="keys nolist"
:key="`key-${key.id}`"
class="key"
> >
<div class="key-row key-header"> <li
<strong class="key-value key-identifier ellipsis">{{ key.identifier }}</strong> v-for="key in keys"
:key="`key-${key.id}`"
class="key"
>
<div class="key-row key-header">
<strong class="key-value key-identifier ellipsis">{{ key.identifier }}</strong>
<span class="key-actions"> <span class="key-actions">
<Icon <Icon
icon="bin" icon="bin"
class="key-remove" class="key-remove"
@click="removeKey(key)" @click="removeKey(key)"
/> />
</span> </span>
</div> </div>
<div class="key-row key-details"> <div class="key-row key-details">
<span class="key-value key-created"> <span class="key-value key-created">
<Icon icon="plus-circle" /> <Icon icon="plus-circle" />
<time
v-tooltip="`Created ${format(key.createdAt, 'yyyy-MM-dd hh:mm:ss')}`"
:datetime="key.createdAt.toISOString()"
>{{ formatDistanceStrict(key.createdAt, now) }} ago</time>
</span>
<span class="key-value key-used">
<Icon icon="history" />
<template v-if="key.lastUsedAt">
<time <time
v-tooltip="`Last used ${format(key.lastUsedAt, 'yyyy-MM-dd hh:mm:ss')} from IP ${key.lastUsedIp}`" v-tooltip="`Created ${format(key.createdAt, 'yyyy-MM-dd hh:mm:ss')}`"
:datetime="key.lastUsedAt.toISOString()" :datetime="key.createdAt.toISOString()"
>{{ formatDistanceStrict(key.lastUsedAt, now) }} ago</time> >{{ formatDistanceStrict(key.createdAt, now) }} ago</time>
</template> </span>
<template v-else>Never</template> <span class="key-value key-used">
</span> <Icon icon="history" />
</div>
</li>
</ul>
<div <template v-if="key.lastUsedAt">
v-if="keys.length > 0" <time
class="info" v-tooltip="`Last used ${format(key.lastUsedAt, 'yyyy-MM-dd hh:mm:ss')} from IP ${key.lastUsedIp}`"
> :datetime="key.lastUsedAt.toISOString()"
<h3 class="info-heading">HTTP headers</h3> >{{ formatDistanceStrict(key.lastUsedAt, now) }} ago</time>
</template>
<code class="headers"> <template v-else>Never</template>
API-User: {{ user.id }}<br> </span>
API-Key: YourSecurelyStoredApiKey12345678 </div>
</code> </li>
</ul>
<div
v-if="keys.length > 0"
class="info"
>
<h3 class="info-heading">HTTP headers</h3>
<code class="headers">
API-User: {{ user.id }}<br>
API-Key: YourSecurelyStoredApiKey12345678
</code>
</div>
</div> </div>
</section> </section>
</template> </template>
@ -151,18 +153,6 @@ function copyKey(event) {
</script> </script>
<style scoped> <style scoped>
.page {
display: flex;
flex-grow: 1;
justify-content: center;
}
.manager {
width: 1200px;
max-width: 100%;
box-sizing: border-box;
}
.keys-header { .keys-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -297,7 +287,17 @@ function copyKey(event) {
margin: 0; margin: 0;
} }
@media(--compact) {
.manager {
padding: 0 1rem;
}
}
@media(--small-20) { @media(--small-20) {
.manager {
padding: 0 .5rem;
}
.keys { .keys {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }

View File

@ -1,3 +1,5 @@
import Router from 'express-promise-router';
import { import {
fetchAlerts, fetchAlerts,
createAlert, createAlert,
@ -7,18 +9,124 @@ import {
updateNotification, updateNotification,
} from '../alerts.js'; } from '../alerts.js';
export const alertsSchema = `
extend type Query {
alerts: [Alert]
alert(
id: Int!
): Alert
}
extend type Mutation {
createAlert(
all: Boolean = true
allActors: Boolean = true
allTags: Boolean = true
allMatches: Boolean = true
actors: [Int!]
tags: [Int!]
entities: [Int!]
matches: [AlertMatchInput!]
notify: Boolean = true
email: Boolean = false
stashes: [Int!]
): Alert
}
type AlertAnd {
fields: Boolean
actors: Boolean
tags: Boolean
entities: Boolean
matches: Boolean
}
type AlertActor {
id: Int
name: String
slug: String
}
type AlertTag {
id: Int
name: String
slug: String
}
enum EntityType {
network
channel
studio
info
}
type AlertEntity {
id: Int
name: String
slug: String
type: EntityType
}
type AlertMatch {
id: Int
property: String
expression: String
}
input AlertMatchInput {
property: String!
expression: String!
}
type AlertStash {
id: Int
name: String
slug: String
isPrimary: Boolean
}
type Alert {
id: Int
notify: Boolean
email: Boolean
isFromPreset: Boolean
isPrimary: Boolean
createdAt: Date
and: AlertAnd
actors: [AlertActor]
tags: [AlertTag]
entities: [AlertEntity]
matches: [AlertMatch]
stashes: [AlertStash]
}
`;
export async function fetchAlertsApi(req, res) { export async function fetchAlertsApi(req, res) {
const alerts = await fetchAlerts(req.user); const alerts = await fetchAlerts(req.user);
res.send(alerts); res.send(alerts);
} }
export async function fetchAlertsGraphql(query, req) {
const alerts = await fetchAlerts(req.user);
return alerts;
}
export async function createAlertApi(req, res) { export async function createAlertApi(req, res) {
const alert = await createAlert(req.body, req.user); const alert = await createAlert(req.body, req.user);
res.send(alert); res.send(alert);
} }
export async function createAlertGraphql(query, req) {
console.log('CREATE ALERT', query);
const alert = await createAlert(query, req.user);
return alert;
}
export async function removeAlertApi(req, res) { export async function removeAlertApi(req, res) {
await Promise.all(req.params.alertId.split(',').map(async (alertId) => removeAlert(alertId, req.user))); await Promise.all(req.params.alertId.split(',').map(async (alertId) => removeAlert(alertId, req.user)));
@ -44,3 +152,13 @@ export async function updateNotificationApi(req, res) {
res.status(204).send(); res.status(204).send();
} }
export const router = Router();
router.get('/api/alerts', fetchAlertsApi);
router.post('/api/alerts', createAlertApi);
router.delete('/api/alerts/:alertId', removeAlertApi);
router.get('/api/users/:userId/notifications', fetchNotificationsApi);
router.patch('/api/users/:userId/notifications', updateNotificationsApi);
router.patch('/api/users/:userId/notifications/:notificationId', updateNotificationApi);

View File

@ -46,6 +46,12 @@ import {
unstashMovieGraphql, unstashMovieGraphql,
} from './stashes.js'; } from './stashes.js';
import {
alertsSchema,
fetchAlertsGraphql,
createAlertGraphql,
} from './alerts.js';
import { verifyKey } from '../auth.js'; import { verifyKey } from '../auth.js';
const schema = buildSchema(` const schema = buildSchema(`
@ -64,6 +70,7 @@ const schema = buildSchema(`
${actorsSchema} ${actorsSchema}
${entitiesSchema} ${entitiesSchema}
${stashesSchema} ${stashesSchema}
${alertsSchema}
`); `);
const DateTimeScalar = new GraphQLScalarType({ const DateTimeScalar = new GraphQLScalarType({
@ -125,7 +132,8 @@ export async function graphqlApi(req, res) {
entitiesById: async (query, args, info) => fetchEntitiesByIdGraphql(query, req, info), entitiesById: async (query, args, info) => fetchEntitiesByIdGraphql(query, req, info),
stashes: async (query) => fetchUserStashesGraphql(query, req), stashes: async (query) => fetchUserStashesGraphql(query, req),
stash: async (query) => fetchStashGraphql(query, req), stash: async (query) => fetchStashGraphql(query, req),
// mutation alerts: async (query) => fetchAlertsGraphql(query, req),
// stash mutation
createStash: async (query) => createStashGraphql(query, req), createStash: async (query) => createStashGraphql(query, req),
updateStash: async (query) => updateStashGraphql(query, req), updateStash: async (query) => updateStashGraphql(query, req),
removeStash: async (query) => removeStashGraphql(query, req), removeStash: async (query) => removeStashGraphql(query, req),
@ -135,10 +143,12 @@ export async function graphqlApi(req, res) {
unstashActor: async (query) => unstashActorGraphql(query, req), unstashActor: async (query) => unstashActorGraphql(query, req),
stashMovie: async (query) => stashMovieGraphql(query, req), stashMovie: async (query) => stashMovieGraphql(query, req),
unstashMovie: async (query) => unstashMovieGraphql(query, req), unstashMovie: async (query) => unstashMovieGraphql(query, req),
// alert mutation
createAlert: async (query) => createAlertGraphql(query, req),
}, },
}); });
const statusCode = data.errors?.[0]?.originalError.httpCode || 200; const statusCode = data.errors?.[0]?.originalError?.httpCode || 200;
res.status(statusCode).send(data); res.status(statusCode).send(data);
} }

View File

@ -132,6 +132,7 @@ export const scenesSchema = `
photos: [Media!]! photos: [Media!]!
covers: [Media!]! covers: [Media!]!
movies: [Release!]! movies: [Release!]!
stashes: [Stash!]
} }
type Tag { type Tag {

View File

@ -38,15 +38,7 @@ import {
import { router as userRouter } from './users.js'; import { router as userRouter } from './users.js';
import { router as stashesRouter } from './stashes.js'; import { router as stashesRouter } from './stashes.js';
import { router as alertsRouter } from './alerts.js';
import {
fetchAlertsApi,
createAlertApi,
removeAlertApi,
fetchNotificationsApi,
updateNotificationApi,
updateNotificationsApi,
} from './alerts.js';
import initLogger from '../logger.js'; import initLogger from '../logger.js';
@ -140,23 +132,15 @@ export default async function initServer() {
// USERS // USERS
router.post('/api/users', signupApi); router.post('/api/users', signupApi);
router.get('/api/users/:userId/notifications', fetchNotificationsApi);
router.patch('/api/users/:userId/notifications', updateNotificationsApi);
router.patch('/api/users/:userId/notifications/:notificationId', updateNotificationApi);
// API KEYS // API KEYS
router.get('/api/me/keys', fetchUserKeysApi); router.get('/api/me/keys', fetchUserKeysApi);
router.post('/api/me/keys', createKeyApi); router.post('/api/me/keys', createKeyApi);
router.delete('/api/me/keys/:keyIdentifier', removeUserKeyApi); router.delete('/api/me/keys/:keyIdentifier', removeUserKeyApi);
router.delete('/api/me/keys', flushUserKeysApi); router.delete('/api/me/keys', flushUserKeysApi);
// ALERTS
router.get('/api/alerts', fetchAlertsApi);
router.post('/api/alerts', createAlertApi);
router.delete('/api/alerts/:alertId', removeAlertApi);
router.use(userRouter); router.use(userRouter);
router.use(stashesRouter); router.use(stashesRouter);
router.use(alertsRouter);
router.use(scenesRouter); router.use(scenesRouter);
router.use(actorsRouter); router.use(actorsRouter);