tags update

This commit is contained in:
Chris Grimmett 2023-06-14 20:39:02 +00:00
parent 2de81ff1c1
commit b3c19c24b6
28 changed files with 1051 additions and 17 deletions

View File

@ -1,5 +1,5 @@
{
"latest": "4.10.6",
"lastUpdateCheck": 1685050367579,
"lastNotification": 1684550793542
"latest": "4.11.1",
"lastUpdateCheck": 1686705141258,
"lastNotification": 1686696427587
}

View File

@ -4,4 +4,4 @@
ironmouse "Thank you" (for testing): 4760169
cj_clippy "Full library access" (for production): 9380584
cj_clippy "Your URL displayed on Futureporn.net": 10663202

View File

@ -1,4 +1,36 @@
module.exports = ({ env }) => ({
module.exports = ({
env
}) => ({
'fuzzy-search': {
enabled: true,
config: {
contentTypes: [{
uid: 'api::tag.tag',
modelName: 'tag',
transliterate: false,
queryConstraints: {
where: {
'$and': [
{
publishedAt: {
'$notNull': true
}
},
]
}
},
fuzzysortOptions: {
characterLimit: 32,
threshold: -600,
limit: 10,
keys: [{
name: 'name',
weight: 100
}]
}
}]
}
},
upload: {
config: {
provider: 'cloudinary',
@ -27,4 +59,4 @@ module.exports = ({ env }) => ({
},
},
},
});
});

View File

@ -0,0 +1,98 @@
const fetch = require('node-fetch')
// greets chatgpt
async function getFileDetailsFromUrl(url) {
const controller = new AbortController();
const signal = controller.signal;
const options = {
signal,
};
let retries = 10;
while (retries) {
console.log(`fetching ${url}`);
const timeoutId = setTimeout(() => {
console.log('fetch timed out, aborting...');
controller.abort();
}, 5000);
try {
const res = await fetch(url, options);
clearTimeout(timeoutId);
console.log('finished fetch');
if (!res.ok) throw new Error(`problem while getting file from url with url ${url}`);
if (!res?.headers?.get('x-bz-file-name')) throw new Error(`${url} did not have a x-bz-file-name in the response headers`);
if (!res?.headers?.get('x-bz-file-id')) throw new Error(`${url} did not have a x-bz-file-id in the response headers`);
return {
key: res.headers.get('x-bz-file-name'),
url: url,
uploadId: res.headers.get('x-bz-file-id'),
};
} catch (err) {
clearTimeout(timeoutId);
retries--;
if (retries === 0) {
console.error(`Could not fetch file details from URL: ${url}.`);
throw err;
}
console.warn(`Retrying fetch (${retries} attempts left)`);
}
}
}
module.exports = {
async up(knex) {
// You have full access to the Knex.js API with an already initialized connection to the database
// Get all VODs from the database
const vods = await knex.select('*').from('vods');
// Process each VOD
for (const vod of vods) {
// courtesy timer
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log(vod)
// Get the file details from the VOD's video source URL
if (vod?.video_src) {
try {
const fileDetails = await getFileDetailsFromUrl(vod.video_src);
// Insert the B2 file into the database
const [file] = await knex('b2_files').insert({
url: fileDetails.url,
key: fileDetails.key,
upload_id: fileDetails.uploadId,
}).returning('id');
console.log(file)
console.log(`attempting to insert vod_id:${vod.id}, b_2_file_id:${file.id} for videoSrcB2`)
// Link the B2 file to the VOD
await knex('vods_video_src_b_2_links').insert({
vod_id: vod.id,
b_2_file_id: file.id,
});
} catch (e) {
console.error(e)
console.log(`there was an error so we are skipping vod ${vod.id}`)
}
} else {
console.log(`${vod.id} has no video_src. skipping.`)
}
}
},
};

View File

@ -0,0 +1,110 @@
// const fetch = require('node-fetch')
// // greets chatgpt
// async function getFileDetailsFromUrl(url) {
// const controller = new AbortController();
// const signal = controller.signal;
// const options = {
// signal,
// };
// let retries = 10;
// while (retries) {
// console.log(`fetching ${url}`);
// const timeoutId = setTimeout(() => {
// console.log('fetch timed out, aborting...');
// controller.abort();
// }, 5000);
// try {
// const res = await fetch(url, options);
// clearTimeout(timeoutId);
// console.log('finished fetch');
// if (!res.ok) throw new Error(`problem while getting file from url with url ${url}`);
// if (!res?.headers?.get('x-bz-file-name')) throw new Error(`${url} did not have a x-bz-file-name in the response headers`);
// if (!res?.headers?.get('x-bz-file-id')) throw new Error(`${url} did not have a x-bz-file-id in the response headers`);
// return {
// key: res.headers.get('x-bz-file-name'),
// url: url,
// uploadId: res.headers.get('x-bz-file-id'),
// };
// } catch (err) {
// clearTimeout(timeoutId);
// retries--;
// if (retries === 0) {
// console.error(`Could not fetch file details from URL: ${url}.`);
// throw err;
// }
// console.warn(`Retrying fetch (${retries} attempts left)`);
// }
// }
// }
module.exports = {
async up(knex) {
// You have full access to the Knex.js API with an already initialized connection to the database
// we iterate through the local, non-strapi backup db first.
// get list of all tags
// for each tag
// * get list of related vods
// * create relation in Strapi
// *
// Get all VODs from the database
const vods = await knex.select('*').from('vods');
// Process each VOD
for (const vod of vods) {
// courtesy timer
await new Promise((resolve) => setTimeout(resolve, 10))
// @todo
console.log(vod)
// Get the file details from the VOD's video source URL
if (vod?.video_src) {
try {
const fileDetails = await getFileDetailsFromUrl(vod.video_src);
// Insert the B2 file into the database
const [file] = await knex('b2_files').insert({
url: fileDetails.url,
key: fileDetails.key,
upload_id: fileDetails.uploadId,
}).returning('id');
console.log(file)
console.log(`attempting to insert vod_id:${vod.id}, b_2_file_id:${file.id} for videoSrcB2`)
// Link the B2 file to the VOD
await knex('vods_video_src_b_2_links').insert({
vod_id: vod.id,
b_2_file_id: file.id,
});
} catch (e) {
console.error(e)
console.log(`there was an error so we are skipping vod ${vod.id}`)
}
} else {
console.log(`${vod.id} has no video_src. skipping.`)
}
}
},
};

View File

@ -4,6 +4,26 @@ const { Client } = require('pg')
const fetch = require('node-fetch')
const _ = require('lodash');
// module.exports = {
// async up(knex) {
// // Get all VODs from the database
// const vods = await knex.select('*').from('vods');
// // sanity check every B2 URL
// for (const vod of vods) {
// await checkUrl(vod.video_src)
// }
// console.log(`there are ${problemUrls.length} the problem urls`)
// console.log(problemUrls)
// process.exit(5923423)
// },
// };
// const slugify = require('slugify')
@ -110,6 +130,9 @@ async function main () {
// get list of vods from our source db
const vodsResponse = await client.query('SELECT tags, date, "announceUrl" FROM vod')
console.log(JSON.stringify(vodsResponse.rows))
process.exit(5)
for (const vod of vodsResponse.rows) {

View File

@ -22,7 +22,8 @@
"@strapi/strapi": "4.9.0",
"@strapi/utils": "^4.9.0",
"better-sqlite3": "8.0.1",
"pg": "^8.10.0"
"pg": "^8.10.0",
"strapi-plugin-fuzzy-search": "^1.11.0-beta.1"
},
"author": {
"name": "CJ_Clippy"

View File

@ -0,0 +1,36 @@
{
"kind": "collectionType",
"collectionName": "issues",
"info": {
"singularName": "issue",
"pluralName": "issues",
"displayName": "issue",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"url": {
"type": "string",
"required": true
},
"sla": {
"type": "enumeration",
"enum": [
"public",
"patron",
"authenticated"
],
"default": "public",
"required": true
},
"type": {
"type": "enumeration",
"enum": [
"stall"
]
}
}
}

View File

@ -0,0 +1,9 @@
'use strict';
/**
* issue controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::issue.issue');

View File

@ -0,0 +1,9 @@
'use strict';
/**
* issue router
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::issue.issue');

View File

@ -0,0 +1,9 @@
'use strict';
/**
* issue service
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::issue.issue');

View File

@ -1,5 +1,7 @@
'use strict';
const { JWT } = require('@mux/mux-node');
const MUX_SIGNING_KEY_ID = process.env.MUX_SIGNING_KEY_ID;
const MUX_SIGNING_KEY_PRIVATE_KEY = process.env.MUX_SIGNING_KEY_PRIVATE_KEY;
const MUX_PLAYBACK_RESTRICTION_ID = process.env.MUX_PLAYBACK_RESTRICTION_ID
@ -25,12 +27,25 @@ module.exports = createCoreController('api::mux-asset.mux-asset', ({ strapi }) =
return
}
ctx.body = await strapi.service('api::mux-asset.mux-asset').signJwt(
ctx.query.id,
MUX_SIGNING_KEY_ID,
MUX_SIGNING_KEY_PRIVATE_KEY,
MUX_PLAYBACK_RESTRICTION_ID
)
const tokens = {}
tokens.playbackToken = JWT.signPlaybackId(ctx.query.id, {
keyId: MUX_SIGNING_KEY_ID,
keySecret: MUX_SIGNING_KEY_PRIVATE_KEY,
params: {
playback_restriction_id: MUX_PLAYBACK_RESTRICTION_ID
},
})
tokens.storyboardToken = JWT.signPlaybackId(ctx.query.id, {
keyId: MUX_SIGNING_KEY_ID,
keySecret: MUX_SIGNING_KEY_PRIVATE_KEY,
type: 'storyboard'
})
ctx.body = tokens
}
}))

View File

@ -0,0 +1,38 @@
{
"kind": "collectionType",
"collectionName": "tag_vod_relations",
"info": {
"singularName": "tag-vod-relation",
"pluralName": "tag-vod-relations",
"displayName": "Tag Vod Relation",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"votes": {
"type": "integer"
},
"creator": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"tag": {
"type": "relation",
"relation": "oneToOne",
"target": "api::tag.tag"
},
"vod": {
"type": "relation",
"relation": "oneToOne",
"target": "api::vod.vod"
},
"creatorId": {
"type": "integer",
"required": true
}
}
}

View File

@ -0,0 +1,251 @@
'use strict';
/**
* tag-vod-relation controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::tag-vod-relation.tag-vod-relation', ({ strapi }) => ({
async relate(ctx) {
const userId = ctx?.state?.user?.id;
if (!userId) return ctx.badRequest("There was no user id in the request!");
if (!ctx.request.body.data) return ctx.badRequest('data was missing from body');
if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data');
if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data');
const { tag: tagId, vod: vodId } = ctx.request.body.data;
const tagVodRelation = await strapi.entityService.create('api::tag-vod-relation.tag-vod-relation', {
data: {
vod: vodId,
tag: tagId,
creator: userId,
creatorId: userId,
publishedAt: new Date(),
votes: 2
}
})
return tagVodRelation
},
async vote(ctx) {
// @todo
},
// // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers
// // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller
// // Method 2: Wrapping a core action (leaves core logic in place)
// async find(ctx) {
// // // some custom logic here
// // ctx.query = { ...ctx.query, local: 'en' }
// const userId = ctx?.state?.user?.id;
// if (!userId) return ctx.badRequest("There was no user id in the request!");
// // Calling the default core action
// const { data, meta } = await super.find(ctx);
// // add isCreator if the tvr was created by this user
// let dataWithCreator = data.map((d) => {
// if (d.data.attributes.)
// })
// // // some more custom logic
// // meta.date = Date.now()
// return { data, meta };
// },
// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers
// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller
// Method 2: Wrapping a core action (leaves core logic in place)
async create(ctx) {
// only allow unique tag, vod combos
const { query } = ctx.request;
const userId = ctx?.state?.user?.id;
if (!userId) return ctx.badRequest("There was no user id in the request!");
if (!ctx.request.body.data) return ctx.badRequest('data was missing from body');
if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data');
if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data');
const { tag: tagId, vod: vodId } = ctx.request.body.data;
const combo = await strapi.entityService.findMany('api::tag-vod-relation.tag-vod-relation', {
populate: {
tag: true,
vod: true
},
filters: {
$and: [{
tag: {
id: {
$eq: ctx.request.body.data.tag
}
}
}, {
vod: {
id: {
$eq: ctx.request.body.data.vod
}
}
}]
}
})
if (combo.length > 0) {
return ctx.badRequest('this vod already has that tag')
}
// @todo add votes and creator
ctx.request.body.data.creator = userId
ctx.request.body.data.votes = 2
const parseBody = (ctx) => {
if (ctx.is('multipart')) {
return parseMultipartData(ctx);
}
const { data } = ctx.request.body || {};
return { data };
};
const sanitizedInputData = {
vod: vodId,
tag: tagId,
publishedAt: new Date(),
creator: userId,
creatorId: userId,
votes: 2
}
// const tagVodRelation = await strapi.entityService.create('api::tag-vod-relation.tag-vod-relation', {
// ,
// populate: {
// tag: true,
// vod: true
// }
// })
const entity = await strapi
.service('api::tag-vod-relation.tag-vod-relation')
.create({
...query,
data: sanitizedInputData,
populate: { vod: true, tag: true }
});
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
console.log(sanitizedEntity)
return this.transformResponse({ ...sanitizedEntity });
},
async tagVod (ctx) {
let tagEntry
let tagVodRelationEntry
// create tag if needed
const { query } = ctx.request;
const userId = ctx?.state?.user?.id;
if (!userId) return ctx.badRequest("There was no user id in the request!");
if (!ctx.request.body.data) return ctx.badRequest('data was missing from body');
if (!ctx.request.body.data.tagName) return ctx.badRequest('tagName was missing from data');
if (!ctx.request.body.data.vodId) return ctx.badRequest('vodId was missing from data');
const { tagName, vodId } = ctx.request.body.data;
// does the named tag already exist?
tagEntry = await strapi.db.query('api::tag.tag')
.findOne({ where: { name: tagName }})
// create the named tag if it doesn't exist
if (!tagEntry) {
tagEntry = await strapi.entityService.create('api::tag.tag', {
data: {
name: tagName,
creator: userId,
publishedAt: new Date(),
}
})
}
// create tag-vod-relation
tagVodRelationEntry = await strapi.entityService.create('api::tag-vod-relation.tag-vod-relation', {
data: {
tag: tagEntry.id,
vod: vodId,
creator: userId,
creatorId: userId,
},
populate: {
tag: true,
vod: true
}
})
const sanitizedEntity = await this.sanitizeOutput(tagVodRelationEntry, ctx);
return this.transformResponse({ ...sanitizedEntity });
},
async deleteMine (ctx) {
// // some custom logic here
// ctx.query = { ...ctx.query, local: 'en' }
const userId = ctx?.state?.user?.id;
if (!userId) return ctx.badRequest("There was no user id in the request!");
if (!ctx.request.params.id) return ctx.badRequest('id was missing from params');
const { id } = ctx.request.params;
// constraints
// only able to delete tagVodRelation if
// * creator
// * publishedAt isBefore(now-24h)
// get the tvr the user wants to delete
const tvrToDelete = await strapi.entityService.findOne('api::tag-vod-relation.tag-vod-relation', id, {
populate: {
tag: true,
vod: true,
creator: true,
}
})
if (!tvrToDelete) return ctx.badRequest('Tag to be deleted does not exist.');
if (tvrToDelete.creator.id !== userId)
ctx.forbidden('only the creator of the tag can delete it');
if ((new Date(tvrToDelete.createdAt).valueOf()+86400000) < new Date().valueOf())
ctx.forbidden('cannot delete tags older than 24 hours')
// Calling the default core action
const { data, meta } = await super.delete(ctx);
// delete the related tag if it has no other vod
// @todo?? or maybe this is handled by lifecycle hook?
// // some more custom logic
// meta.date = Date.now()
return { data, meta };
}
}));

View File

@ -0,0 +1,51 @@
'use strict';
/**
* tag-vod-relation router
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
const defaultRouter = createCoreRouter('api::tag-vod-relation.tag-vod-relation');
// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7
const customRouter = (innerRouter, extraRoutes = []) => {
let routes;
return {
get prefix() {
return innerRouter.prefix;
},
get routes() {
if (!routes) routes = extraRoutes.concat(innerRouter.routes)
return routes;
},
};
};
const myExtraRoutes = [
{
method: "POST",
path: "/tag-vod-relations/relate",
handler: "api::tag-vod-relation.tag-vod-relation.relate"
},
// {
// method: 'GET',
// path: '/tag-vod-relations',
// handler: 'api::tag-vod-relation.tag-vod-relation.find'
// },
{
method: "PUT",
path: "/tag-vod-relations/vote",
handler: "api::tag-vod-relation.tag-vod-relation.vote"
}, {
method: 'POST',
path: '/tag-vod-relations/tag',
handler: 'api::tag-vod-relation.tag-vod-relation.tagVod'
}, {
method: 'DELETE',
path: '/tag-vod-relations/deleteMine/:id',
handler: 'api::tag-vod-relation.tag-vod-relation.deleteMine'
}
];
module.exports = customRouter(defaultRouter, myExtraRoutes);

View File

@ -0,0 +1,9 @@
'use strict';
/**
* tag-vod-relation service
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::tag-vod-relation.tag-vod-relation');

View File

@ -28,6 +28,11 @@
"relation": "manyToMany",
"target": "api::vod.vod",
"inversedBy": "tags"
},
"creator": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
}
}
}

View File

@ -6,4 +6,67 @@
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::tag.tag');
module.exports = createCoreController('api::tag.tag', ({ strapi }) => ({
async createTagRelation(ctx) {
// we have this controller which associates a tag with a vod
// this exists so users can indirectly update vod records which they dont have permissions to update
// first we need to get the user's request.
// they are telling us a vod ID and a tag ID
// our job is to get a reference to the vod, and add the tag relation.
if (!ctx.request.body.data) return ctx.badRequest('data was missing from body');
if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data');
if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data');
const { tag, vod: vodId } = ctx.request.body.data;
// const vod = await strapi.entityService.findOne('api::vod.vod', vodId, {
// populate: { tags: true }
// })
// console.log(vod)
// const existingTags = vod.attributes.tags
// const updatedTags = vod.attributes.tags.concat([tag])
// await strapi.entityService.update('api::vod.vod', vodId, {
// data: {
// tags: updatedTags
// }
// })
await strapi.entityService.update('api::vod.vod', vodId, {
data: {
tags: {
connect: [tag]
}
}
})
return 'OK'
// const userId = ctx.state.user.id;
// console.log(`createTagRelation controller hit by userID ${userId}`)
// console.log(ctx.request.body.data)
// if (!ctx.request.body.data) return ctx.badRequest("data was missing in request body");
// if (!ctx.request.body.data.date) return ctx.badRequest("date was missing");
// if (!ctx.request.body.data.videoSrcB2) return ctx.badRequest("videoSrcB2 was missing");
// if (!ctx.request.body.data.videoSrcB2.key) return ctx.badRequest("videoSrcB2.key was missing");
// if (!ctx.request.body.data.videoSrcB2.uploadId) return ctx.badRequest("videoSrcB2.uploadId was missing");
// console.log(ctx)
// const publicPatrons = await strapi.entityService.findMany('plugin::users-permissions.user', {
// fields: ['username'],
// filters: { isNamePublic: true }
// })
// console.log(publicPatrons)
// return publicPatrons
},
}));

View File

@ -5,5 +5,28 @@
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
const defaultRouter = createCoreRouter('api::tag.tag');
module.exports = createCoreRouter('api::tag.tag');
// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7
const customRouter = (innerRouter, extraRoutes = []) => {
let routes;
return {
get prefix() {
return innerRouter.prefix;
},
get routes() {
if (!routes) routes = extraRoutes.concat(innerRouter.routes)
return routes;
},
};
};
const myExtraRoutes = [
{
method: "POST",
path: "/tag/tagRelation",
handler: "api::tag.tag.createTagRelation"
}
];
module.exports = customRouter(defaultRouter, myExtraRoutes)

View File

@ -0,0 +1,45 @@
{
"kind": "collectionType",
"collectionName": "timestamps",
"info": {
"singularName": "timestamp",
"pluralName": "timestamps",
"displayName": "Timestamp",
"description": ""
},
"options": {
"draftAndPublish": false,
"populateCreatorFields": true
},
"pluginOptions": {},
"attributes": {
"time": {
"type": "integer"
},
"tag": {
"type": "relation",
"relation": "oneToOne",
"target": "api::tag.tag"
},
"vod": {
"type": "relation",
"relation": "manyToOne",
"target": "api::vod.vod",
"inversedBy": "timestamps"
},
"creatorId": {
"type": "integer",
"required": true
},
"upvoters": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
},
"downvoters": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user"
}
}
}

View File

@ -0,0 +1,111 @@
'use strict';
/**
* timestamp controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::timestamp.timestamp', ({ strapi }) => ({
async find(ctx) {
const { data, meta } = await super.find(ctx);
// Iterate over each timestamp in the data array
const timestampsWithVotes = await Promise.all(data.map(async (timestamp) => {
// Retrieve the upvoters count for the current timestamp
// Retrieve the downvoters count for the current timestamp
const entry = await strapi.db
.query('api::timestamp.timestamp')
.findOne({
populate: ['upvoters', 'downvoters'],
where: {
id: timestamp.id
}
});
const upvotesCount = entry.upvoters.length
const downvotesCount = entry.downvoters.length
// Create new properties "upvotes" and "downvotes" on the timestamp object
timestamp.attributes.upvotes = upvotesCount;
timestamp.attributes.downvotes = downvotesCount;
return timestamp;
}));
console.log(timestampsWithVotes)
return { data: timestampsWithVotes, meta };
},
// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers
// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller
// Method 2: Wrapping a core action (leaves core logic in place)
async create(ctx) {
// add creatorId to the record
const userId = ctx?.state?.user?.id;
if (!userId) return ctx.badRequest("There was no user id in the request!");
if (!ctx.request.body.data) return ctx.badRequest('data was missing from body');
if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data');
const { time, tag } = ctx.request.body.data;
ctx.request.body.data.creatorId = userId
console.log(ctx.request.body.data)
// does the timestamp already exist with same combination of time+tag?
const duplicate = await strapi.db.query('api::timestamp.timestamp')
.findOne({ where: { time, tag }})
if (!!duplicate) return ctx.badRequest('A duplicate timestamp already exists!');
// Calling the default core action
const res = await super.create(ctx);
return res
},
async vote(ctx) {
const userId = ctx?.state?.user?.id;
const { direction } = ctx.request.body.data;
if (!ctx.request.params.id) return ctx.badRequest('id was missing from params');
const { id } = ctx.request.params;
// get the ts to be voted on
const ts = await strapi.entityService.findOne('api::timestamp.timestamp', id)
if (!ts) return ctx.badRequest('timestamp does not exist');
const res = await strapi.entityService.update('api::timestamp.timestamp', id, {
data: {
upvoters: direction === 1 ? { connect: [userId] } : { disconnect: [userId] },
downvoters: direction === 1 ? { disconnect: [userId] } : { connect: [userId] }
}
});
return res;
},
async delete(ctx) {
const userId = ctx?.state?.user?.id;
const { id } = ctx.request.params;
// get the ts to be deleted
const ts = await strapi.entityService.findOne('api::timestamp.timestamp', id)
if (!ts) return ctx.badRequest('Timestamp does not exist')
// Refuse to delete if not the tag creator
if (ts.creatorId !== userId) return ctx.forbidden('Only the timestamp creator can delete the timestamp.')
const res = await super.delete(ctx)
return res
}
}))

View File

@ -0,0 +1,43 @@
'use strict';
/**
* timestamp router
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
const defaultRouter = createCoreRouter('api::timestamp.timestamp');
// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7
const customRouter = (innerRouter, extraRoutes = []) => {
let routes;
return {
get prefix() {
return innerRouter.prefix;
},
get routes() {
if (!routes) routes = extraRoutes.concat(innerRouter.routes)
return routes;
},
};
};
const myExtraRoutes = [
{
method: "PUT",
path: "/timestamps/:id/vote",
handler: "api::timestamp.timestamp.vote"
},
{
method: 'DELETE',
path: '/timestamps/:id',
handler: 'api::timestamp.timestamp.delete'
}
];
module.exports = customRouter(defaultRouter, myExtraRoutes);

View File

@ -0,0 +1,9 @@
'use strict';
/**
* timestamp service
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::timestamp.timestamp');

View File

@ -38,6 +38,11 @@
"allowedTypes": [
"images"
]
},
"linkTag": {
"type": "relation",
"relation": "oneToOne",
"target": "api::tag.tag"
}
}
}

View File

@ -99,6 +99,12 @@
"relation": "manyToMany",
"target": "api::tag.tag",
"mappedBy": "vods"
},
"timestamps": {
"type": "relation",
"relation": "oneToMany",
"target": "api::timestamp.timestamp",
"mappedBy": "vod"
}
}
}

View File

@ -73,6 +73,17 @@
"avatar": {
"type": "string",
"default": "http://placekitten.com/32/32"
},
"isLinkPublic": {
"type": "boolean",
"default": false
},
"vanityLink": {
"type": "string",
"maxLength": 256
},
"patreonBenefits": {
"type": "text"
}
}
}

View File

@ -111,6 +111,7 @@ module.exports = ({ strapi }) => {
console.log(` >> HERE is the user's patreon profile`)
console.log(profile)
const isPatron = profile.benefits.includes(patreonModel.benefitId)
const patreonBenefits = profile.benefits.join(',')
console.log(`isPatron:${isPatron}`)
@ -126,7 +127,8 @@ module.exports = ({ strapi }) => {
where: { email },
data: {
...user,
role: selectedRole
role: selectedRole,
patreonBenefits
}
})
return updatedUser;

View File

@ -6603,6 +6603,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
fuzzysort@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-2.0.4.tgz#a21d1ce8947eaf2797dc3b7c28c36db9d1165f84"
integrity sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@ -10703,6 +10708,14 @@ std-env@^3.0.1:
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.2.tgz#af27343b001616015534292178327b202b9ee955"
integrity sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==
strapi-plugin-fuzzy-search@^1.11.0-beta.1:
version "1.11.0-beta.1"
resolved "https://registry.yarnpkg.com/strapi-plugin-fuzzy-search/-/strapi-plugin-fuzzy-search-1.11.0-beta.1.tgz#fadd58e6cb5278bc793179f649cc19b77ed4103c"
integrity sha512-hMOfgONiN5b7drMI8R3IhVoDXUpzGcgRM/zkSPLWymJDiJEPVaVyIexSoJhll+syZUmHZB3WXq/oK/sWiizekQ==
dependencies:
fuzzysort "2.0.4"
transliteration "2.3.5"
stream-chain@2.2.5, stream-chain@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09"
@ -11049,6 +11062,13 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
transliteration@2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/transliteration/-/transliteration-2.3.5.tgz#8f92309575f69e4a8a525dab4ff705ebcf961c45"
integrity sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==
dependencies:
yargs "^17.5.1"
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
@ -11676,7 +11696,7 @@ yargs-parser@^21.1.1:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^17.7.1:
yargs@^17.5.1, yargs@^17.7.1:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==