add filterable stream archive table

This commit is contained in:
Chris Grimmett 2024-02-02 15:50:24 -08:00
parent d60c6ac3bb
commit 47e5919893
24 changed files with 1117 additions and 145 deletions

1
.gitignore vendored
View File

@ -146,3 +146,4 @@ dist
# End of https://www.toptal.com/developers/gitignore/api/node
node_modules

View File

@ -9,7 +9,7 @@ services:
ports:
- "9312:9312"
restart: on-failure
command: "client --auth=${CHISEL_AUTH} ${CHISEL_SERVER} R:8899:cluster0:9094 R:8901:link2cid:3939 R:8900:strapi:1337 R:8902:next:3000 R:8903:uppy:3020"
command: "client --auth=${CHISEL_AUTH} ${CHISEL_SERVER} R:8899:cluster0:9094 R:8901:link2cid:3939 R:8900:strapi:1337 R:8902:next:3000 R:8903:uppy:3020 R:8904:uppy:8888"
link2cid:
container_name: fp-link2cid
@ -128,6 +128,29 @@ services:
- ./packages/next/app:/app/app
bot:
container_name: fp-bot
build:
context: .
dockerfile: ./packages/bot/Dockerfile
target: dev
restart: on-failure
environment:
REST_HOST: localhost
REST_PORT: 8888
DISCORD_TOKEN: ${DISCORD_TOKEN}
DISCORD_GUILD_ID: ${DISCORD_GUILD_ID}
ports:
- "8888:8888"
volumes:
- ./packages/bot/package.json:/app/package.json
- ./packages/bot/src:/app/src
db:
container_name: fp-db
image: postgres:16

View File

@ -9,11 +9,22 @@ interface IPagerProps {
export default function Pager({ baseUrl, page, pageCount }: IPagerProps): React.JSX.Element {
const pageNumbers = Array.from({ length: pageCount }, (_, i) => i + 1);
const getPagePath = (page: any) => {
const getPagePath = (page: any): string => {
const pageNumber = parseInt(page);
console.log(`pageNumber=${pageNumber}`)
return `${baseUrl}/${pageNumber}`;
};
const getNextPagePath = (page: any): string => {
const pageNumber = parseInt(page);
return `${baseUrl}/${pageNumber+1}`;
}
const getPreviousPagePath = (page: any): string => {
const pageNumber = parseInt(page);
return `${baseUrl}/${pageNumber-1}`
}
// Define the number of page links to show around the current page
const maxPageLinksToShow = 3;
@ -25,12 +36,12 @@ export default function Pager({ baseUrl, page, pageCount }: IPagerProps): React.
<div className="box">
<nav className="pagination">
{page > 1 && (
<Link href={getPagePath(page - 1)} className="pagination-previous">
<Link href={getPreviousPagePath(page)} className="pagination-previous">
<span>Previous</span>
</Link>
)}
{page < pageCount && (
<Link href={getPagePath(page + 1)} className="pagination-next" >
<Link href={getNextPagePath(page)} className="pagination-next" >
<span>Next</span>
</Link>
)}

View File

@ -1,11 +1,8 @@
import React from 'react'
import Link from 'next/link';
import VodCard from './vod-card';
import { IVtuber } from '@/lib/vtubers';
import { IVod } from '@/lib/vods';
import { getVodTitle } from './vod-page';
import { notFound } from 'next/navigation';
import { IStream, getStreamsForVtuber, getAllStreams } from '@/lib/streams';
import { IStream, getAllStreams } from '@/lib/streams';
import { StreamSummary } from '@/components/stream';
interface IStreamsListProps {
@ -50,7 +47,7 @@ export default async function StreamsList({ vtubers, page = 1, pageSize = 24 }:
<ul className="menu-list">
{streams.length < 1 && <p className='section'><i>There are no streams</i></p>}
{streams.map((stream: IStream) => (
<li>
<li key={stream.id}>
<StreamSummary stream={stream}/>
</li>
))}

View File

@ -0,0 +1,318 @@
'use client'
import {
Column,
Table as ReactTable,
useReactTable,
ColumnFiltersState,
getCoreRowModel,
getFilteredRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getPaginationRowModel,
sortingFns,
getSortedRowModel,
FilterFn,
SortingFn,
ColumnDef,
flexRender,
FilterFns,
ColumnOrderState,
createColumnHelper,
} from '@tanstack/react-table'
import Image from 'next/image';
import { useState } from "react";
import { IStream } from "@/lib/streams";
import { LocalizedDate } from './localized-date';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleLeft, faAngleRight, faAnglesLeft, faAnglesRight, faChevronCircleRight, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import Link from 'next/link';
function Filter({
column,
table,
}: {
column: Column<any, any>
table: ReactTable<any>
}) {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id)
const columnFilterValue = column.getFilterValue()
if (typeof firstValue === 'number') {
return (
<div className="flex space-x-2">
<input
type="number"
value={(columnFilterValue as [number, number])?.[0] ?? ''}
onChange={e =>
column.setFilterValue((old: [number, number]) => [
e.target.value,
old?.[1],
])
}
placeholder={`Min`}
className="w-24 border shadow rounded"
/>
<input
type="number"
value={(columnFilterValue as [number, number])?.[1] ?? ''}
onChange={e =>
column.setFilterValue((old: [number, number]) => [
old?.[0],
e.target.value,
])
}
placeholder={`Max`}
className="w-24 border shadow rounded"
/>
</div>
)
}
if (typeof firstValue === 'boolean') {
return (
<>
<div className='select'>
<select
onChange={(evt) => {
if (evt.target.value === "any")
column?.setFilterValue(null);
if (evt.target.value === "yes")
column?.setFilterValue(true);
if (evt.target.value === "no")
column?.setFilterValue(false);
}}
>
<option>any</option>
<option>yes</option>
<option>no</option>
</select>
</div>
</>
)
}
return (
<input
type="text"
value={(columnFilterValue ?? '') as string}
onChange={e => column.setFilterValue(e.target.value)}
placeholder={`Search...`}
className="input"
/>
)
}
const archiveStatusClassName = (archiveStatus: string): string => {
if (archiveStatus === 'missing') return 'is-danger';
if (archiveStatus === 'issue') return 'is-warning';
if (archiveStatus === 'good') return 'is-success';
return 'is-info';
};
export default function StreamsTable({ streams }: { streams: IStream[] }) {
const columnHelper = createColumnHelper<IStream>()
const columns = [
columnHelper.accessor('attributes.cuid', {
cell: info => <Link href={`/streams/${info.getValue()}`}>{info.getValue()}</Link>,
header: () => <span>ID</span>
}),
columnHelper.accessor('attributes.vtuber.data.attributes.image', {
cell: info => <figure className='image is-24x24'><Image className="is-rounded" width={24} height={24} alt="" src={info.getValue()}></Image></figure>,
header: () => <span></span>,
enableColumnFilter: false
}),
columnHelper.accessor('attributes.vtuber.data.attributes.displayName', {
cell: info => info.getValue(),
header: () => <span>VTuber</span>
}),
columnHelper.accessor('attributes.date', {
cell: info => <LocalizedDate date={new Date(info.getValue())}/>,
header: () => <span>Date</span>
}),
columnHelper.accessor('attributes.isChaturbateStream', {
id: 'isChaturbateStream',
cell: info => info.getValue() === true ? 'yes' : 'no',
header: () => <span>Chaturbate</span>
}),
columnHelper.accessor('attributes.isFanslyStream', {
id: 'isFanslyStream',
cell: info => info.getValue() === true ? 'yes' : 'no',
header: () => <span>Fansly</span>
}),
columnHelper.accessor('attributes.archiveStatus', {
cell: info => <div className={`tag ${archiveStatusClassName(info.getValue())}`} >{info.getValue()}</div>,
header: () => <span>Status</span>
})
]
const [columnVisibility, setColumnVisibility] = useState({})
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [data, setData] = useState(() => streams)
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
state: {
columnVisibility,
columnOrder,
columnFilters
},
onColumnOrderChange: setColumnOrder,
onColumnFiltersChange: setColumnFilters,
})
return (
<>
<div className="p-2">
<div className="h-2" />
<table className='table'>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getCanFilter() ? (
<div>
<Filter column={header.column} table={table} />
</div>
) : null}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => {
return (
<tr key={row.id}>
{row.getVisibleCells().map(cell => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
<div className="columns is-multiline is-mobile" />
<div className="column is-12">
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<FontAwesomeIcon className='fa-solid fa-angles-left' icon={faAnglesLeft}></FontAwesomeIcon>
</button>
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<FontAwesomeIcon className='fa-solid fa-angle-left' icon={faAngleLeft}></FontAwesomeIcon>
</button>
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<FontAwesomeIcon className="fa-solid fa-angle-right" icon={faAngleRight}></FontAwesomeIcon>
</button>
<button
className="button icon is-medium is-rounded p-1 m-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<FontAwesomeIcon className='fa-solid fa-angles-right' icon={faAnglesRight}></FontAwesomeIcon>
</button>
</div>
<div className='column is-2'>
<div className=''>
<div className=''>
<span className='mr-1'>Page</span>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</div>
</div>
<div className=''>
<label className='label'>
Go to page:
</label>
<div className="control is-expanded">
<input
type="number"
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
className="input p-1"
/>
</div>
</div>
</div>
<div className='column is-2'>
<div className="m-1">
<span className='mr-1'>Page</span>
</div>
<div className='select'>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</div>
</div>
</>
)
}

View File

@ -1,6 +1,7 @@
'use client';
import { IVtuber } from "@/lib/vtubers";
import { IStream } from "@/lib/streams";
import { useSearchParams } from 'next/navigation';
import React, { useContext, useState, useEffect } from 'react';
import { UppyContext } from 'app/uppy';
@ -11,11 +12,13 @@ import { projektMelodyEpoch } from "@/lib/constants";
import add from "date-fns/add";
import sub from "date-fns/sub";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle, faPaperPlane, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faCheckCircle, faEraser, faPaperPlane, faSpinner, faX, faXmark } from "@fortawesome/free-solid-svg-icons";
import { useForm, useFieldArray, ValidationMode } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
import qs from 'qs';
import { toast } from "react-toastify";
import { ErrorMessage } from "@hookform/error-message"
interface IUploadFormProps {
vtubers: IVtuber[];
@ -32,6 +35,7 @@ interface IFormSchema extends Yup.InferType<typeof validationSchema> { };
const validationSchema = Yup.object().shape({
vtuber: Yup.number()
.required('VTuber is required'),
streamCuid: Yup.string().optional(),
date: Yup.date()
.typeError('Invalid date') // https://stackoverflow.com/a/72985532/1004931
.min(sub(projektMelodyEpoch, { days: 1 }), 'Date must be after February 7 2020')
@ -57,6 +61,7 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
const uppy = useContext(UppyContext);
const { authData } = useAuth();
const formOptions = {
resolver: yupResolver(validationSchema),
mode: 'onChange' as keyof ValidationMode,
@ -64,20 +69,47 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
const {
register,
handleSubmit,
setError,
clearErrors,
formState: {
errors,
isValid
isValid,
isSubmitted,
isSubmitSuccessful,
isSubmitting
},
setValue,
watch,
reset
} = useForm(formOptions);
// useEffect(() => {
// if (!cuid) return;
// (async () => {
// console.log('query')
// const query = qs.stringify({
// filters: {
// cuid: {
// '$eq': cuid
// }
// }
// });
// const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/streams?${query}`);
// if (!res.ok) return;
// const matchingStream = (await res.json()).data as IStream;
// console.log(matchingStream);
// setValue('vtuber', matchingStream.attributes.vtuber.data.id);
// })();
// }, [cuid]);
// setValue('streamCuid', cuid||'');
const files = watch('files');
async function createUSC(data: IFormSchema) {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/user-submitted-contents/createFromUppy`, {
method: 'POST',
headers: {
@ -91,13 +123,37 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
attribution: data.attribution,
notes: data.notes,
vtuber: data.vtuber,
date: data.date
date: data.date,
streamCuid: cuid
}
})
});
if (!res.ok) {
console.error('failed to fetch /api/user-submitted-contents/createFromUppy');
const body = await res.json();
const error = body.error;
toast.error(`${error.type} ${error.message}`, { theme: 'dark' });
setError('root.serverError', {
type: error.type,
message: error.message
})
}
} catch (e) {
if (e instanceof Error) {
toast.error(`${e.message}`, { theme: 'dark' });
setError('root.serverError', {
type: "remote",
message: e.message,
});
} else {
toast.error(`Something went wrong. Please try again.`, { theme: 'dark' });
setError('root.serverError', {
type: 'remote',
message: 'Something went wrong. Please try again.'
})
}
}
}
@ -126,10 +182,10 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
<div className='columns is-multiline'>
<form id="vod-details" onSubmit={handleSubmit((data) => createUSC(data))}>
<form id="vod-details" onSubmit={handleSubmit(createUSC)}>
<div className='column is-full'>
{(!isSubmitSuccessful) && <div className='column is-full'>
<section className="hero is-info mb-3">
<div className="hero-body">
<p className="title">
@ -146,6 +202,7 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
theme='dark'
proudlyDisplayPoweredByUppy={false}
/>
<input
required
hidden={true}
@ -157,9 +214,9 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
{errors.files && <p className="help is-danger">{errors.files.message?.toString()}</p>}
</section>
</div>
</div>}
<div className='column is-full '>
{(!isSubmitSuccessful) && <div className='column is-full '>
{/* {(!cuid) && <aside className='notification is-info'>Hint: Some of these fields are filled out automatically when uploading from a <Link href="/streams">stream</Link> page.</aside>} */}
<section className="hero is-info mb-3">
@ -177,7 +234,13 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
{/* <input
required
// hidden={false}
// style={{ display: 'none' }}
className="input" type="text"
{...register('streamCuid')}
></input> */}
<div className="field">
@ -238,7 +301,7 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
</section>
</div>
</div>}
<div className="column is-full">
@ -256,57 +319,48 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
<div className="icon-text">
<span className={`icon has-text-${(files) ? 'success' : 'danger'}`}>
<FontAwesomeIcon icon={(files) ? faCheckCircle : faXmark}></FontAwesomeIcon>
</span>
<span>Step 1, File Upload</span>
{errors.root?.serverError && (
<div className="notification">
<button className="delete" onClick={() => clearErrors()}></button>
<ErrorMessage name="root" errors={errors} ></ErrorMessage>
</div>
<div className="icon-text">
<span className={`icon has-text-${(isValid) ? 'success' : 'danger'}`}>
<FontAwesomeIcon icon={(isValid) ? faCheckCircle : faXmark}></FontAwesomeIcon>
</span>
<span>Step 2, Metadata</span>
</div>
{/* <ErrorMessage
errors={errors}
name="date"
render={({ message }) => <p>{message}</p>}
/> */}
{/* {fields.map((field, index) => (
<div key={field.id}>
<input
{...register(
// @ts-expect-error incorrect schema resolution in library types
`guests.${index}.name`
)}
/>{' '}
<button onClick={() => remove(index)}>Remove</button>
</div>
))} */}
{/* {
JSON.stringify({
touchedFields: Object.keys(touchedFields),
errors: Object.keys(errors)
}, null, 2)
} */}
{/* setError('date', { type: 'custom', message: 'custom message' }); */}
<button disabled={!isValid} className="button is-primary is-large mt-5">
{!isSubmitSuccessful && (
<button className="button is-primary is-large mt-5">
<span className="icon is-small">
<FontAwesomeIcon icon={faPaperPlane}></FontAwesomeIcon>
</span>
<span>Send</span>
</button>
)}
{isSubmitting && (
<p>
<FontAwesomeIcon className="mt-5 fa-spin-pulse" icon={faSpinner} ></FontAwesomeIcon>
</p>
)}
{isSubmitSuccessful && (
<>
<aside className="notification mt-5 is-success">Thank you for uploading! </aside>
<button onClick={() => {
reset(); // reset form
const files = uppy.getFiles()
for (const file of files) {
uppy.removeFile(file.id); // reset uppy
}
}} className="button is-primary">
<span className="icon is-small">
<FontAwesomeIcon icon={faEraser}></FontAwesomeIcon>
</span>
<span>Reset form</span>
</button>
</>
)}
</section>

View File

@ -119,7 +119,7 @@ export function VideoInteractive({ vod }: IVideoInteractiveProps): React.JSX.Ele
<LinkableHeading text="Tags" slug="tags" icon={faTags}></LinkableHeading>
<div className="tags has-addons mb-5">
{vod.attributes.tagVodRelations.data.length === 0 && <div className="ml-5"><p><i>This vod has no tags</i></p></div>}
{vod.attributes.tagVodRelations.data.length === 0 && <div className="ml-5 mr-2"><p><i>This vod has no tags</i></p></div>}
{vod.attributes.tagVodRelations.data.map((tvr: ITagVodRelation) => (
<Tag key={tvr.id} tvr={tvr}></Tag>
))}

View File

@ -1,7 +1,7 @@
'use client';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faPatreon } from "@fortawesome/free-brands-svg-icons";
import { faGlobe } from "@fortawesome/free-solid-svg-icons";
import { faGlobe, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { useState, useEffect } from 'react';
interface IVSSProps {
@ -88,7 +88,7 @@ export function VideoSourceSelector({
</div>
{(!isMux && !isB2 && !isIPFSSource && !isIPFS240) && <div className="nav-item">
<div className="notification is-danger">
<span>No video sources available</span>
<span><FontAwesomeIcon icon={faTriangleExclamation}></FontAwesomeIcon> No video sources available</span>
</div>
</div>}
{(isMux) && <div className="nav-item">

View File

@ -10,7 +10,9 @@ import LinkableHeading from './linkable-heading';
export function getVodTitle(vod: IVod): string {
return vod.attributes.title || vod.attributes.announceTitle || `${vod.attributes.vtuber.data.attributes.displayName} ${vod.attributes.date2}`;
console.log('lets getVodTitle, ey?')
console.log(JSON.stringify(vod, null, 2))
return vod.attributes.title || vod.attributes.announceTitle || (vod.attributes?.date2 && vod.attributes?.vtuber?.data?.attributes?.displayName) ? `${vod.attributes.vtuber.data.attributes.displayName} ${vod.attributes.date2}` : `VOD ${vod.id}`;
}
export function buildMuxUrl(playbackId: string, token: string) {
@ -27,6 +29,8 @@ export function buildMuxThumbnailUrl(playbackId: string, token: string) {
export default async function VodPage({vod}: { vod: IVod }) {
console.log('vod page helllo')
console.log(vod)
if (!vod) notFound();
const slug = vod.attributes.vtuber.data.attributes.slug;

View File

@ -4,6 +4,7 @@ import { IVodsResponse } from '@/lib/vods';
import Pager from '@/components/pager';
import { getVods } from '@/lib/vods';
interface IPageParams {
params: {
slug: string;

View File

@ -113,27 +113,6 @@ export async function getAllVtubers(): Promise<IVtuber[] | null> {
while (true) {
const query = qs.stringify({
// populate: {
// vtuber: {
// fields: ['slug', 'displayName', 'image', 'imageBlur'],
// },
// muxAsset: {
// fields: ['playbackId', 'assetId'],
// },
// thumbnail: {
// fields: ['cdnUrl', 'url'],
// },
// tagVodRelations: {
// fields: ['tag'],
// populate: ['tag'],
// },
// videoSrcB2: {
// fields: ['url', 'key', 'uploadId', 'cdnUrl'],
// },
// },
// sort: {
// date: sortDesc ? 'desc' : 'asc',
// },
pagination: {
pageSize,
page: currentPage,
@ -141,7 +120,7 @@ export async function getAllVtubers(): Promise<IVtuber[] | null> {
});
try {
console.log(`getting /api/vtubers page=${currentPage}`);
console.log(`Getting /api/vtubers page=${currentPage}`);
const response = await fetch(`${strapiUrl}/api/vtubers?${query}`, fetchVtubersOptions);
if (!response.ok) {

View File

@ -1,21 +1,17 @@
import Pager from "@/components/pager";
import StreamsCalendar from "@/components/streams-calendar";
import StreamsList from "@/components/streams-list";
import StreamsTable from '@/components/streams-table';
import { getAllStreams } from "@/lib/streams";
import { getAllVtubers } from "@/lib/vtubers";
import { MissingStaticPage } from "next/dist/shared/lib/utils";
// import { getAllVtubers } from "@/lib/vtubers";
import { notFound } from "next/navigation";
// import { useState } from "react";
export default async function Page() {
const vtubers = await getAllVtubers();
const pageSize = 100;
const page = 1;
if (!vtubers) notFound();
const missingStreams = await getAllStreams(['missing']);
const issueStreams = await getAllStreams(['issue']);
const goodStreams = await getAllStreams(['good']);
// const vtubers = await getAllVtubers();
const streams = await getAllStreams();
// const pageSize = 100;
// const page = 1;
// if (!vtubers) notFound();
return (
<div className="section">
@ -25,10 +21,10 @@ export default async function Page() {
</code>
</pre> */}
{/* <StreamsCalendar missingStreams={missingStreams} issueStreams={issueStreams} goodStreams={goodStreams} /> */}
<StreamsList vtubers={vtubers} page={page} pageSize={pageSize} />
<Pager baseUrl="/streams" page={page} pageCount={vtubers.length/pageSize}/>
<h1 className="title">Stream Archive</h1>
<StreamsTable streams={streams} />
{/* <StreamsList vtubers={vtubers} page={page} pageSize={pageSize} />
<Pager baseUrl="/streams" page={page} pageCount={vtubers.length/pageSize}/> */}
</div>
)
}

View File

@ -27,6 +27,7 @@
"@mux/mux-player-react": "^2.3.1",
"@paralleldrive/cuid2": "^2.2.2",
"@react-hookz/web": "^24.0.2",
"@tanstack/react-table": "^8.11.7",
"@types/lodash": "^4.14.202",
"@types/qs": "^6.9.11",
"@types/react": "^18.2.47",

View File

@ -14,8 +14,8 @@
"attributes": {
"url": {
"type": "string",
"required": true,
"unique": true
"required": false,
"unique": false
},
"key": {
"type": "string",

View File

@ -0,0 +1,26 @@
{
"kind": "collectionType",
"collectionName": "discord_interactions",
"info": {
"singularName": "discord-interaction",
"pluralName": "discord-interactions",
"displayName": "Discord Interaction",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"interactionId": {
"type": "string",
"required": true
},
"userSubmittedContent": {
"type": "relation",
"relation": "oneToOne",
"target": "api::user-submitted-content.user-submitted-content",
"inversedBy": "discordInteraction"
}
}
}

View File

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

View File

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

View File

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

View File

@ -11,10 +11,7 @@ if (!process.env.AWS_SECRET_ACCESS_KEY) throw new Error('AWS_SECRET_ACCESS_KEY m
module.exports = {
async beforeCreate(event) {
console.log('>>> beforeCreate!');
},
// when strapi deletes a USC, we delete the related files in the S3 bucket.
async afterDelete(event) {
@ -33,12 +30,15 @@ module.exports = {
});
// https://fp-usc-dev.s3.us-west-000.backblazeb2.com/GEB7_QcaUAAQ29O.jpg
for (const file of result.files) {
console.log(`deleting ${file.key}`);
const res = await client.send(new DeleteObjectCommand({
Bucket: process.env.S3_USC_BUCKET_NAME,
Key: result.key
Key: file.key
}));
console.log(res);
}

View File

@ -31,6 +31,27 @@
"files": {
"type": "json",
"required": true
},
"approved": {
"type": "boolean",
"default": false
},
"vtuber": {
"type": "relation",
"relation": "oneToOne",
"target": "api::vtuber.vtuber"
},
"uploaderId": {
"type": "integer"
},
"streamCuid": {
"type": "string"
},
"discordInteraction": {
"type": "relation",
"relation": "oneToOne",
"target": "api::discord-interaction.discord-interaction",
"mappedBy": "userSubmittedContent"
}
}
}

View File

@ -33,15 +33,18 @@ module.exports = createCoreController('api::user-submitted-content.user-submitte
}
// Extract relevant data
const { files, vtuber, date, notes, attribution } = data;
const { files, vtuber, streamCuid, date, notes, attribution } = data;
const uploader = ctx.state.user.id;
const uploaderId = ctx.state.user.id;
console.log('Creating user-submitted content');
const usc = await strapi.entityService.create('api::user-submitted-content.user-submitted-content', {
data: {
uploader,
uploaderId,
files: files.map((f) => ({ ...f, cdnUrl: `${process.env.CDN_BUCKET_USC_URL}/${f.key}` })),
vtuber,
streamCuid,
date,
notes,
attribution,

View File

@ -1,7 +1,6 @@
const { init } = require('@paralleldrive/cuid2');
module.exports = {
async beforeUpdate(event) {
function generateCuid(event) {
const { data } = event.params;
if (!data.cuid) {
const length = 10; // 50% odds of collision after ~51,386,368 ids
@ -9,4 +8,18 @@ module.exports = {
event.params.data.cuid = cuid();
}
}
module.exports = {
async beforeUpdate(event) {
generateCuid(event);
},
async beforeCreate(event) {
console.log(`>> beforeCreate! We are generating a CUID`);
console.log(`>> beforeCreate! We are generating a CUID`);
generateCuid(event);
},
async afterCreate(event) {
console.log(`>> afterCreate! We are generating a CUID`);
generateCuid(event);
}
}

View File

@ -6,6 +6,50 @@ settings:
importers:
packages/bot:
dependencies:
'@types/express':
specifier: ^4.17.21
version: 4.17.21
'@types/node':
specifier: ^20.11.0
version: 20.11.5
discordeno:
specifier: ^18.0.1
version: 18.0.1
express:
specifier: ^4.18.2
version: 4.18.2
oceanic.js:
specifier: ^1.9.0
version: 1.9.0
devDependencies:
tsx:
specifier: ^4.7.0
version: 4.7.0
packages/futurebot:
dependencies:
'@types/express':
specifier: ^4.17.21
version: 4.17.21
'@types/node':
specifier: ^20.11.0
version: 20.11.5
discordeno:
specifier: ^18.0.1
version: 18.0.1
dotenv:
specifier: ^16.3.1
version: 16.3.1
express:
specifier: ^4.18.2
version: 4.18.2
devDependencies:
tsx:
specifier: ^4.7.0
version: 4.7.0
packages/next:
dependencies:
'@fortawesome/fontawesome-free':
@ -59,6 +103,9 @@ importers:
'@react-hookz/web':
specifier: ^24.0.2
version: 24.0.2(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-table':
specifier: ^8.11.7
version: 8.11.7(react-dom@18.2.0)(react@18.2.0)
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
@ -894,6 +941,41 @@ packages:
dependencies:
regenerator-runtime: 0.14.1
/@deno/shim-deno-test@0.3.3:
resolution: {integrity: sha512-Ge0Tnl7zZY0VvEfgsyLhjid8DzI1d0La0dgm+3m0/A8gZXgp5xwlyIyue5e4SCUuVB/3AH/0lun9LcJhhTwmbg==}
dev: false
/@deno/shim-deno@0.9.0:
resolution: {integrity: sha512-iP+qdI4Oy/Mw9yv40TqdjNKL+stpKDo8drki2cKisTXgZf+GoIdMhIuODxSypRyv6wxIuHNx7ZiKE3Sl3kAHuw==}
dependencies:
'@deno/shim-deno-test': 0.3.3
which: 2.0.2
dev: false
/@deno/shim-timers@0.1.0:
resolution: {integrity: sha512-XFRnB5Rtbkd5RiYHwhugNK9gvDgYXmFTUOT5dmhWCKG7WnOWZggbJMnH1NcyYS3QgHvmaTOaHCyNFNSv57j3Dg==}
dev: false
/@discordjs/voice@0.16.1:
resolution: {integrity: sha512-uiWiW0Ta6K473yf8zs13RfKuPqm/xU4m4dAidMkIdwqgy1CztbbZBtPLfDkVSKzpW7s6m072C+uQcs4LwF3FhA==}
engines: {node: '>=16.11.0'}
requiresBuild: true
dependencies:
'@types/ws': 8.5.10
discord-api-types: 0.37.61
prism-media: 1.3.5
tslib: 2.6.2
ws: 8.16.0
transitivePeerDependencies:
- '@discordjs/opus'
- bufferutil
- ffmpeg-static
- node-opus
- opusscript
- utf-8-validate
dev: false
optional: true
/@emnapi/runtime@0.45.0:
resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==}
requiresBuild: true
@ -902,6 +984,213 @@ packages:
dev: false
optional: true
/@esbuild/aix-ppc64@0.19.12:
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64@0.19.12:
resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm@0.19.12:
resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64@0.19.12:
resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64@0.19.12:
resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64@0.19.12:
resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64@0.19.12:
resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64@0.19.12:
resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64@0.19.12:
resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm@0.19.12:
resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32@0.19.12:
resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64@0.19.12:
resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el@0.19.12:
resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64@0.19.12:
resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64@0.19.12:
resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x@0.19.12:
resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64@0.19.12:
resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64@0.19.12:
resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64@0.19.12:
resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64@0.19.12:
resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64@0.19.12:
resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32@0.19.12:
resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64@0.19.12:
resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.56.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -939,6 +1228,11 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@fastify/busboy@2.1.0:
resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==}
engines: {node: '>=14'}
dev: false
/@fortawesome/fontawesome-common-types@6.5.1:
resolution: {integrity: sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==}
engines: {node: '>=6'}
@ -1992,6 +2286,23 @@ packages:
defer-to-connect: 2.0.1
dev: false
/@tanstack/react-table@8.11.7(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-ZbzfMkLjxUTzNPBXJYH38pv2VpC9WUA+Qe5USSHEBz0dysDTv4z/ARI3csOed/5gmlmrPzVUN3UXGuUMbod3Jg==}
engines: {node: '>=12'}
peerDependencies:
react: '>=16'
react-dom: '>=16'
dependencies:
'@tanstack/table-core': 8.11.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@tanstack/table-core@8.11.7:
resolution: {integrity: sha512-N3ksnkbPbsF3PjubuZCB/etTqvctpXWRHIXTmYfJFnhynQKjeZu8BCuHvdlLPpumKbA+bjY4Ay9AELYLOXPWBg==}
engines: {node: '>=12'}
dev: false
/@transloadit/prettier-bytes@0.0.7:
resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==}
dev: false
@ -2000,6 +2311,13 @@ packages:
resolution: {integrity: sha512-pCvdmea/F3Tn4hAtHqNXmjcixSaroJJ+L3STXlYJdir1g1m2mRQpWbN8a4SvgQtaw2930Ckhdx8qXdXBFMKbAA==}
dev: false
/@types/body-parser@1.19.5:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies:
'@types/connect': 3.4.38
'@types/node': 20.11.5
dev: false
/@types/cacheable-request@6.0.3:
resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
dependencies:
@ -2009,10 +2327,38 @@ packages:
'@types/responselike': 1.0.3
dev: false
/@types/connect@3.4.38:
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
dependencies:
'@types/node': 20.11.5
dev: false
/@types/express-serve-static-core@4.17.41:
resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==}
dependencies:
'@types/node': 20.11.5
'@types/qs': 6.9.11
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
dev: false
/@types/express@4.17.21:
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
dependencies:
'@types/body-parser': 1.19.5
'@types/express-serve-static-core': 4.17.41
'@types/qs': 6.9.11
'@types/serve-static': 1.15.5
dev: false
/@types/http-cache-semantics@4.0.4:
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
dev: false
/@types/http-errors@2.0.4:
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
dev: false
/@types/json5@0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
@ -2027,6 +2373,14 @@ packages:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
dev: false
/@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: false
/@types/mime@3.0.4:
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
dev: false
/@types/node@20.11.0:
resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==}
dependencies:
@ -2055,6 +2409,10 @@ packages:
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
dev: false
/@types/range-parser@1.2.7:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: false
/@types/react-dom@18.2.18:
resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==}
dependencies:
@ -2083,6 +2441,29 @@ packages:
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
dev: false
/@types/send@0.17.4:
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
dependencies:
'@types/mime': 1.3.5
'@types/node': 20.11.5
dev: false
/@types/serve-static@1.15.5:
resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
dependencies:
'@types/http-errors': 2.0.4
'@types/mime': 3.0.4
'@types/node': 20.11.5
dev: false
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
requiresBuild: true
dependencies:
'@types/node': 20.11.5
dev: false
optional: true
/@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.3.3):
resolution: {integrity: sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -3576,6 +3957,24 @@ packages:
path-type: 4.0.0
dev: true
/discord-api-types@0.37.61:
resolution: {integrity: sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==}
requiresBuild: true
dev: false
optional: true
/discordeno@18.0.1:
resolution: {integrity: sha512-d3D/HpC39YGInmxy2HK90kPpMMu2gYYsWuwtEEFPWpq2hlR9dvad4ihvLursPz5bj4Ob1NWOgPv3kz/bwMSIpw==}
dependencies:
'@deno/shim-deno': 0.9.0
'@deno/shim-timers': 0.1.0
undici: 5.28.2
ws: 8.8.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: false
/doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@ -3730,6 +4129,37 @@ packages:
is-symbol: 1.0.4
dev: true
/esbuild@0.19.12:
resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/aix-ppc64': 0.19.12
'@esbuild/android-arm': 0.19.12
'@esbuild/android-arm64': 0.19.12
'@esbuild/android-x64': 0.19.12
'@esbuild/darwin-arm64': 0.19.12
'@esbuild/darwin-x64': 0.19.12
'@esbuild/freebsd-arm64': 0.19.12
'@esbuild/freebsd-x64': 0.19.12
'@esbuild/linux-arm': 0.19.12
'@esbuild/linux-arm64': 0.19.12
'@esbuild/linux-ia32': 0.19.12
'@esbuild/linux-loong64': 0.19.12
'@esbuild/linux-mips64el': 0.19.12
'@esbuild/linux-ppc64': 0.19.12
'@esbuild/linux-riscv64': 0.19.12
'@esbuild/linux-s390x': 0.19.12
'@esbuild/linux-x64': 0.19.12
'@esbuild/netbsd-x64': 0.19.12
'@esbuild/openbsd-x64': 0.19.12
'@esbuild/sunos-x64': 0.19.12
'@esbuild/win32-arm64': 0.19.12
'@esbuild/win32-ia32': 0.19.12
'@esbuild/win32-x64': 0.19.12
dev: true
/escape-goat@3.0.0:
resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
engines: {node: '>=10'}
@ -4853,7 +5283,6 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/isoformat@0.2.1:
resolution: {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==}
@ -5525,6 +5954,23 @@ packages:
es-abstract: 1.22.3
dev: true
/oceanic.js@1.9.0:
resolution: {integrity: sha512-zBHxBnJDkhYiHvQnTWcLsdRa/0v7+S2BYHWS3TLZpmMDXJU/+KOB9oL17YqSK8xvfpA0ykIsZTPKTgwxQUfLxA==}
engines: {node: '>=18.13.0'}
dependencies:
tslib: 2.6.2
ws: 8.16.0
optionalDependencies:
'@discordjs/voice': 0.16.1
transitivePeerDependencies:
- '@discordjs/opus'
- bufferutil
- ffmpeg-static
- node-opus
- opusscript
- utf-8-validate
dev: false
/on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
@ -5716,6 +6162,26 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prism-media@1.3.5:
resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==}
requiresBuild: true
peerDependencies:
'@discordjs/opus': '>=0.8.0 <1.0.0'
ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0
node-opus: ^0.3.3
opusscript: ^0.0.8
peerDependenciesMeta:
'@discordjs/opus':
optional: true
ffmpeg-static:
optional: true
node-opus:
optional: true
opusscript:
optional: true
dev: false
optional: true
/prism-react-renderer@2.3.1(react@18.2.0):
resolution: {integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==}
peerDependencies:
@ -6537,6 +7003,17 @@ packages:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: false
/tsx@4.7.0:
resolution: {integrity: sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==}
engines: {node: '>=18.0.0'}
hasBin: true
dependencies:
esbuild: 0.19.12
get-tsconfig: 4.7.2
optionalDependencies:
fsevents: 2.3.3
dev: true
/tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies:
@ -6647,6 +7124,13 @@ packages:
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
/undici@5.28.2:
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
engines: {node: '>=14.0'}
dependencies:
'@fastify/busboy': 2.1.0
dev: false
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
@ -6766,7 +7250,6 @@ packages:
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/wildcard@1.1.2:
resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==}
@ -6775,6 +7258,19 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/ws@8.16.0:
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/ws@8.8.1:
resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==}
engines: {node: '>=10.0.0'}

View File

@ -1,5 +1,6 @@
packages:
- 'packages/next'
- 'packages/uppy'
- 'packages/bot'
# strapi is not on here because it's not compatible with pnpm