From 7f871b6b0a26ce99d9b81d41ea4997cca0c7c2b1 Mon Sep 17 00:00:00 2001 From: CJ_Clippy Date: Tue, 12 Aug 2025 21:43:26 -0800 Subject: [PATCH] switch to bulma --- .../migration.sql | 8 + services/our/prisma/schema.prisma | 2 +- .../our/scripts/2025-08-12-migrate-from-v1.ts | 117 +++++ .../2025-08-12-migrate-from-v1.ts.prompt | 269 ++++++++++ services/our/scripts/README.md | 22 + services/our/src/app.ts | 10 +- services/our/src/plugins/vods.ts | 31 ++ services/our/src/plugins/vtubers.ts | 52 +- services/our/src/views/index.hbs | 143 +++--- services/our/src/views/layouts/main.hbs | 34 +- services/our/src/views/partials/navbar.hbs | 76 +-- services/our/src/views/perks.hbs | 77 +-- services/our/src/views/profile.hbs | 24 +- services/our/src/views/stream.hbs | 2 +- services/our/src/views/uploads/list.hbs | 14 +- services/our/src/views/uploads/new.hbs | 75 ++- services/our/src/views/uploads/show.hbs | 2 +- services/our/src/views/vod.hbs | 197 ++++---- services/our/src/views/vods.hbs | 73 +-- services/our/src/views/vtubers/list.hbs | 12 +- services/our/src/views/vtubers/new.hbs | 224 ++++++--- services/our/src/views/vtubers/show.hbs | 467 +++++++++--------- 22 files changed, 1277 insertions(+), 654 deletions(-) create mode 100644 services/our/prisma/migrations/20250812115216_make_slug_unique/migration.sql create mode 100644 services/our/scripts/2025-08-12-migrate-from-v1.ts create mode 100644 services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt create mode 100644 services/our/scripts/README.md diff --git a/services/our/prisma/migrations/20250812115216_make_slug_unique/migration.sql b/services/our/prisma/migrations/20250812115216_make_slug_unique/migration.sql new file mode 100644 index 0000000..ba977bb --- /dev/null +++ b/services/our/prisma/migrations/20250812115216_make_slug_unique/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[slug]` on the table `Vtuber` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Vtuber_slug_key" ON "Vtuber"("slug"); diff --git a/services/our/prisma/schema.prisma b/services/our/prisma/schema.prisma index e7d9e77..2aa8a43 100644 --- a/services/our/prisma/schema.prisma +++ b/services/our/prisma/schema.prisma @@ -96,7 +96,7 @@ model Vod { model Vtuber { id String @id @default(cuid(2)) image String? - slug String? + slug String? @unique displayName String? chaturbate String? twitter String? diff --git a/services/our/scripts/2025-08-12-migrate-from-v1.ts b/services/our/scripts/2025-08-12-migrate-from-v1.ts new file mode 100644 index 0000000..1d83c46 --- /dev/null +++ b/services/our/scripts/2025-08-12-migrate-from-v1.ts @@ -0,0 +1,117 @@ +import { PrismaClient } from '../generated/prisma'; +import pg from 'pg'; + +const prisma = new PrismaClient(); + +const v1 = new pg.Pool({ + host: process.env.V1_DB_HOST || 'localhost', + port: +(process.env.V1_DB_PORT || '5444'), + user: process.env.V1_DB_USER || 'postgres', + password: process.env.V1_DB_PASS || 'password', + database: process.env.V1_DB_NAME || 'restoredb' +}); + +// Set this to an existing user ID in v2 +const DEFAULT_UPLOADER_ID = process.env.DEFAULT_UPLOADER_ID || 'REPLACE_WITH_V2_USER_ID'; + +async function migrateVtubers() { + console.log('Migrating vtubers...'); + const res = await v1.query(`SELECT * FROM vtubers`); + for (const vt of res.rows) { + await prisma.vtuber.create({ + data: { + slug: vt.slug, + image: vt.image, + displayName: vt.display_name, + chaturbate: vt.chaturbate, + twitter: vt.twitter, + patreon: vt.patreon, + twitch: vt.twitch, + tiktok: vt.tiktok, + onlyfans: vt.onlyfans, + youtube: vt.youtube, + linktree: vt.linktree, + carrd: vt.carrd, + fansly: vt.fansly, + pornhub: vt.pornhub, + discord: vt.discord, + reddit: vt.reddit, + throne: vt.throne, + instagram: vt.instagram, + facebook: vt.facebook, + merch: vt.merch, + description: `${vt.description_1 ?? ''}\n${vt.description_2 ?? ''}`.trim() || null, + themeColor: vt.theme_color, + uploaderId: DEFAULT_UPLOADER_ID + } + }); + } + console.log(`Migrated ${res.rows.length} vtubers`); +} + +async function migrateVods() { + console.log('Migrating vods...'); + const vods = await v1.query(`SELECT * FROM vods`); + + for (const vod of vods.rows) { + // Get linked vtubers + const vtuberLinks = await v1.query( + `SELECT vtuber_id FROM vods_vtuber_links WHERE vod_id = $1`, + [vod.id] + ); + + let vtuberSlugs: string[] = []; + if (vtuberLinks.rows.length > 0) { + const vtuberRes = await v1.query( + `SELECT slug FROM vtubers WHERE id = ANY($1)`, + [vtuberLinks.rows.map(r => r.vtuber_id)] + ); + vtuberSlugs = vtuberRes.rows.map(v => v.slug).filter(Boolean); + } + + // Get thumbnail + const thumbLink = await v1.query( + `SELECT b2.cdn_url, b2.url FROM vods_thumbnail_links vtl + JOIN b2_files b2 ON vtl.b_2_file_id = b2.id + WHERE vtl.vod_id = $1 LIMIT 1`, + [vod.id] + ); + + // Get source video + const videoSrcLink = await v1.query( + `SELECT b2.cdn_url, b2.url FROM vods_video_src_b_2_links vsl + JOIN b2_files b2 ON vsl.b_2_file_id = b2.id + WHERE vsl.vod_id = $1 LIMIT 1`, + [vod.id] + ); + + await prisma.vod.create({ + data: { + uploaderId: DEFAULT_UPLOADER_ID, + streamDate: vod.date ?? new Date(), + notes: vod.note, + sourceVideo: videoSrcLink.rows[0]?.cdn_url || videoSrcLink.rows[0]?.url || null, + thumbnail: thumbLink.rows[0]?.cdn_url || thumbLink.rows[0]?.url || null, + vtubers: { + connect: vtuberSlugs.map(slug => ({ slug })) + } + } + }); + } + + console.log(`Migrated ${vods.rows.length} vods`); +} + +async function main() { + try { + await migrateVtubers(); + await migrateVods(); + } catch (err) { + console.error(err); + } finally { + await v1.end(); + await prisma.$disconnect(); + } +} + +main(); diff --git a/services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt b/services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt new file mode 100644 index 0000000..a02ab7b --- /dev/null +++ b/services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt @@ -0,0 +1,269 @@ +// 2025-08-12-migrate-from-futureporn.ts +// +// we are migrating from v1 futureporn.net (a strapi site) to v2 future.porn (a fastify site with prisma) +// +// +// the idea is to read a local backup of the strapi site from a .sql +// and use the data contained within to populate the database on the v2 site using prisma. +// + +// here's the schema from the v1 site + +```sql +CREATE TABLE "public"."b2_files" ( + "id" SERIAL, + "url" VARCHAR(255) NULL, + "key" VARCHAR(255) NULL, + "upload_id" VARCHAR(255) NULL, + "created_at" TIMESTAMP NULL, + "updated_at" TIMESTAMP NULL, + "created_by_id" INTEGER NULL, + "updated_by_id" INTEGER NULL, + "cdn_url" VARCHAR(255) NULL, + CONSTRAINT "b2_files_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "public"."mux_assets" ( + "id" SERIAL, + "playback_id" VARCHAR(255) NULL, + "asset_id" VARCHAR(255) NULL, + "created_at" TIMESTAMP NULL, + "updated_at" TIMESTAMP NULL, + "created_by_id" INTEGER NULL, + "updated_by_id" INTEGER NULL, + "deletion_queued_at" TIMESTAMP NULL, + CONSTRAINT "mux_assets_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "public"."vods" ( + "id" SERIAL, + "video_src_hash" VARCHAR(255) NULL, + "video_720_hash" VARCHAR(255) NULL, + "video_480_hash" VARCHAR(255) NULL, + "video_360_hash" VARCHAR(255) NULL, + "video_240_hash" VARCHAR(255) NULL, + "thin_hash" VARCHAR(255) NULL, + "thicc_hash" VARCHAR(255) NULL, + "announce_title" VARCHAR(255) NULL, + "announce_url" VARCHAR(255) NULL, + "note" TEXT NULL, + "date" TIMESTAMP NULL, + "spoilers" TEXT NULL, + "created_at" TIMESTAMP NULL, + "updated_at" TIMESTAMP NULL, + "published_at" TIMESTAMP NULL, + "created_by_id" INTEGER NULL, + "updated_by_id" INTEGER NULL, + "title" VARCHAR(255) NULL, + "chat_log" TEXT NULL, + "date_2" VARCHAR(255) NULL, + "cuid" VARCHAR(255) NULL, + "archive_status" VARCHAR(255) NULL, + CONSTRAINT "vods_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "public"."vods_mux_asset_links" ( + "id" SERIAL, + "vod_id" INTEGER NULL, + "mux_asset_id" INTEGER NULL, + CONSTRAINT "vods_mux_asset_links_pkey" PRIMARY KEY ("id"), + CONSTRAINT "vods_mux_asset_links_unique" UNIQUE ("vod_id", "mux_asset_id") +); + +CREATE TABLE "public"."vods_thumbnail_links" ( + "id" SERIAL, + "vod_id" INTEGER NULL, + "b_2_file_id" INTEGER NULL, + CONSTRAINT "vods_thumbnail_links_pkey" PRIMARY KEY ("id"), + CONSTRAINT "vods_thumbnail_links_unique" UNIQUE ("vod_id", "b_2_file_id") +); + +CREATE TABLE "public"."vods_video_src_b_2_links" ( + "id" SERIAL, + "vod_id" INTEGER NULL, + "b_2_file_id" INTEGER NULL, + CONSTRAINT "vods_video_src_b_2_links_pkey" PRIMARY KEY ("id"), + CONSTRAINT "vods_video_src_b_2_links_unique" UNIQUE ("vod_id", "b_2_file_id") +); +CREATE TABLE "public"."vods_vtuber_links" ( + "id" SERIAL, + "vod_id" INTEGER NULL, + "vtuber_id" INTEGER NULL, + "vod_order" DOUBLE PRECISION NULL, + CONSTRAINT "vods_vtuber_links_pkey" PRIMARY KEY ("id"), + CONSTRAINT "vods_vtuber_links_unique" UNIQUE ("vod_id", "vtuber_id") +); + +CREATE TABLE "public"."vtubers" ( + "id" SERIAL, + "chaturbate" VARCHAR(255) NULL, + "twitter" VARCHAR(255) NULL, + "patreon" VARCHAR(255) NULL, + "twitch" VARCHAR(255) NULL, + "tiktok" VARCHAR(255) NULL, + "onlyfans" VARCHAR(255) NULL, + "youtube" VARCHAR(255) NULL, + "linktree" VARCHAR(255) NULL, + "carrd" VARCHAR(255) NULL, + "fansly" VARCHAR(255) NULL, + "pornhub" VARCHAR(255) NULL, + "discord" VARCHAR(255) NULL, + "reddit" VARCHAR(255) NULL, + "throne" VARCHAR(255) NULL, + "instagram" VARCHAR(255) NULL, + "facebook" VARCHAR(255) NULL, + "merch" VARCHAR(255) NULL, + "slug" VARCHAR(255) NULL, + "image" VARCHAR(255) NULL, + "display_name" VARCHAR(255) NULL, + "description_1" TEXT NULL, + "description_2" TEXT NULL, + "created_at" TIMESTAMP NULL, + "updated_at" TIMESTAMP NULL, + "published_at" TIMESTAMP NULL, + "created_by_id" INTEGER NULL, + "updated_by_id" INTEGER NULL, + "theme_color" VARCHAR(255) NULL, + "image_blur" VARCHAR(255) NULL, + CONSTRAINT "vtubers_pkey" PRIMARY KEY ("id") +); + +``` + + + // here's the schema from the v2 site + + ```prisma +generator client { + provider = "prisma-client-js" + output = "../generated/prisma" + binaryTargets = ["native", "debian-openssl-3.0.x"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(uuid()) + patreonId String @unique + patreonFullName String? + imageUrl String? + roles Role[] + vods Vod[] + Vtuber Vtuber[] +} + +enum RoleName { + user + supporterTier1 + supporterTier2 + supporterTier3 + supporterTier4 + supporterTier5 + supporterTier6 + moderator + admin +} + +model Role { + id String @id @default(cuid(2)) + name String @unique + users User[] +} + +model RateLimiterFlexible { + key String @id + points Int + expire DateTime? +} + +model Stream { + id String @id @default(cuid(2)) + date DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + announcementUrl String? + + vods Vod[] + + @@map("stream_entity") +} + +enum VodStatus { + ordering + pending + approved + rejected + processing + processed +} + +model Vod { + id String @id @default(cuid(2)) + streamId String? + stream Stream? @relation(fields: [streamId], references: [id]) + uploaderId String // previously in Upload + uploader User @relation(fields: [uploaderId], references: [id]) + + streamDate DateTime + notes String? + segmentKeys Json? + sourceVideo String? + hlsPlaylist String? + thumbnail String? + asrVttKey String? + slvttSheetKeys Json? + slvttVTTKey String? + magnetLink String? + + status VodStatus @default(pending) + sha256sum String? + cidv1 String? + funscript String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + vtubers Vtuber[] +} + +model Vtuber { + id String @id @default(cuid(2)) + image String? + slug String? + displayName String? + chaturbate String? + twitter String? + patreon String? + twitch String? + tiktok String? + onlyfans String? + youtube String? + linktree String? + carrd String? + fansly String? + pornhub String? + discord String? + reddit String? + throne String? + instagram String? + facebook String? + merch String? + description String? + themeColor String? + + fanslyId String? + chaturbateId String? + twitterId String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + vods Vod[] + uploaderId String + uploader User @relation(fields: [uploaderId], references: [id]) +} + +``` \ No newline at end of file diff --git a/services/our/scripts/README.md b/services/our/scripts/README.md new file mode 100644 index 0000000..1b94348 --- /dev/null +++ b/services/our/scripts/README.md @@ -0,0 +1,22 @@ +# scripts + +This directory is for **DATA MIGRATIONS ONLY**. + +Data migrations are **not** the same as schema migrations. + +- **Schema migrations** (handled by Prisma Migrate) change the database structure — adding/removing columns, altering data types, creating new tables, etc. +- **Data migrations** modify the *contents* of the database to align with business logic changes, clean up old data, or backfill new fields without altering the schema. + +Examples of data migrations: +- Populating a new column with default or computed values. +- Normalizing inconsistent text formats (e.g., fixing casing, trimming whitespace). +- Converting old enum/string values to new ones. +- Moving data between tables after a structural change has already been applied. + +Guidelines: +1. **Idempotent when possible** — running the script twice should not break data integrity. +2. **Version-controlled** — keep a clear history of changes. +3. **Document assumptions** — include comments explaining why the migration is needed and what it affects. +4. **Run after schema changes** — if both schema and data changes are required, update the schema first. + +> ⚠️ Always back up your database before running data migrations in production. diff --git a/services/our/src/app.ts b/services/our/src/app.ts index 2ece56c..d4cb06a 100644 --- a/services/our/src/app.ts +++ b/services/our/src/app.ts @@ -181,11 +181,11 @@ export function buildApp() { }) // @todo we are going to need to use redis or similar https://github.com/fastify/fastify-caching - app.register(fastifyCaching, { - expiresIn: 300, - privacy: 'private', - serverExpiresIn: 300, - }) + // app.register(fastifyCaching, { + // expiresIn: 300, + // privacy: 'private', + // serverExpiresIn: 300, + // }) app.register(graphileWorker) app.register(prismaPlugin) diff --git a/services/our/src/plugins/vods.ts b/services/our/src/plugins/vods.ts index cc06eb8..7dc8d9b 100644 --- a/services/our/src/plugins/vods.ts +++ b/services/our/src/plugins/vods.ts @@ -29,9 +29,12 @@ export default async function vodsRoutes( fastify: FastifyInstance, ): Promise { fastify.get('/vods', async function (request, reply) { + const { format } = request.query as { format: 'rss' | 'html' }; + const userId = request.session.get('userId'); + let user = null if (userId !== undefined) { user = await prisma.user.findUnique({ @@ -65,6 +68,34 @@ export default async function vodsRoutes( }, }); + + + // RSS branch + if (format === 'rss') { + const items = vods.map(vod => { + const vtuberNames = vod.vtubers.map(v => v.displayName || v.slug).join(', '); + return { + title: `${vtuberNames} stream on ${vod.streamDate.toDateString()}`, + link: `${env.ORIGIN}/vods/${vod.id}`, + guid: `${env.ORIGIN}/vods/${vod.id}`, + pubDate: vod.streamDate.toUTCString(), + description: vod.notes + ? `${vod.notes}\n\nFeaturing: ${vtuberNames}` + : `Featuring: ${vtuberNames}`, + }; + }); + + return reply + .type('application/rss+xml') + .view('/feed.hbs', { + title: 'future.porn - VODs', + description: 'All VODs and their featured VTubers', + link: `${env.ORIGIN}/vods`, + items, + }, { layout: 'layouts/xml.hbs' }); + } + // rss branch + return reply.viewAsync('vods.hbs', { user, vods, diff --git a/services/our/src/plugins/vtubers.ts b/services/our/src/plugins/vtubers.ts index 08b848c..691411c 100644 --- a/services/our/src/plugins/vtubers.ts +++ b/services/our/src/plugins/vtubers.ts @@ -1,4 +1,4 @@ -import { FastifyInstance } from "fastify" +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify" import { constants } from "../config/constants"; import { PrismaClient, Vtuber } from '../../generated/prisma' @@ -12,12 +12,14 @@ const prisma = new PrismaClient().$extends(withAccelerate()) const hexColorRegex = /^#([0-9a-fA-F]{6})$/; + export default async function vtubersRoutes( fastify: FastifyInstance, ): Promise { - fastify.get('/vtubers', async function (request, reply) { + + const vtuberIndexHandler = async (request: FastifyRequest, reply: FastifyReply) => { const userId = request.session.get('userId') console.log(`userId=${userId}`) @@ -39,10 +41,18 @@ export default async function vtubersRoutes( vtubers, site: constants.site }, { layout: 'layouts/main.hbs' }); - }); + }; + + ['/vtubers', '/vt'].forEach(path => { + fastify.route({ + method: ['GET'], // you could define multiple methods + url: path, + handler: vtuberIndexHandler + }) + }) - fastify.get('/vtubers/new', async function (request, reply) { + fastify.get('/vt/new', async function (request, reply) { const userId = request.session.get('userId'); const user = await prisma.user.findFirst({ @@ -64,7 +74,7 @@ export default async function vtubersRoutes( }, { layout: 'layouts/main.hbs' }) }) - fastify.post('/vtubers/create', async function (request, reply) { + fastify.post('/vt/create', async function (request, reply) { const { displayName, themeColor, @@ -123,7 +133,7 @@ export default async function vtubersRoutes( if (!uppyResult) { request.flash('error', '❌ missing uppyResult') - reply.redirect('/vtubers/new') + reply.redirect('/vt/new') // return reply.status(400).view('vtubers/new.hbs', { // message: '❌ Missing uppyResult', @@ -135,7 +145,7 @@ export default async function vtubersRoutes( if (!themeColor) { request.flash('error', '❌ Missing themeColor') - reply.redirect('/vtubers/new') + reply.redirect('/vt/new') // return reply.status(400).view('vtubers/new.hbs', { // message: '❌ Missing themeColor', // vtubers, @@ -240,14 +250,15 @@ export default async function vtubersRoutes( // successful upload - request.flash('info', `✅ Successfully created vtuber ${vtuber.id}`) - return reply.redirect('/vtubers/new') + request.flash('info', `✅ Successfully created vtuber ${vtuber.id}`) + return reply.redirect('/vt/new') }) - fastify.get('/vtubers/:idOrSlug', async function (request, reply) { + fastify.get('/vt/:idOrSlug', async function (request, reply) { const { idOrSlug } = request.params as { idOrSlug: string }; + const userId = request.session.get('userId'); @@ -257,7 +268,7 @@ export default async function vtubersRoutes( }); if (!idOrSlug) { - return reply.status(400).send({ error: 'Invalid VTuber identifier' }); + return reply.redirect('/vt') } // Determine if it's a CUID (starts with "c" and length of 24) @@ -273,7 +284,11 @@ export default async function vtubersRoutes( ], }, include: { - vods: true, + vods: { + orderBy: { + streamDate: 'desc' + } + } }, }); @@ -289,14 +304,21 @@ export default async function vtubersRoutes( }); - fastify.get('/vtubers/:idOrSlug/rss', async function (request, reply) { + fastify.get('/vt/:idOrSlug/vods', async function (request, reply) { const { idOrSlug } = request.params as { idOrSlug: string }; + const { format } = request.query as { format: 'rss' | 'html' }; + if (!idOrSlug) { - return reply.status(400).send({ error: 'Invalid VTuber identifier' }); + return reply.status(400).send({ error: 'Invalid VTuber identifier ~' }); } + if (format !== 'rss') { + return reply.status(404).send({ error: 'the only available format for this endpoint is rss' }); + } + + // Determine if it's a CUID (starts with "c" and length of 24) const isCuid = /^c[a-z0-9]{23}$/i.test(idOrSlug); @@ -337,7 +359,7 @@ export default async function vtubersRoutes( .view('/feed.hbs', { title, description: vtuber.description || title, - link: `${env.ORIGIN}/vtubers/${vtuber.slug || vtuber.id}`, + link: `${env.ORIGIN}/vt/${vtuber.slug || vtuber.id}`, items, }, { layout: 'layouts/xml.hbs' }); }); diff --git a/services/our/src/views/index.hbs b/services/our/src/views/index.hbs index 0a45fa9..84ef8c6 100644 --- a/services/our/src/views/index.hbs +++ b/services/our/src/views/index.hbs @@ -1,86 +1,78 @@ {{#> main}} - -
-
-

{{ site.title }}

-

{{ site.description }}

-
+ +
{{> navbar}}
- - -
+
- -
-

Latest VODs

-
- - - - - - - - - - - {{#each vods}} - - - - - - - {{/each}} - -
IDVtubersUploaderStatus
{{this.id}} - {{#each this.vtubers}} - {{this.displayName}} - {{/each}} - {{{identicon this.upload.user.id 24}}}{{this.status}}
+
+
+

{{ site.title }}

+

{{ site.description }}

- - - -
-

Latest VTubers

-
- - - - - - - - - - - {{#each vtubers}} - - - - - - {{/each}} - -
IDNameImage
{{this.id}} - {{this.displayName}} -
-
+
+

Latest VODs

+ + + + + + + + + + + {{#each vods}} + + + + + + + {{/each}} + +
IDVtubersUploaderStatus
{{this.id}} + {{#each this.vtubers}} + {{this.displayName}} + {{/each}} + {{{identicon this.upload.user.id 24}}}{{this.status}}
- +
+

Latest VTubers

+ + + + + + - -
-

Latest Streams

+
+ + + {{#each vtubers}} + + + + + + {{/each}} + +
IDNameImage
{{this.id}} + {{this.displayName}} +
+
+ + + {{!-- +
+

Latest Streams

- +
@@ -121,13 +113,11 @@ - - -
-

Latest Tags

+
+

Latest Tags

-
🚧 ID
+
@@ -166,8 +156,7 @@
🚧 ID
-
- +
--}} {{>footer}}
diff --git a/services/our/src/views/layouts/main.hbs b/services/our/src/views/layouts/main.hbs index 8e2d77a..f4e8097 100644 --- a/services/our/src/views/layouts/main.hbs +++ b/services/our/src/views/layouts/main.hbs @@ -11,8 +11,9 @@ - - + --}} - {{{body}}} + + {{!-- JS for Bulma's navbar --}} + + diff --git a/services/our/src/views/partials/navbar.hbs b/services/our/src/views/partials/navbar.hbs index affab18..d3c4a70 100644 --- a/services/our/src/views/partials/navbar.hbs +++ b/services/our/src/views/partials/navbar.hbs @@ -1,45 +1,55 @@ -
- -
\ No newline at end of file + {{#if user.id}} + Profile + {{else}} + Log in via Patreon + {{/if}} + + + \ No newline at end of file diff --git a/services/our/src/views/perks.hbs b/services/our/src/views/perks.hbs index 02b88a3..cf939d2 100644 --- a/services/our/src/views/perks.hbs +++ b/services/our/src/views/perks.hbs @@ -1,18 +1,19 @@ {{#> main}} -
+
{{> navbar}}
-
+
-

Perks

+

Perks

-

future.porn is free to use, but to keep the site running we need your help! In return, we offer extra perks +

We need your help to keep the site running! In return, we offer + extra perks to supporters.

- +
@@ -24,78 +25,90 @@ - - - + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + {{!-- @todo add these things - - + + - + - + - + --}}
Feature
View✔️✔️✔️
RSS
API
Torrent Downloads✔️✔️✔️
CDN Downloads✔️✔️
Ad-Free✔️✔️
Upload✔️✔️
Funscripts✔️✔️
Closed Captions✔️✔️
CC Search ✔️✔️
CSV Export ✔️
SQL Export ✔️
vibeui PyTorch Model ✔️
-
+ diff --git a/services/our/src/views/profile.hbs b/services/our/src/views/profile.hbs index 87fe086..5ee0644 100644 --- a/services/our/src/views/profile.hbs +++ b/services/our/src/views/profile.hbs @@ -1,21 +1,23 @@ {{#> main}} -
+
{{> navbar}}
-
+
-
-

Profile

-

ID: {{user.id}}

-

Identicon: {{{identicon user.id 48}}}

-

Roles: {{#each user.roles}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}} -

-

Perks: @todo - {{!-- @todo {{#each user.perks}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}} --}} -

+
+

Profile

+
+

Identicon: {{{identicon user.id 48}}}

+

ID: {{user.id}}

+

Roles: {{#each user.roles}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}} +

+
+ {{!--

Perks: @todo + @todo {{#each user.perks}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}} +

--}}
Logout diff --git a/services/our/src/views/stream.hbs b/services/our/src/views/stream.hbs index c25dc46..1f8d839 100644 --- a/services/our/src/views/stream.hbs +++ b/services/our/src/views/stream.hbs @@ -8,7 +8,7 @@
-
+

{{#each vod.vtubers}} {{this.displayName}}{{#unless @last}}, {{/unless}} diff --git a/services/our/src/views/uploads/list.hbs b/services/our/src/views/uploads/list.hbs index f444ec0..edc8bef 100644 --- a/services/our/src/views/uploads/list.hbs +++ b/services/our/src/views/uploads/list.hbs @@ -4,25 +4,25 @@ crossorigin="anonymous" referrerpolicy="no-referrer"> -
+
{{> navbar }}
-
+
{{#each info}} -
+

{{{this}}}

-
+

{{/each}} -
-

Uploads

+
+

Uploads

- +
diff --git a/services/our/src/views/uploads/new.hbs b/services/our/src/views/uploads/new.hbs index dfd1192..528e997 100644 --- a/services/our/src/views/uploads/new.hbs +++ b/services/our/src/views/uploads/new.hbs @@ -4,52 +4,71 @@ crossorigin="anonymous" referrerpolicy="no-referrer"> -
+
{{> navbar }}
-

Upload Lewdtuber VODs here.

+

Upload

+

Upload Lewdtuber VODs here.

-
+

Instructions: Enter the metadata first. Vtuber name and original stream date is required. Then upload the vod segment(s). Wait for the vod segment(s) to fully upload before pressing the submit button.

-
- - - - - - - - - - - - + +
+ +
+
+ +
+
-
-

- - - {{#if message}}

{{message}}

{{/if}} -
- + +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+ + + {{#if message}} +

{{message}}

+ {{/if}} + + +
+
+ +
+
+ {{> footer }}
@@ -60,6 +79,7 @@ import ImageEditor from 'https://esm.sh/@uppy/image-editor@3.3.3' import AwsS3 from 'https://esm.sh/@uppy/aws-s3@4.2.3' import Form from 'https://esm.sh/@uppy/form@4.1.1' + //import Link from 'https://esm.sh/@uppy/url@4.3.2' // requires companion instance 💢 window.vtuberOptions = {{ safeJson vtubers }} @@ -93,6 +113,7 @@ // 'Authorization': `Bearer @todo` // } //}) + // .use(Link) requires companion instance 💢 .use(AwsS3, { id: 's3Plugin', endpoint: '/', diff --git a/services/our/src/views/uploads/show.hbs b/services/our/src/views/uploads/show.hbs index 15ee220..2bea886 100644 --- a/services/our/src/views/uploads/show.hbs +++ b/services/our/src/views/uploads/show.hbs @@ -15,7 +15,7 @@ -
+
diff --git a/services/our/src/views/vod.hbs b/services/our/src/views/vod.hbs index 6dd7d89..4d92269 100644 --- a/services/our/src/views/vod.hbs +++ b/services/our/src/views/vod.hbs @@ -1,5 +1,5 @@ {{#> main}} - + @@ -35,14 +35,12 @@ } -
+
{{> navbar}} -
- -
+
{{#if vod.hlsPlaylist}}
@@ -63,15 +61,15 @@ {{else}} -
-
+
+
{{icon "processing" 24}} HTTP Live Streaming is processing. -
+
{{/if}}
-
+
{{!--

Details

@@ -98,32 +96,38 @@ type="application/x-mpegURL"> --}} -

- {{#each vod.vtubers}} - {{this.displayName}}{{#unless @last}}, {{/unless}} - {{/each}} - - {{formatDate vod.streamDate}} -

+
{{#if vod.notes}} -

Notes

-
-

{{vod.notes}}

-
- — {{{identicon vod.uploader.id 24}}} ❦ +

Notes

+
+
+
{{vod.notes}}
+
+
+
-
+
{{/if}} -

Downloads

+

Downloads

-

VOD

-
+

VOD

+
{{#if vod.sourceVideo}} {{#if (hasRole "supporterTier1" user)}} @@ -133,7 +137,7 @@ target="_blank">{{icon "download" 24}} Download

{{else}} -

+

{{icon "patreon" 24}} CDN Download @@ -145,22 +149,22 @@ {{#if vod.cidv1}}

IPFS cidv1 {{vod.cidv1}}

{{else}} -
-

Raw Segments

-
+

Raw Segments

+
{{#if vod.segmentKeys}} {{#if (hasRole "supporterTier1" user)}}
    @@ -181,28 +185,28 @@ {{/if}} {{else}} -
    +
    This VOD has no file segments. -
    +
{{/if}} {{!-- end of raw segments --}} -
+
{{else}} -
+
Video Source is processing. -
+
{{/if}} {{!-- end of vod.sourceVideo --}} -

HLS Playlist

-
+

HLS Playlist

+
{{#if vod.hlsPlaylist}} {{#if (hasRole "supporterTier1" user)}} {{signedHlsUrl vod.hlsPlaylist}} @@ -215,26 +219,28 @@

{{/if}} {{else}} -
+
HLS Playlist is processing. -
+
{{/if}} -
+ -

Thumbnail Image

- {{#if vod.thumbnail}} - {{vtuber.displayName}} thumbnail -
- {{else}} -
- Thumbnail is processing. -
- {{/if}} +
+

Thumbnail Image

+ {{#if vod.thumbnail}} + {{vtuber.displayName}} thumbnail +
+ {{else}} +
+ Thumbnail is processing. +
+ {{/if}} +
-

Funscripts (sex toy sync)

-
+
+

Funscripts (sex toy sync)

{{#if vod.funscript}} {{#if (hasRole "supporterTier1" user)}} @@ -259,35 +265,37 @@ {{/if}}
{{else}} -
+
Funscript file is processing. +
+ {{/if}} +
+ +
+

Closed Captions / Subtitles

+ - {{/if}} -
- -

Closed Captions / Subtitles

- + {{else}} -
+
Closed captions are processing. -
+ {{/if}} @@ -295,23 +303,30 @@ {{#if (isModerator user)}} -
-

Moderator section

+
+

Moderator section

+
-

Storyboard Images

- {{#if vod.slvttVTTKey}} - {{icon "download" 24}} slvttVTTKey - {{else}} -
- Storyboard Images are processing -
- {{/if}} +
+

Storyboard Images

+ {{#if vod.slvttVTTKey}} + {{icon "download" 24}} slvttVTTKey + {{else}} +
+ Storyboard Images are processing +
+ {{/if}} +
-

Controls

- +

Controls

+ +
{{/if}} @@ -322,11 +337,11 @@ {{!--

Comments

{{>commentForm}} --}} -
+ {{>footer}} -
+
Upload ID