add /account settings

This commit is contained in:
CJ_Clippy 2025-11-05 20:49:00 -09:00
parent a05bf6de74
commit fe1f318424
83 changed files with 11206 additions and 2 deletions

View File

@ -3,6 +3,7 @@
"redhat.vscode-yaml", "redhat.vscode-yaml",
"jetify.devbox", "jetify.devbox",
"redhat.ansible", "redhat.ansible",
"dotjoshjohnson.xml" "dotjoshjohnson.xml",
"j69.ejs-beautify"
] ]
} }

View File

@ -60,3 +60,6 @@ In other words, pick something for a name and roll with the punches.
### Do what you can, with what you have, where you are at ### Do what you can, with what you have, where you are at
### Use it up, wear it out, make it do, or do without ### Use it up, wear it out, make it do, or do without
### [Just Culture](https://pilotswhoaskwhy.com/2023/05/15/the-power-of-just-culture-killing-the-blame-game/)

View File

@ -80,6 +80,9 @@ model Vod {
sourceAudioCodec String? sourceAudioCodec String?
sourceVideoFps Float? sourceVideoFps Float?
muxAssetId String?
muxPlaybackId String?
// audio analysis // audio analysis
audioIntegratedLufs Float? // Integrated loudness (LUFS-I) audioIntegratedLufs Float? // Integrated loudness (LUFS-I)
audioLoudnessRange Float? // Loudness Range (LRA) audioLoudnessRange Float? // Loudness Range (LRA)

5
services/pocketbase/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.ftp-deploy-sync-state.json
node_modules
pb_data
.DS_Store
.secrets

View File

@ -0,0 +1 @@
# pocketpages-starter-datastar

View File

@ -0,0 +1,94 @@
# Datastar Starter Kit
This starter kit demonstrates how to build realtime web applications using [Datastar](https://github.com/starfederation/datastar) with PocketPages. It includes a live chat demo and counter example.
## Features
- Realtime chat with emoji avatars
- Live counter with instant updates
- Datastar signal management
- Server-sent events (SSE) integration
- Modern UI with Tailwind CSS
## Installation
```bash
npx tiged benallfree/pocketpages/packages/starters/datastar .
cd datastar
npm i
pocketbase serve --dir=pb_data --dev
```
## Setup
The starter kit is pre-configured with the required Datastar setup:
1. **Plugin Configuration** (`+config.js`): Includes the datastar plugin
2. **Script Injection** (`+layout.ejs`): Includes `<%- datastar.scripts() %>` in the `<head>` section
3. **Realtime Integration**: Configured for realtime updates
## Usage
1. Start the server and visit `http://127.0.0.1:8090`
2. Open multiple browser tabs to see realtime updates
3. Send messages in the chat to see them appear instantly across all tabs
4. Click the counter button to see live updates
## Key Components
- **Chat System**: Real-time messaging with random emoji avatars
- **Counter**: Simple state management with live updates
- **Datastar Integration**: Server-side signal reading and client-side binding
- **Realtime Broadcasting**: Updates sent to all connected clients
## Datastar Features Demonstrated
- `datastar.patchElements()` - DOM updates
- `datastar.patchSignals()` - Signal management
- `datastar.readSignals()` - Form data reading
- `datastar.realtime.*` - Broadcasting to all clients
- Client-side data binding with `data-bind-*` attributes
- Event handling with `data-on-click` attributes
## dev notes
### S3 has some jenk
cache s3 requests at the proxy level
https://github.com/pocketbase/pocketbase/issues/28#issuecomment-1492516269
https://github.com/pocketbase/pocketbase/discussions/4332#discussioncomment-8450890
### search
https://www.meilisearch.com/
### horizontal scaling
https://github.com/fondoger/pocketbase (pg fork)
https://github.com/litesql/pocketbase-ha
### Data migrations
https://github.com/pocketbase/pocketbase/discussions/2836#discussioncomment-6340103
### FTS
https://github.com/pocketbuilds/fts?tab=readme-ov-file#readme
### plugins
https://pocketbuilds.com/
## Deployments
how the pros do it
https://github.com/benallfree/pocketpages/blob/5bc48d4f8df75b2f78ca61fa18c792d814b926e8/packages/starters/deploy-pockethost-manual/deploy-pockethost.ts#L5

2868
services/pocketbase/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "futureporn",
"version": "3.0.3",
"private": true,
"description": "Dedication to the preservation of lewdtuber history",
"license": "Unlicense",
"scripts": {
"deploy": "phio deploy"
},
"dependencies": {
"jsonwebtoken": "^9.0.2",
"pg": "^8.16.3",
"pocketpages": ">=0.22.3",
"pocketpages-plugin-auth": "^0.2.2",
"pocketpages-plugin-datastar": ">=0.2.0",
"pocketpages-plugin-micro-dash": "^0.1.0"
},
"files": [
"pb_hooks",
"*.md"
],
"devDependencies": {
"phio": "^0.3.5"
}
}

View File

@ -0,0 +1,37 @@
/// <reference path="../pb_data/types.d.ts" />
// @see https://github.com/pocketbase/pocketbase/discussions/6287
// NOTE: this script must be run from the project root
// ./pocketbase import /path/to/your/data.json YOUR_COLLECTION
$app.rootCmd.addCommand(new Command({
use: "import",
run: (cmd, args) => {
const data = require(args[0])
const collection = $app.findCollectionByNameOrId(args[1]);
// console.log(`data`, data, 'collection', collection);
$app.runInTransaction((txApp) => {
let record;
for (let item of data) {
// check https://pocketbase.io/docs/js-records/
record = new Record(collection)
// bulk set the item field values
//
// note: if you want to set predefined values for "autodate" type fields
// you'll need to manually load them with record.setRaw("created", val)
record.load(item)
// call txApp.saveNoValidate for better performance
// but be aware that the fields validators will be skipped and could result in data integrity issues
// if you haven't checked and verified the imported data beforehand
txApp.save(record)
}
})
},
}))

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
<%=meta('title') || '~~~~~' %>
</title>
<meta name="description" content="<%=meta('description') || 'aaaaa'%>" />
<meta property="og:title" content="<%=meta('title') || 'Futureporn.net'%>" />
<meta property="og:description" content="<%=meta('description') %>" />
<meta property="og:image" content="<%=meta('image') || '/android-chrome-512x512.png'%>" />
<meta property="og:url" content="<%=meta('path') ? `${meta('path')}` : meta('url') || `${request.url}`%>" />
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
<link rel="alternate" type="application/rss+xml" title="Futureporn VODs (RSS)" href="/vods/feed.xml">
<link rel="alternate" type="application/rss+xml" title="Futureporn VODs (Atom)" href="/vods/rss.xml">
<link rel="alternate" type="application/json" title="Futureporn VODs" href="/vods/feed.json">
<%- slots.head %> <%- datastar.scripts({realtime: true}) %>
</head>
<body class="h-full bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 font-sans antialiased">
<div class="min-h-full flex flex-col">
<main class="flex-1 container mx-auto px-4 py-8 sm:px-6 lg:px-8">
<div class="max-w-4xl mx-auto">
<div class="space-y-8">
<!-- Header Section -->
<nav class="level">
<!-- Left side -->
<div class="level-left">
<div class="level-item">
<p class="subtitle is-5"><a href="/"><strong>Futureporn.net</strong></a></p>
</div>
</div>
<div class="level-right">
<% if (auth) { %>
<div class="level-item">
<a href="/account">Account</a>
</div>
<div class="level-item">
<a href="/auth/logout">Logout</a>
</div>
<% } else { %>
<div class="level-item">
<a href="/auth/login">Login</a>
</div>
<% } %>
</div>
</nav>
<div class="notification is-info" data-signals='{"showNotif": false}' data-show="$showNotif">
<button class="delete" data-on-click="$showNotif = false"></button>
<span data-text="$message"></span>
</div>
<%- slots.body || slot%>
</div>
</div>
</main>
<footer class="footer mt-5">
<div class="content has-text-centered">
<p>
<strong>Futureporn <%= data.version %></strong> made with love by <a href="https://t.co/I8p0oH0AAB">@CJ_Clippy</a>.
</p>
</div>
</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function ({ meta, redirect, request, auth }) {
meta('title', 'Futureporn.net')
meta('description', 'Dedication to the preservation of Lewdtuber history')
meta('image', 'https://example.com/about-preview.jpg')
const cookies = request.cookies()
// console.log('cookies as follows')
// console.log(JSON.stringify(cookies))
// console.log('auth as follows')
// console.log(auth)
let user;
if (auth) {
console.log('request.auth is present id:', auth.get('id'))
user = $app.findFirstRecordByData('users', 'id', auth.id);
}
return { user, version: require(`../../../package.json`).version }
}
// module.exports = (api, next) => {
// const { auth, redirect } = api
// if (!auth) {
// return redirect('/auth/login', {
// message: 'You must be logged in to access this page',
// })
// }
// next()
// }

View File

@ -0,0 +1,9 @@
module.exports = (api, next) => {
const { auth, redirect } = api
if (!auth) {
return redirect('/auth/login', {
message: 'You must be logged in to access this page',
})
}
next()
}

View File

@ -0,0 +1,36 @@
<h2 class="title is-2">Account Overview</h2>
<p>Welcome back, <strong><%= auth.get('name') %></strong>.</p>
<h3 class="title is-3 mt-5">Patreon Information</h3>
<div class="notification is-info is-light">
Please note: Patron status updates may take up to one minute to synchronize.
</div>
<p>
<strong>Role:</strong>
<% if (auth.get('patron')) { %>
Patron
<% } else { %>
Not a Patron
<a class="ml-3" target="_blank" href="https://patreon.com/cj_clippy">Become a Patron</a>
<% } %>
</p>
<p><strong>Patreon ID:</strong> <%= auth.get('patreonId') %></p>
<div class="mt-5">
<h3 class="title is-3">Account Settings</h3>
<label class="checkbox">
<input class="checkbox" type="checkbox" name="publicUsername" data-bind="publicUsername" data-on-input__debounce.300ms.leading="@patch('/api/user/settings')">
Show <%= auth.get('name') %> on the <a href="/patrons">patrons page</a>
</label>
<div id="results" class="mt-5"></div>
<!-- <br />
<input class="button" type="submit" value="Save" /> -->
</div>

View File

@ -0,0 +1,30 @@
<script server>
let error = null
const methods = pb().collection('users').listAuthMethods();
dbg({
methods
})
const {
providers
} = methods.oauth2
</script>
<% if (error) { %>
<mark><%= error %></mark>
<% } %>
<% if (!methods.oauth2.enabled) { %>
<mark>OAuth2 login is disabled. See README.md for details.</mark>
<% } else {%>
<% providers.forEach(provider => { %>
<form method="POST" action="/auth/oauth/login">
<input type="hidden" name="provider" value="<%=provider.name%>">
<button class="button" type="submit">Log in with <%=provider.displayName%></button>
</form>
<% }) %>
<% if (providers.length === 0) { %>
<mark>No OAuth2 providers enabled. See README.md for details.</mark>
<% } %>
<% } %>

View File

@ -0,0 +1,8 @@
<h2 class="title is-2">Login</h2>
<p>Log in to Futureporn using a Patreon account.</p>
<p>Logging in provides access to your patron perks.</p>
<p>To become a patron, visit <a href="https://patreon.com/cj_clippy">CJ_Clippy on Patreon</a>.</p>
<hr />
<%- include('oauth2.ejs') %>

View File

@ -0,0 +1,4 @@
<script server>
signOut()
redirect(`/`)
</script>

View File

@ -0,0 +1,67 @@
<script server>
const {
state,
code
} = params
let error = null
let authData = null
try {
authData = signInWithOAuth2(state, code)
dbg("the shit user id is " + authData.record.id)
if (!authData.record.id) {
throw new Error('invalid authData. authData must contain a record.id');
}
if (!authData.record.created) {
throw new Error('invalid authData. missing record.created')
}
let user = $app.findRecordById('users', authData.record.id)
// if user is new, give them a free trial
const userCreatedAt = new Date(authData.record.created).valueOf();
const anHourAgo = Date.now() - 60 * 60 * 1000; // 1 hour in ms
dbg(`comparing ${userCreatedAt} > ${anHourAgo}`);
if (userCreatedAt > anHourAgo) {
dbg("THE USER IS NEW");
user.set('patron', true);
} else {
dbg("the user is NOT new");
}
console.log(JSON.stringify(authData, null, 2))
dbg("authData:", stringify(authData))
user.set('patreonAccessToken', authData.meta.accessToken);
// user.set('patreonId', authData.meta.id); // we do this via the admin UI
$app.save(user);
// patreon id can be found in _externalAuths system table
// Here we set the patreonId.
// If we're seeing this user for the first time,
// unconditionally grant them patron status.
// The backend later removes the status if they aren't subscribed
// let user = $app.findRecordById('user', )
response.redirect('/')
} catch (e) {
error = e.message
}
</script>
<% if (error) { %>
<mark>
<%=error%>
</mark>
<% } %>

View File

@ -0,0 +1,23 @@
<script server>
let error = null
if (request.method === 'POST') {
let provider
try {
provider = body().provider
const url = requestOAuth2Login(provider)
console.log('url is as follows')
console.log(url)
response.redirect(url)
} catch (error) {
console.log("FAILURE! We failed to get the OAuth2Login from provider", provider)
error = error.message
}
}
</script>
<% if (error) { %>
<p>we got an err</p>
<mark>
<%=error%>
</mark>
<% } %>

View File

@ -0,0 +1,10 @@
Hello humans
futureporn.net
future.porn
R18. For adults only.
Dedication to the preservation of VTuber history
made with love by CJ_Clippy cj@futureporn.net

View File

@ -0,0 +1,23 @@
<% if (!data?.user?.get('patron')) { %>
<div class="notification is-info">
Thank you for your support on <a href="https://patreon.com/cj_clippy">Patreon!</a>
</div>
<% } %>
<ul class="mt-5">
<li class="mb-2">
<a class="button" href="/vt">
<h2 class="title is-2">VTubers</h2>
</a>
</li>
<li class="mb-2">
<a class="button" href="/vods">
<h2 class="title is-2">Vods</h2>
</a>
</li>
<li class="mb-2">
<a class="button" href="/patrons">
<h2 class="title is-2">Patrons</h2>
</a>
</li>
</ul>

View File

@ -0,0 +1,64 @@
<p></p>
<div class="menu">
<p class="subtitle">Thank you to our wonderful patrons who keep this site running</p>
<ul class="menu-list">
<li>Tyrs</li>
<li>StubbstheZombie</li>
<li>Delques1843</li>
<li>Cray Shay</li>
<li>Jay black</li>
<li>E T 4321</li>
<li>ProfessionalUwU</li>
<li>LzyAsn</li>
<li>RettichDerGeile</li>
<li>Vladislav Šlechta</li>
<li>FluffyPenguins527</li>
<li>Enderwolf</li>
<li>Kieren</li>
<li>Captain Highlighter</li>
<li>Jonas andreas Gulbrandsen-Efteland</li>
<li>Drefsab</li>
<li>Randernizer</li>
<li>sheldmaster</li>
<li>Joe Thelizard</li>
<li>IshikawaTXC</li>
<li>Alex</li>
<li>BioAct1ve</li>
<li>AverageYuriEnjoyer</li>
<li>Virted</li>
<li>MechiPlat</li>
<li>JustSleep</li>
<li>Cjyoubusta</li>
<li>Bravo The Chonk</li>
<li>LaoPao</li>
<li>Toffse</li>
<li>Rubén Ruiz</li>
<li>Brewhλwk</li>
<li>BusterMachine7</li>
<li>Lucas</li>
<li>Jogabbagabba</li>
<li>Tony</li>
<li>Mydus</li>
<li>Wubb wubb</li>
<li>François d'Aruna</li>
<li>Cheese And Crackers</li>
<li>Shift</li>
<li>Addable Gravy61</li>
<li>Heiko7761</li>
<li>DustyGreenTea</li>
<li>Lolithia</li>
<li>Aicome 2000</li>
<li>The Gameinator</li>
<li>Dice Loynes</li>
<li>MilkMoblin</li>
<li>Turnip Toss</li>
<li>C4425</li>
<li>TheSlimiestKing</li>
<li>R4z0rSh4rP</li>
<li>Lumbar247</li>
<li>Vilkki</li>
<li>Chiko</li>
<li>Djinn</li>
</ul>
</div>

View File

@ -0,0 +1,10 @@
# robots.txt for futureporn.net
# Block all web crawlers from accessing admin and private areas
User-agent: *
Disallow: /_/
Disallow: /auth/
Disallow: /api/
# Allow crawlers to access public content
Allow: /

View File

@ -0,0 +1,48 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { request, response } = api;
try {
// Read query params with PocketPages
const query = request.url.query || {};
const page = parseInt(query.page) || 1;
const limit = parseInt(query.limit) || 200;
const offset = (page - 1) * limit;
// Fetch all VODs, sorted by -streamDate
const allVods = $app.findRecordsByFilter('vods', null, '-streamDate');
// Slice according to pagination
const vods = allVods.slice(offset, offset + limit);
// Expand related vtubers
$app.expandRecords(vods, ['vtubers'], null);
// Pagination metadata
const total = allVods.length;
const totalPages = Math.ceil(total / limit);
return {
vods,
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1,
},
};
} catch (e) {
console.error('Error fetching VODs:', e.message);
if (e.message.match(/no rows/)) {
return response.html(404, 'VODs not found');
} else {
return response.html(500, 'Unknown internal error while fetching VODs');
}
}
};

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vod = $app.findFirstRecordByData('vods', 'id', params.id);
$app.expandRecord(vod, ["vtubers"], null);
return { vod };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VOD not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vod')
}
}
};

View File

@ -0,0 +1,72 @@
<div class="players" <% if (data?.user?.get('patron')) { %> data-signals="{'selected':'cdn1'}" <% } else { %> data-signals="{'selected':'cdn2'}" <% } %>>
<!-- CDN2 Player (B2) -->
<div class="b2-player" data-show="$selected == 'cdn2'">
<video controls <% if (!data?.user?.get('patron')) { %> preload="none" <% } %>>
<% if (data.vod?.get('videoSrcB2')) { %>
<source src="<%= data.vod?.get('videoSrcB2') %>" type="video/mp4">
<% } else { %>
Your browser does not support the video tag.
<% } %>
</video>
</div>
<!-- CDN1 Player (Mux, patrons only) -->
<% if (data?.user?.get('patron')) { %>
<div class="mux-player" data-show="$selected == 'cdn1'">
<script src="https://cdn.jsdelivr.net/npm/@mux/mux-player" defer></script>
<mux-player playback-id="<%= data.vod?.get('muxPlaybackId') %>" playback-token="<%= data.vod?.get('muxPlaybackToken') %>"></mux-player>
</div>
<% } %>
</div>
<!-- Player toggle buttons -->
<nav class="level mt-5">
<div class="level-left">
<% if (data?.user?.get('patron')) { %>
<button class="button is-success" data-on-click="$selected = 'cdn1'">CDN1 player</button>
<% } else { %>
<button disabled class="button is-danger">CDN1 player (patrons only)</button>
<% } %>
<button class="button is-success" data-on-click="$selected = 'cdn2'">CDN2 player</button>
</div>
</nav>
<!-- VOD Details -->
<div class="vod-details box mt-5">
<p><b>VOD ID:</b> <%= data.vod?.id ?? 'N/A' %></p>
<p>
<b>VTubers:</b>
<% const vtubers = data.vod?.get('expand')?.vtubers ?? []; %>
<% vtubers.forEach((vtuber, index) => { %>
<% if (vtuber) { %>
<a href="/vt/<%= vtuber.get('slug') ?? '#' %>">
<%= vtuber.get('displayName') ?? 'Unknown VTuber' %>
</a><%= index < vtubers.length - 1 ? ', ' : '' %>
<% } %>
<% }) %>
<% if (vtubers.length === 0) { %>None<% } %>
</p>
<p><b>Stream Date:</b> <%= data.vod?.get('streamDate') ?? 'Unknown date' %></p>
<% if (data.vod?.get('announceUrl')) { %>
<p>
<b>Announce URL:</b>
<a href="<%= data.vod?.get('announceUrl') %>"><%= data.vod?.get('announceUrl') %></a>
</p>
<% } %>
<% if (data.vod?.get('ipfsCid')) { %>
<p><b>IPFS CID:</b> <%= data.vod?.get('ipfsCid') %></p>
<% } %>
<% if (data.vod?.get('notes')) { %>
<p><b>Notes:</b></p>
<pre class="p-2"><%= data.vod?.get('notes') %></pre>
<% } %>
</div>
<div class="mb-5"></div>

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate');
$app.expandRecords(vods, ["vtubers"], null);
// vods.expandedAll("vtubers");
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,50 @@
<%#
index.ejs — Atom Feed Generator (Atom 1.0)
Expects: data.vods = [{ id, title, summary, streamDate }]
%>
<%
const siteBase = "https://futureporn.net";
const feedSelf = `${siteBase}/vods/feed.xml`;
const updated = new Date().toISOString();
response.header('Content-Type', 'application/atom+xml')
%>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id><%= siteBase %></id>
<title>Futureporn.net</title>
<updated><%= updated %></updated>
<generator> </generator>
<author>
<name>CJ_Clippy</name>
<email>cj@futureporn.net</email>
<uri><%= siteBase %></uri>
</author>
<link rel="alternate" href="<%= siteBase %>" />
<link rel="self" href="<%= feedSelf %>" />
<subtitle><%= meta('description') %></subtitle>
<logo><%= siteBase %>/images/futureporn-icon.png</logo>
<icon><%= siteBase %>/favicon.ico</icon>
<% for (const vod of data.vods) { %>
<%
const url = `${siteBase}/vods/${vod.id}`;
%>
<entry>
<title type="html">
<![CDATA[ <%= vod.title %> ]]>
</title>
<id><%= url %></id>
<link href="<%= url %>" />
<updated><%= new Date(vod.updated) %></updated>
<% if (vod.notes) { %>
<summary type="html">
<![CDATA[ <%= vod.notes %> ]]>
</summary>
<% } %>
</entry>
<% } %>
</feed>

View File

@ -0,0 +1,72 @@
<h2 class="title is-2">VODs</h2>
<% if (Array.isArray(data.vods) && data.vods.length > 0) { %>
<div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>Stream Date</th>
<th>VTubers</th>
</tr>
</thead>
<tbody>
<% for (const vod of data.vods) { %>
<tr>
<td>
<a href="/vods/<%= vod?.id %>" class="is-small is-link">
<%= vod?.get ? vod.get('streamDate') : vod?.streamDate ?? 'Unknown date' %>
</a>
</td>
<td>
<% const vtubers = vod?.get ? vod.get('expand')?.vtubers ?? [] : vod?.vtubers ?? []; %>
<% if (vtubers.length > 0) { %>
<% for (let i = 0; i < vtubers.length; i++) { %>
<%= vtubers[i]?.get ? vtubers[i].get('displayName') : vtubers[i]?.displayName ?? 'Unknown' %>
<%= i < vtubers.length - 1 ? ', ' : '' %>
<% } %>
<% } else { %>
None
<% } %>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<% if (data.pagination && data.pagination.totalPages > 1) { %>
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<% const currentPage = data.pagination.page; %>
<% const totalPages = data.pagination.totalPages; %>
<!-- Previous Page -->
<% if (data.pagination.hasPrev) { %>
<a class="pagination-previous" href="?page=<%= currentPage - 1 %>&limit=<%= data.pagination.limit %>">Previous</a>
<% } else { %>
<a class="pagination-previous" disabled>Previous</a>
<% } %>
<!-- Next Page -->
<% if (data.pagination.hasNext) { %>
<a class="pagination-next" href="?page=<%= currentPage + 1 %>&limit=<%= data.pagination.limit %>">Next</a>
<% } else { %>
<a class="pagination-next" disabled>Next</a>
<% } %>
<!-- Page Numbers -->
<ul class="pagination-list">
<% for (let i = 1; i <= totalPages; i++) { %>
<li>
<a class="pagination-link <%= i === currentPage ? 'is-current' : '' %>" href="?page=<%= i %>&limit=<%= data.pagination.limit %>">
<%= i %>
</a>
</li>
<% } %>
</ul>
</nav>
<% } %>
<% } else { %>
<p>No VODs available.</p>
<% } %>

View File

@ -0,0 +1,25 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vtuber = $app.findFirstRecordByData('vtubers', 'slug', params.slug);
$app.expandRecord(vtuber, ["vods"], null);
// console.log(JSON.stringify(vtuber))
return { vtuber };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VTuber not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vtuber')
}
}
};

View File

@ -0,0 +1,3 @@
<% if (data.vtuber) { %>
<%- include('vtuber.ejs', { vtuber: data.vtuber }) %>
<% } %>

View File

@ -0,0 +1,8 @@
<script server>
const
vtubers = $app.findRecordsByFilter('vtubers')
</script>
<h3>Vtubers</h3>
<%- include('vtuber-list.ejs', { vtubers }) %>

View File

@ -0,0 +1,14 @@
module.exports = {
debug: false,
plugins: [
'pocketpages-plugin-ejs',
'pocketpages-plugin-datastar',
'pocketpages-plugin-realtime',
'pocketpages-plugin-auth',
'pocketpages-plugin-js-sdk',
'pocketpages-plugin-micro-dash',
'../../../src/plugins/patreon'
],
}

View File

@ -0,0 +1,11 @@
<div id="vtuber-list">
<% for (const vtuber of vtubers) { %>
<div class="">
<span class="">
<a href="/vt/<%= vtuber.get('slug') %>">
<%= vtuber.get('displayName') %>
</a>
</span>
</div>
<% } %>
</div>

View File

@ -0,0 +1,62 @@
<div id="vtuber" class="">
<div class="flex items-center justify-between">
<!-- VTuber Image -->
<figure class="image is-128x128">
<img src="/api/files/vtubers/<%= data.vtuber?.id %>/<%= data.vtuber?.image %>?thumb=128x128" alt="<%= data.vtuber?.get?.('displayName') || 'VTuber' %>" />
</figure>
<!-- VTuber Name -->
<span class="title is-6">
<%= data.vtuber?.displayName || data.vtuber?.get?.('displayName') || 'Unknown VTuber' %>
</span>
<section class="section">
<h3 class="title is-4 mb-4">VODs</h3>
<%
const vods = data.vtuber.get('expand')?.vods || [];
if (vods.length > 0) {
%>
<div class="columns is-multiline">
<% for (const vod of vods) { %>
<div class="column is-one-quarter-desktop is-half-tablet is-full-mobile">
<a href="/vods/<%= vod.get('id') %>" class="box has-text-centered">
<!-- Thumbnail -->
<% if (vod.get('thumbnail')) { %>
<figure class="image is-16by9 mb-2">
<img src="/api/files/vods/<%= vod.get('id') %>/<%= vod.get('thumbnail') %>?thumb=400x225" alt="VOD thumbnail for <%= vod.get('id') %>" />
</figure>
<% } else { %>
<div class="has-background-grey-lighter py-6 mb-2">
<span class="has-text-grey">No thumbnail</span>
</div>
<% } %>
<!-- Title / ID -->
<p class="is-size-6 has-text-weight-semibold">
<%= vod.get('title') || vod.get('id') %>
</p>
<!-- Date -->
<% if (vod.get('streamDate')) { %>
<p class="is-size-7 has-text-grey">
<%= new Date(vod.get('streamDate')).toLocaleDateString() %>
</p>
<% } %>
</a>
</div>
<% } %>
</div>
<% } else { %>
<p>No VODs available for this VTuber.</p>
<% } %>
</section>
</div>
</div>

View File

@ -0,0 +1,6 @@
<%
response.header("Content-Type",
"application/json");
response.html(410,
"");
%>

View File

@ -0,0 +1,41 @@
<script server>
// const {
// request,
// response
// } = api;
const cookies = request.cookies()
let user;
if (auth) {
console.log('request.auth is present id:', auth.get('id'))
user = $app.findFirstRecordByData('users', 'id', auth.id);
}
if (!user) {
datastar.patchSignals(stringify({
showNotif: true
}))
return response.html(401, "Auth required")
}
console.log('signals as follows')
const signals = datastar.readSignals(request, {})
user.set('publicUsername', signals.publicUsername);
// Determine the publicUsername status
const publicStatus = user.get('publicUsername') ?
'Your username is now <b>public</b>.' :
'Your username is now <b>private</b>.';
// Update the page with a verbose message
datastar.patchElements(`
<div id="results" class="notification is-success mt-5">
Saved! ${publicStatus}
</div>
`);
</script>

View File

@ -0,0 +1,6 @@
<%
response.header("Content-Type",
"application/json");
response.html(410,
"");
%>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/feed.json', 301) %>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/feed.xml', 301) %>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/rss.xml', 301) %>

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate');
$app.expandRecords(vods, ["vtubers"], null);
// vods.expandedAll("vtubers");
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,29 @@
<%#
/**
* index.ejs - Generates a JSON feed
* Expects: data.vods = [{ id, title, url, description, publishedAt, ... }]
*/
%>
<%
const feed = {
version: "https://jsonfeed.org/version/1",
title: `${meta('title')} VODs`,
home_page_url: "https://futureporn.net",
feed_url: "https://futureporn.net/vods/feed.json",
description: meta('description'),
icon: "https://futureporn.net/images/futureporn-icon.png",
author: {
name: "CJ_Clippy",
url: "https://futureporn.net"
},
items: data.vods.map(vod => ({
content_html: "",
url: `https://futureporn.net/vods/${vod.id}`,
title: vod.title,
summary: vod.notes || vod.title,
image: vod.thumbnail,
date_modified: vod.updated
}))
};
%><%- JSON.stringify(feed, null, 2) %>

View File

@ -0,0 +1,28 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate', 99);
$app.expandRecords(vods, ["vtubers"], null);
console.log(JSON.stringify(vods, null, 2))
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,58 @@
<%#
index.ejs — ATOM Feed Generator
Expects: data.vods = [{ id, title, notes, thumbnail, streamDate }]
%>
<%=
response.header("Content-Type", "application/atom+xml");
// Build Atom XML as a string
let atom = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Futureporn.net</title>
<link href="<%= env('ORIGIN') %>"/>
<updated>${new Date().toISOString()}</updated>
<id><%= env('ORIGIN') %></id>
<author>
<name>CJ_Clippy</name>
<email>cj@futureporn.net</email>
</author>`;
for (const vod of data.vods) {
const url = `${env('ORIGIN')}/vods/${vod.id}`;
const entryTitle = ((vod?.get('expand')?.vtubers?.map(vt => vt.get('displayName')) || [])
.join(', ') || vod.get('streamDate'));
atom += `
<entry>
<title>
<![CDATA[${entryTitle}]]>
</title>
<link href="${url}" />
<id>${url}</id>
<updated>${vod.get('streamDate')}</updated>`;
if (vod.get('notes')) {
atom += `
<summary type="html">
<![CDATA[${vod.get('notes')}]]>
</summary>`;
}
if (vod.get('thumbnail')) {
atom += `
<content type="image">
<![CDATA[<img src="${vod.get('thumbnail')}" />]]>
</content>`;
}
atom += `
</entry>`;
}
atom += `
</feed>`;
// Send the Atom feed
response.html(200, '<p>HELLO</p>');
%>

View File

@ -0,0 +1,28 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate', 99);
$app.expandRecords(vods, ["vtubers"], null);
console.log(JSON.stringify(vods, null, 2))
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,56 @@
<%#
index.ejs — RSS Feed Generator (RSS 2.0)
Expects: data.vods = [{ id, title, notes, thumbnail, streamDate }]
%>
<%
response.header("Content-Type", "application/rss+xml")
// Build RSS XML as a string
let rss = `<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Futureporn.net</title>
<link>https://futureporn.net</link>
<description>Dedication to the preservaton of lewdtuber history</description>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator> </generator>
<language>en</language>
<image>
<title>Futureporn.net</title>
<url>https://futureporn.net/images/futureporn-icon.png</url>
<link>https://futureporn.net</link>
</image>`;
for (const vod of data.vods) {
const url = `${env('ORIGIN')}/vods/${vod.id}`;
rss += `
<item>
<title><![CDATA[${
((vod?.get('expand')?.vtubers?.map(vt => vt.get('displayName')) || []).join(', ')
|| vod.get('streamDate'))
}]]></title>
<link>${url}</link>
<guid>${url}</guid>
<pubDate>${vod.get('streamDate')}</pubDate>`;
if (vod.notes) {
rss += `
<description><![CDATA[${vod.notes}]]></description>`;
}
if (vod.thumbnail) {
rss += `
<enclosure url="${vod.thumbnail}" length="0" type="image/png" />`;
}
rss += `
</item>`;
}
rss += `
</channel>
</rss>`;
// Send the RSS
response.html(200, rss);
%>

View File

@ -0,0 +1 @@
require('pocketpages')

View File

@ -0,0 +1,79 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "date3986823616",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url3307693472",
"name": "announceUrl",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_144770472",
"indexes": [],
"listRule": null,
"name": "vod",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472");
return app.delete(collection);
})

View File

@ -0,0 +1,57 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_3872109612",
"indexes": [],
"listRule": null,
"name": "vtuber",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612");
return app.delete(collection);
})

View File

@ -0,0 +1,48 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1731158936",
"max": 0,
"min": 0,
"name": "displayName",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(2, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2560465762",
"max": 0,
"min": 0,
"name": "slug",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("text1731158936")
// remove field
collection.fields.removeById("text2560465762")
return app.save(collection)
})

View File

@ -0,0 +1,352 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_144770472",
"hidden": false,
"id": "relation3825607268",
"maxSelect": 999,
"minSelect": 0,
"name": "vods",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "file3309110367",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "image",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(5, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1288386755",
"name": "chaturbate",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(6, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url376077238",
"name": "twitter",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(7, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2042351438",
"name": "patreon",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(8, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1928100768",
"name": "twitch",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(9, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url847873150",
"name": "tiktok",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(10, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1505654846",
"name": "onlyfans",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(11, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url4034435380",
"name": "youtube",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(12, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3056519709",
"name": "linktree",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(13, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2609473773",
"name": "carrd",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(14, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2799872019",
"name": "fansly",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(15, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1736283674",
"name": "pornhub",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(16, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1322392000",
"name": "discord",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(17, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3191313207",
"name": "reddit",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(18, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3425232140",
"name": "throne",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(19, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2225635011",
"name": "instagram",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(20, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1802815712",
"name": "facebook",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(21, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url4055117536",
"name": "merch",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(22, new Field({
"convertURLs": false,
"hidden": false,
"id": "editor1843675174",
"maxSize": 0,
"name": "description",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
}))
// add field
collection.fields.addAt(23, new Field({
"hidden": false,
"id": "number2960516043",
"max": null,
"min": null,
"name": "themeColor",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("relation3825607268")
// remove field
collection.fields.removeById("file3309110367")
// remove field
collection.fields.removeById("url1288386755")
// remove field
collection.fields.removeById("url376077238")
// remove field
collection.fields.removeById("url2042351438")
// remove field
collection.fields.removeById("url1928100768")
// remove field
collection.fields.removeById("url847873150")
// remove field
collection.fields.removeById("url1505654846")
// remove field
collection.fields.removeById("url4034435380")
// remove field
collection.fields.removeById("url3056519709")
// remove field
collection.fields.removeById("url2609473773")
// remove field
collection.fields.removeById("url2799872019")
// remove field
collection.fields.removeById("url1736283674")
// remove field
collection.fields.removeById("url1322392000")
// remove field
collection.fields.removeById("url3191313207")
// remove field
collection.fields.removeById("url3425232140")
// remove field
collection.fields.removeById("url2225635011")
// remove field
collection.fields.removeById("url1802815712")
// remove field
collection.fields.removeById("url4055117536")
// remove field
collection.fields.removeById("editor1843675174")
// remove field
collection.fields.removeById("number2960516043")
return app.save(collection)
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"name": "vods"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"name": "vod"
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"name": "vtubers"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"name": "vtuber"
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,139 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation1668006755",
"maxSelect": 1,
"minSelect": 0,
"name": "uploader",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(4, new Field({
"convertURLs": false,
"hidden": false,
"id": "editor18589324",
"maxSize": 0,
"name": "notes",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
}))
// add field
collection.fields.addAt(5, new Field({
"hidden": false,
"id": "json2596236420",
"maxSize": 0,
"name": "segmentKeys",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "file3785949232",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "sourceVideo",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "file651090729",
"maxSelect": 99,
"maxSize": 0,
"mimeTypes": [],
"name": "segments",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "file3277268710",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "thumbnail",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(9, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"ordering",
"pending",
"approved",
"rejected",
"processing",
"processed"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("relation1668006755")
// remove field
collection.fields.removeById("editor18589324")
// remove field
collection.fields.removeById("json2596236420")
// remove field
collection.fields.removeById("file3785949232")
// remove field
collection.fields.removeById("file651090729")
// remove field
collection.fields.removeById("file3277268710")
// remove field
collection.fields.removeById("select2063623452")
return app.save(collection)
})

View File

@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(10, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3872109612",
"hidden": false,
"id": "relation3093501878",
"maxSelect": 999,
"minSelect": 0,
"name": "vtubers",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("relation3093501878")
return app.save(collection)
})

View File

@ -0,0 +1,46 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("number2960516043")
// add field
collection.fields.addAt(23, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2960516043",
"max": 0,
"min": 0,
"name": "themeColor",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(23, new Field({
"hidden": false,
"id": "number2960516043",
"max": null,
"min": null,
"name": "themeColor",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// remove field
collection.fields.removeById("text2960516043")
return app.save(collection)
})

View File

@ -0,0 +1,124 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("file3785949232")
// remove field
collection.fields.removeById("file651090729")
// remove field
collection.fields.removeById("file3277268710")
// add field
collection.fields.addAt(8, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3785949232",
"max": 0,
"min": 0,
"name": "sourceVideo",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(9, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text651090729",
"max": 0,
"min": 0,
"name": "segments",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(10, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3277268710",
"max": 0,
"min": 0,
"name": "thumbnail",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "file3785949232",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "sourceVideo",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "file651090729",
"maxSelect": 99,
"maxSize": 0,
"mimeTypes": [],
"name": "segments",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "file3277268710",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "thumbnail",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// remove field
collection.fields.removeById("text3785949232")
// remove field
collection.fields.removeById("text651090729")
// remove field
collection.fields.removeById("text3277268710")
return app.save(collection)
})

View File

@ -0,0 +1,67 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(11, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3889096196",
"max": 0,
"min": 0,
"name": "muxAssetId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(12, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1061856600",
"max": 0,
"min": 0,
"name": "muxPlaybackId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(13, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1805914144",
"max": 0,
"min": 0,
"name": "ipfsCid",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text3889096196")
// remove field
collection.fields.removeById("text1061856600")
// remove field
collection.fields.removeById("text1805914144")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(14, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1646005928",
"max": 0,
"min": 0,
"name": "videoSrcB2",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text1646005928")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(15, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1642358089",
"max": 0,
"min": 0,
"name": "muxPlaybackToken",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text1642358089")
return app.save(collection)
})

View File

@ -0,0 +1,24 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"enabled": true
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"enabled": false
}
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,34 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "select66088533",
"maxSelect": 1,
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"free",
"tier1",
"tier2",
"tier3",
"tier4",
"tier5",
"tier6"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("select66088533")
return app.save(collection)
})

View File

@ -0,0 +1,48 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("select66088533")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool66088533",
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "select66088533",
"maxSelect": 1,
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"free",
"tier1",
"tier2",
"tier3",
"tier4",
"tier5",
"tier6"
]
}))
// remove field
collection.fields.removeById("bool66088533")
return app.save(collection)
})

View File

@ -0,0 +1,38 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("bool66088533")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool3858055773",
"name": "patron",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool66088533",
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
// remove field
collection.fields.removeById("bool3858055773")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(9, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1335658467",
"max": 0,
"min": 0,
"name": "patreonId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("text1335658467")
return app.save(collection)
})

View File

@ -0,0 +1,58 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_4199263681");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "json3486377887",
"maxSize": 1,
"name": "pending_tasks",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json907408884",
"maxSize": 1,
"name": "failed_tasks",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_4199263681",
"indexes": [],
"listRule": null,
"name": "queue_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT queue as id,\n SUM(CASE WHEN failed IS '' THEN 1 ELSE 0 END) as pending_tasks,\n SUM(CASE WHEN failed IS NOT '' THEN 1 ELSE 0 END) as failed_tasks\n FROM queue_tasks\n GROUP BY queue;",
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,87 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1488669058");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1797306934",
"max": 0,
"min": 0,
"name": "worker_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_4107420034",
"hidden": false,
"id": "relation1384045349",
"maxSelect": 1,
"minSelect": 0,
"name": "task",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1488669058",
"indexes": [
"CREATE UNIQUE INDEX idx_HSFtKfUArr ON queue_locks (task)",
"CREATE INDEX idx_bn5iw0BzOm ON queue_locks (created)"
],
"listRule": null,
"name": "queue_locks",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,109 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_4107420034");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2147319651",
"max": 0,
"min": 0,
"name": "queue",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "json1384045349",
"maxSize": 2000000,
"name": "task",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "date2659951479",
"max": "",
"min": "",
"name": "failed",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text4016436606",
"max": 0,
"min": 0,
"name": "failed_reason",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_4107420034",
"indexes": [
"CREATE INDEX idx_DjQFazUd5h ON queue_tasks (\n queue,\n failed,\n created\n)",
"CREATE INDEX idx_pUcaFfpCFF ON queue_tasks (\n failed,\n updated\n)"
],
"listRule": null,
"name": "queue_tasks",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,30 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"mappedFields": {
"avatarURL": "",
"id": "patreonId"
}
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"mappedFields": {
"avatarURL": "avatar",
"id": ""
}
}
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(10, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3341984971",
"max": 0,
"min": 0,
"name": "patreonAccessToken",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("text3341984971")
return app.save(collection)
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

View File

@ -0,0 +1 @@
// hello

View File

@ -0,0 +1,957 @@
{
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"license": "Unlicense",
"dependencies": {
"pocketpages-plugin-js-sdk": "0.2.0"
},
"devDependencies": {
"tsdown": "^0.15.12",
"typescript": "^5.7.3"
}
},
"node_modules/@babel/generator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz",
"integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz",
"integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@oxc-project/types": {
"version": "0.95.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.95.0.tgz",
"integrity": "sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@quansync/fs": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz",
"integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"quansync": "^0.2.11"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.45.tgz",
"integrity": "sha512-ddcO9TD3D/CLUa/l8GO8LHzBOaZqWg5ClMy3jICoxwCuoz47h9dtqPsIeTiB6yR501LQTeDsjA4lIFd7u3Ljfw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.45.tgz",
"integrity": "sha512-MBTWdrzW9w+UMYDUvnEuh0pQvLENkl2Sis15fHTfHVW7ClbGuez+RWopZudIDEGkpZXdeI4CkRXk+vdIIebrmg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.45.tgz",
"integrity": "sha512-4YgoCFiki1HR6oSg+GxxfzfnVCesQxLF1LEnw9uXS/MpBmuog0EOO2rYfy69rWP4tFZL9IWp6KEfGZLrZ7aUog==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.45.tgz",
"integrity": "sha512-LE1gjAwQRrbCOorJJ7LFr10s5vqYf5a00V5Ea9wXcT2+56n5YosJkcp8eQ12FxRBv2YX8dsdQJb+ZTtYJwb6XQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.45.tgz",
"integrity": "sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.45.tgz",
"integrity": "sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.45.tgz",
"integrity": "sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.45.tgz",
"integrity": "sha512-ZJDB7lkuZE9XUnWQSYrBObZxczut+8FZ5pdanm8nNS1DAo8zsrPuvGwn+U3fwU98WaiFsNrA4XHngesCGr8tEQ==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^1.0.7"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-zyzAjItHPUmxg6Z8SyRhLdXlJn3/D9KL5b9mObUrBHhWS/GwRH4665xCiFqeuktAhhWutqfc+rOV2LjK4VYQGQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-ia32-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-wODcGzlfxqS6D7BR0srkJk3drPwXYLu7jPHN27ce2c4PUnVVmJnp9mJzUQGT4LpmHmmVdMZ+P6hKvyTGBzc1CA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.45.tgz",
"integrity": "sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/ansis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
}
},
"node_modules/ast-kit": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.3.tgz",
"integrity": "sha512-TH+b3Lv6pUjy/Nu0m6A2JULtdzLpmqF9x1Dhj00ZoEiML8qvVA9j1flkzTKNYgdEhWrjDwtWNpyyCUbfQe514g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.4",
"pathe": "^2.0.3"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/birpc": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz",
"integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"dev": true,
"license": "MIT"
},
"node_modules/diff": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dts-resolver": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.2.tgz",
"integrity": "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.18.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"oxc-resolver": ">=11.0.0"
},
"peerDependenciesMeta": {
"oxc-resolver": {
"optional": true
}
}
},
"node_modules/empathic": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/get-tsconfig": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pocketbase-js-sdk-jsvm": {
"version": "0.25.10004",
"resolved": "https://registry.npmjs.org/pocketbase-js-sdk-jsvm/-/pocketbase-js-sdk-jsvm-0.25.10004.tgz",
"integrity": "sha512-/0RkFa6X4LeKMdv4KCXhC5i2cvpG0elvXInRyk8bApjG8jyyPiNDL6o9VBvgxjl6j8/HcQYf3oKYXmZteqAFvQ==",
"license": "MIT"
},
"node_modules/pocketpages-plugin-js-sdk": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/pocketpages-plugin-js-sdk/-/pocketpages-plugin-js-sdk-0.2.0.tgz",
"integrity": "sha512-/uY1nCz7Zt19C/kyoOraH5ekTZa4PLPpHd0qIEoyf1XBPtEpHL4k7jZE0IPEMu1dH/8RfsKb/YzN52GR+9DdzQ==",
"dependencies": {
"pocketbase-js-sdk-jsvm": "^0.25.10004"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rolldown": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.45.tgz",
"integrity": "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.95.0",
"@rolldown/pluginutils": "1.0.0-beta.45"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.0-beta.45",
"@rolldown/binding-darwin-arm64": "1.0.0-beta.45",
"@rolldown/binding-darwin-x64": "1.0.0-beta.45",
"@rolldown/binding-freebsd-x64": "1.0.0-beta.45",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.45",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.45",
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.45",
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.45",
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.45",
"@rolldown/binding-openharmony-arm64": "1.0.0-beta.45",
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.45",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.45",
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.45",
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.45"
}
},
"node_modules/rolldown-plugin-dts": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.17.3.tgz",
"integrity": "sha512-8mGnNUVNrqEdTnrlcaDxs4sAZg0No6njO+FuhQd4L56nUbJO1tHxOoKDH3mmMJg7f/BhEj/1KjU5W9kZ9zM/kQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.5",
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"ast-kit": "^2.1.3",
"birpc": "^2.6.1",
"debug": "^4.4.3",
"dts-resolver": "^2.1.2",
"get-tsconfig": "^4.13.0",
"magic-string": "^0.30.21"
},
"engines": {
"node": ">=20.18.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"@ts-macro/tsc": "^0.3.6",
"@typescript/native-preview": ">=7.0.0-dev.20250601.1",
"rolldown": "^1.0.0-beta.44",
"typescript": "^5.0.0",
"vue-tsc": "~3.1.0"
},
"peerDependenciesMeta": {
"@ts-macro/tsc": {
"optional": true
},
"@typescript/native-preview": {
"optional": true
},
"typescript": {
"optional": true
},
"vue-tsc": {
"optional": true
}
}
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tinyexec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"dev": true,
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/tsdown": {
"version": "0.15.12",
"resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.15.12.tgz",
"integrity": "sha512-c8VLlQm8/lFrOAg5VMVeN4NAbejZyVQkzd+ErjuaQgJFI/9MhR9ivr0H/CM7UlOF1+ELlF6YaI7sU/4itgGQ8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansis": "^4.2.0",
"cac": "^6.7.14",
"chokidar": "^4.0.3",
"debug": "^4.4.3",
"diff": "^8.0.2",
"empathic": "^2.0.0",
"hookable": "^5.5.3",
"rolldown": "1.0.0-beta.45",
"rolldown-plugin-dts": "^0.17.2",
"semver": "^7.7.3",
"tinyexec": "^1.0.1",
"tinyglobby": "^0.2.15",
"tree-kill": "^1.2.2",
"unconfig": "^7.3.3"
},
"bin": {
"tsdown": "dist/run.mjs"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"@arethetypeswrong/core": "^0.18.1",
"publint": "^0.3.0",
"typescript": "^5.0.0",
"unplugin-lightningcss": "^0.4.0",
"unplugin-unused": "^0.5.0",
"unrun": "^0.2.1"
},
"peerDependenciesMeta": {
"@arethetypeswrong/core": {
"optional": true
},
"publint": {
"optional": true
},
"typescript": {
"optional": true
},
"unplugin-lightningcss": {
"optional": true
},
"unplugin-unused": {
"optional": true
},
"unrun": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unconfig": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.3.3.tgz",
"integrity": "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@quansync/fs": "^0.1.5",
"defu": "^6.1.4",
"jiti": "^2.5.1",
"quansync": "^0.2.11"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsdown",
"dev": "tsdown --watch"
},
"dependencies": {
"pocketpages-plugin-js-sdk": "0.2.0"
},
"devDependencies": {
"tsdown": "^0.15.12",
"typescript": "^5.7.3"
},
"license": "Unlicense"
}

View File

@ -0,0 +1,40 @@
import { type ExtendContextApiContext } from 'pocketpages'
import PocketBase, {
AuthModel,
RecordAuthResponse,
} from 'pocketbase-js-sdk-jsvm'
import { type OAuth2SignInOptions } from 'pocketpages-plugin-auth'
import { PluginFactory } from 'pocketpages'
import { OAuth2ProviderInfo } from 'pocketpages-plugin-auth'
const authPluginFactory: PluginFactory = (config) => {
return {
name: 'patreon',
onExtendContextApi(context: ExtendContextApiContext) {
const { api } = context;
const pb = () => api.pb() as PocketBase
api.savePatreonData = (
authData: AuthModel
) => {
pb.collection('users')
},
api.getPatreonUser = (
authData: AuthModel,
) => {
console.log("AYO we got authData as follo");
console.log(JSON.stringify(authData, null, 2));
// make a request to Patreon
}
},
}
}
export default authPluginFactory;

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"types": [
"pocketbase-jsvm"
],
"strict": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"noImplicitAny": true,
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": [
"src/**/*.ts"
]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: {
index: 'src/index.ts',
},
format: ['cjs'],
clean: true,
outDir: 'dist',
})

View File

@ -0,0 +1,14 @@
[Service]
Type = simple
User = pb
Group = pb
LimitNOFILE = 4096
Restart = always
RestartSec = 5s
StandardOutput = append:/home/pb/pb/std.log
StandardError = append:/home/pb/pb/std.log
WorkingDirectory = /home/pb/pb
ExecStart = /home/pb/pb/pocketbase serve
[Install]
WantedBy = multi-user.target

View File

@ -0,0 +1,8 @@
#!/bin/bash
# This script must run from project root
npx @dotenvx/dotenvx run -f ./.env.development.local -- node ./utils/data_migrations/2025-10-30-v1-export.js ./utils/data_migrations
pocketbase import ./utils/data_migrations/vtubers.json vtubers
pocketbase import ./utils/data_migrations/vods.json vods

View File

@ -0,0 +1,172 @@
/**
* Migration Script: V1 json
* -----------------------------------
* This script migrates VTuber and VOD data from an old Postgres database (V1) into JSON files
* One JSON file per collection, suitable for importing using pocketbase import script
*
* Usage:
* - Ensure environment variables are configured for both databases:
* V1_DB_HOST Hostname of the V1 database (default: "localhost")
* V1_DB_PORT Port of the V1 database (default: "5444")
* V1_DB_USER Username for the V1 database (default: "postgres")
* V1_DB_PASS Password for the V1 database (default: "password")
* V1_DB_NAME Database name for V1 (default: "restoredb")
* DEFAULT_UPLOADER_ID
* An existing user ID in the V2 database that will be set as the uploader for all migrated records.
*
* Usage:
* Run with Node.js:
* $ npx @dotenvx/dotenvx run -f ./.env.local -- node ./2025-10-30-v1-export.js ../pb_data/
*
*/
import pg from 'pg';
import fs from 'fs';
import path from 'path';
const dataDir = process.argv[2]; // e.g., "../../pb_data/"
if (!dataDir) {
console.error("Please provide the output data directory as the first argument.");
process.exit(1);
}
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'
});
async function migrateVtubers() {
console.log('Migrating vtubers...');
const res = await v1.query(`SELECT * FROM vtubers`);
const vtubers = res.rows.map(vt => ({
slug: vt.slug,
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,
}));
console.log('vtubers sample', vtubers.at(0))
const outPath = path.join(dataDir, 'vtubers.json');
fs.writeFileSync(outPath, JSON.stringify(vtubers, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vtubers → ${outPath}`);
}
async function migrateVods() {
console.log('Migrating vods...');
const res = await v1.query(`SELECT * FROM vods`);
const vods = [];
for (const vod of res.rows) {
// Get linked vtubers
const vtuberLinks = await v1.query(
`SELECT vtuber_id FROM vods_vtuber_links WHERE vod_id = $1`,
[vod.id]
);
let vtuberIds = [];
if (vtuberLinks.rows.length > 0) {
const vtuberRes = await v1.query(
`SELECT id FROM vtubers WHERE id = ANY($1)`,
[vtuberLinks.rows.map(r => r.vtuber_id)]
);
vtuberIds = vtuberRes.rows.map(v => v.id).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]
);
// Get Mux asset and playback ID
const muxLink = await v1.query(
`SELECT m.asset_id, m.playback_id
FROM vods_mux_asset_links vmal
JOIN mux_assets m ON vmal.mux_asset_id = m.id
WHERE vmal.vod_id = $1 LIMIT 1`,
[vod.id]
);
const muxAssetId = muxLink.rows[0]?.asset_id || null;
const muxPlaybackId = muxLink.rows[0]?.playback_id || null;
// @chatgpt we need one more
// we need to populate videoSrcB2
// we want the cdn_url from b2_files
// we have to get the link from the vods_video_src_b_2_links table
vods.push({
streamDate: vod.date ?? new Date(),
notes: vod.note,
sourceVideo: (() => {
const u = videoSrcLink.rows[0]?.cdn_url || videoSrcLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
thumbnail: (() => {
const u = thumbLink.rows[0]?.cdn_url || thumbLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
ipfsCid: vod.video_src_hash,
videoSrcB2: videoSrcLink.rows[0]?.cdn_url || null,
muxAssetId,
muxPlaybackId,
});
}
console.log(vods.at(0))
const outPath = path.join(dataDir, 'vods.json');
fs.writeFileSync(outPath, JSON.stringify(vods, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vods → ${outPath}`);
}
async function main() {
try {
await migrateVtubers();
await migrateVods();
} catch (err) {
console.error(err);
} finally {
await v1.end();
}
}
main();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
[
{
"slug": "projektmelody",
"displayName": "ProjektMelody",
"chaturbate": "https://chaturbate.com/projektmelody",
"twitter": "https://twitter.com/projektmelody",
"patreon": "https://www.patreon.com/projektmelody",
"twitch": "https://twitch.tv/projektmelody",
"tiktok": "https://www.tiktok.com/@realprojektmelody",
"onlyfans": "https://onlyfans.com/projektbutt",
"youtube": "https://www.youtube.com/projektmelodyofficial",
"linktree": "https://linktr.ee/projektmelody",
"carrd": null,
"fansly": "https://fansly.com/r/scienceteam",
"pornhub": "https://www.pornhub.com/model/projekt-melody",
"discord": null,
"reddit": "https://www.reddit.com/r/projektmelody/",
"throne": "https://throne.com/realprojektmelody",
"instagram": null,
"facebook": null,
"merch": "https://melody.vshojo.com/",
"description": "Also known as, \"Daddy's little grandpa,\" ProjektMelody is the pioneering hentai cam model who embodies a unique blend of sweetness and perversion. She engages with her audience with remarkable patience and politeness, demonstrating her commitment to fostering an inclusive environment.\nOn her livestreams, ProjektMelody's demeanor can range from overtly sexual to adorably reserved and self-censored. Alongside her captivating presence, she actively promotes pro-social behavior, advocates for sexual education, and emphasizes the importance of kindness.",
"themeColor": "#5C23C0"
},
{
"slug": "el_xox",
"displayName": "el_XoX",
"chaturbate": "https://chaturbate.com/el_xox/",
"twitter": "https://twitter.com/el_XoX34",
"patreon": null,
"twitch": "https://www.twitch.tv/el_xox",
"tiktok": null,
"onlyfans": null,
"youtube": null,
"linktree": null,
"carrd": "https://elxox.carrd.co/",
"fansly": null,
"pornhub": null,
"discord": null,
"reddit": null,
"throne": null,
"instagram": null,
"facebook": null,
"merch": "https://elxox34.com/pages/limited-merch",
"description": null,
"themeColor": "#353FFF"
},
{
"slug": "vexruby",
"displayName": "Vexruby",
"chaturbate": "https://chaturbate.com/vexruby",
"twitter": "https://x.com/vexxxruby",
"patreon": "https://www.patreon.com/ViRoClub",
"twitch": null,
"tiktok": null,
"onlyfans": null,
"youtube": null,
"linktree": null,
"carrd": null,
"fansly": null,
"pornhub": null,
"discord": null,
"reddit": null,
"throne": null,
"instagram": null,
"facebook": null,
"merch": null,
"description": null,
"themeColor": "#f882f5"
},
{
"slug": "athena_airis",
"displayName": "AIRIS ATHENA",
"chaturbate": "https://chaturbate.com/athena_airis/",
"twitter": "https://twitter.com/Athena_Airis",
"patreon": null,
"twitch": "https://www.twitch.tv/athena_airis",
"tiktok": "https://tiktok.com/@athena_airis",
"onlyfans": null,
"youtube": "https://www.youtube.com/channel/UClizqUqSuy26sO01qsPh5bw",
"linktree": "https://linktr.ee/athenaairis",
"carrd": null,
"fansly": "https://fansly.com/Athena-Airis",
"pornhub": null,
"discord": "https://discord.gg/eXPTgua",
"reddit": null,
"throne": null,
"instagram": "https://instagram.com/theathenaairis",
"facebook": null,
"merch": null,
"description": null,
"themeColor": "#dde1ec"
}
]

View File

@ -0,0 +1,43 @@
#!/usr/bin/env node
import PocketBase from 'pocketbase';
import { spawn } from 'node:child_process';
import util from 'node:util';
const spawnAsync = util.promisify(spawn);
if (!process.env.POCKETBASE_PASSWORD) throw new Error('POCKETBASE_PASSWORD missing in env');
if (!process.env.POCKETBASE_USERNAME) throw new Error('POCKETBASE_USERNAME missing in env');
if (!process.env.APPURL) throw new Error('APPURL missing in env');
const pb = new PocketBase('http://localhost:8090');
await pb
.collection("_superusers")
.authWithPassword(process.env.POCKETBASE_USERNAME, process.env.POCKETBASE_PASSWORD);
// change to the production APPURL
await pb.settings.update({
meta: {
appName: 'Futureporn',
appUrl: 'https://futureporn.net',
},
});
// upload the site
spawnAsync('rsync', [
'-avz',
'-e',
'ssh',
'.',
'root@fp:/home/pb/pb'
]);
// @see https://pocketbase.io/docs/api-settings/#update-settings
// put it back to dev app url
await pb.settings.update({
meta: {
appName: 'Futureporn',
appUrl: process.env.APPURL,
},
});