Added socials.

This commit is contained in:
2024-11-04 02:36:30 +01:00
parent 208f1dfde4
commit de60b67cb9
20 changed files with 945 additions and 142 deletions

View File

@@ -55,6 +55,8 @@ const keyMap = {
isCircumcised: 'circumcised',
};
const socialsOrder = ['onlyfans', 'twitter'];
export function curateActor(actor, context = {}) {
return {
id: actor.id,
@@ -115,9 +117,11 @@ export function curateActor(actor, context = {}) {
agency: actor.agency,
avatar: curateMedia(actor.avatar),
socials: context.socials?.map((social) => ({
id: social.id,
url: social.url,
platform: social.platform,
})),
handle: social.handle,
})).toSorted((socialA, socialB) => socialsOrder.indexOf(socialB.platform) - socialsOrder.indexOf(socialA.platform)),
profiles: context.profiles?.map((profile) => ({
id: profile.id,
description: profile.description,
@@ -216,7 +220,7 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
.leftJoin('media', 'media.id', 'actors_avatars.media_id')
.groupBy('media.id', 'actors_avatars.actor_id')
.orderBy(knex.raw('max(actors_avatars.created_at)'), 'desc'),
knex('actors_social')
knex('actors_socials')
.whereIn('actor_id', actorIds),
reqUser
? knex('stashes_actors')
@@ -527,14 +531,14 @@ export async function fetchActorRevisions(revisionId, filters = {}, reqUser) {
}
async function applyActorValueDelta(profileId, delta, trx) {
return knex('actors_profiles')
await knex('actors_profiles')
.where('id', profileId)
.update(keyMap[delta.key] || delta.key, delta.value)
.transacting(trx);
}
async function applyActorDirectDelta(actorId, delta, trx) {
return knex('actors')
await knex('actors')
.where('id', actorId)
.update(keyMap[delta.key] || delta.key, delta.value)
.modify((builder) => {
@@ -545,6 +549,22 @@ async function applyActorDirectDelta(actorId, delta, trx) {
.transacting(trx);
}
async function applyActorSocialsDelta(actorId, delta, trx) {
await knex('actors_socials')
.where('actor_id', actorId)
.delete()
.transacting(trx);
await knex('actors_socials')
.insert(delta.value.map((social) => ({
actor_id: actorId,
platform: social.platform,
handle: social.handle,
url: social.url,
})))
.transacting(trx);
}
async function fetchMainProfile(actorId, wasCreated = false) {
const profileEntry = await knex('actors_profiles')
.where('actor_id', actorId)
@@ -623,6 +643,10 @@ async function applyActorRevision(revisionIds, reqUser) {
return applyActorValueDelta(mainProfile.id, delta, trx);
}
if (delta.key === 'socials') {
return applyActorSocialsDelta(revision.actor_id, delta, trx);
}
if (delta.key === 'name' && reqUser.role === 'admin') {
return applyActorDirectDelta(revision.actor_id, delta, trx);
}
@@ -767,35 +791,59 @@ function convertWeight(weight, units) {
return Number(weight) || null;
}
export async function createActorRevision(actorId, {
edits,
comment,
apply,
...options
}, reqUser) {
const [
[actor],
openRevisions,
] = await Promise.all([
fetchActorsById([actorId], {
reqUser,
includeAssets: true,
includePartOf: true,
}),
knex('actors_revisions')
.where('user_id', reqUser.id)
.whereNull('approved'),
]);
const platformsByHostname = Object.fromEntries(Object.entries(config.socials.urls).map(([platform, url]) => {
const { hostname, pathname } = new URL(url);
if (!actor) {
throw new HttpError(`No actor with ID ${actorId} found to update`, 404);
}
return [hostname, {
platform,
pathname: decodeURIComponent(pathname),
url,
}];
}));
if (openRevisions.length >= config.revisions.unapprovedLimit && reqUser.role !== 'admin') {
throw new HttpError(`You have ${config.revisions.unapprovedLimit} unapproved revisions, please wait for approval before submitting another revision.`, 429);
}
function curateSocials(socials) {
return socials.map((social) => {
if (!social.handle && !social.url) {
throw new Error('No social handle or website URL specified');
}
const baseActor = Object.fromEntries(Object.entries(actor).map(([key, values]) => {
if (social.handle && !social.platform) {
throw new Error('No platform specified for social handle');
}
if (social.handle && social.platform && /[\w-]+/.test(social.handle) && /[a-z]+/i.test(social.platform)) {
return {
platform: social.platform.toLowerCase(),
handle: social.handle,
};
}
if (social.url) {
const { hostname, pathname } = new URL(social.url);
const platform = platformsByHostname[hostname];
if (platform) {
const handle = pathname.match(new RegExp(platform.pathname.replace('{handle}', '([\\w-]+)')))?.[1];
if (handle) {
return {
platform: platform.platform,
handle,
};
}
}
return {
url: social.url,
};
}
throw new Error('Invalid social');
}).filter(Boolean);
}
function getBaseActor(actor) {
return Object.fromEntries(Object.entries(actor).map(([key, values]) => {
if ([
'scenes',
'likes',
@@ -805,11 +853,11 @@ export async function createActorRevision(actorId, {
return null;
}
/* avatar should return id
if (values?.hash) {
return [key, values.hash];
if ([
'socials',
].includes(key)) {
return [key, values];
}
*/
if (values?.id) {
return [key, values.id];
@@ -825,8 +873,10 @@ export async function createActorRevision(actorId, {
return [key, values];
}).filter(Boolean));
}
const deltas = await Promise.all(Object.entries(edits).map(async ([key, value]) => {
function getDeltas(edits, baseActor, options) {
return Promise.all(Object.entries(edits).map(async ([key, value]) => {
if (baseActor[key] === value || typeof value === 'undefined') {
return null;
}
@@ -890,6 +940,24 @@ export async function createActorRevision(actorId, {
];
}
if (key === 'socials') {
const convertedSocials = curateSocials(value);
const convertedUrls = value
.filter((social) => social.url && !convertedSocials.some((convertedSocial) => convertedSocial.url === social.url))
.map((social) => social.url);
const conversionComment = convertedUrls.length > 0
? `curated URLs ${convertedUrls.join(', ')} as social handles`
: null;
return {
key,
value: convertedSocials,
comment: conversionComment,
};
}
if (['cup', 'bust', 'waist', 'hip'].includes(key)) {
const convertedValue = convertFigure(key, value, options.figureUnits);
@@ -967,6 +1035,38 @@ export async function createActorRevision(actorId, {
return { key, value };
})).then((rawDeltas) => rawDeltas.flat().filter(Boolean));
}
export async function createActorRevision(actorId, {
edits,
comment,
apply,
...options
}, reqUser) {
const [
[actor],
openRevisions,
] = await Promise.all([
fetchActorsById([actorId], {
reqUser,
includeAssets: true,
includePartOf: true,
}),
knex('actors_revisions')
.where('user_id', reqUser.id)
.whereNull('approved'),
]);
if (!actor) {
throw new HttpError(`No actor with ID ${actorId} found to update`, 404);
}
if (openRevisions.length >= config.revisions.unapprovedLimit && reqUser.role !== 'admin') {
throw new HttpError(`You have ${config.revisions.unapprovedLimit} unapproved revisions, please wait for approval before submitting another revision.`, 429);
}
const baseActor = getBaseActor(actor);
const deltas = await getDeltas(edits, baseActor, options);
const deltaComments = deltas.map((delta) => delta.comment);
const curatedComment = [comment, ...deltaComments].filter(Boolean).join(' | ');

View File

@@ -42,6 +42,7 @@ export default async function mainHandler(req, res, next) {
media: config.media,
psa: config.psa,
links: config.links,
socials: config.socials,
},
meta: {
now: new Date().toISOString(),