From 967a6d1587b3bc1c7b833af714b6cfb28e27e298 Mon Sep 17 00:00:00 2001 From: Jorge Vargas Date: Sat, 8 Mar 2025 22:24:22 -0600 Subject: [PATCH] Create Album endpoint --- astro.config.mjs | 4 +- package.json | 6 +- .../migration.sql | 19 +++ .../migration.sql | 43 +++++++ prisma/schema.prisma | 76 ++++-------- src/auth.ts | 10 +- src/integrations/requestCat.ts | 58 +++++++++ src/pages/album/[id].astro | 6 +- src/pages/api/album/create.ts | 111 ++++++++++++++++++ src/pages/rss.xml.ts | 3 +- src/schemas/album.ts | 15 +++ src/utils/form.ts | 21 ++++ src/utils/img.ts | 29 +++++ tsconfig.json | 3 +- yarn.lock | 69 +++++++---- 15 files changed, 388 insertions(+), 85 deletions(-) create mode 100644 prisma/migrations/20250227050444_album_model_cleanup/migration.sql create mode 100644 prisma/migrations/20250308032135_cleanup_tables/migration.sql create mode 100644 src/integrations/requestCat.ts create mode 100644 src/pages/api/album/create.ts create mode 100644 src/schemas/album.ts create mode 100644 src/utils/form.ts create mode 100644 src/utils/img.ts diff --git a/astro.config.mjs b/astro.config.mjs index 2b421b8..4de4c7c 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -20,7 +20,8 @@ export default defineConfig({ access: 'public', optional: true, default: 'http://localhost:4321' - }) + }), + WEBHOOK_URL: envField.string({ context: 'server', access: 'secret' }) }, validateSecrets: true }, @@ -44,7 +45,6 @@ export default defineConfig({ adapter: node({ mode: 'standalone' }), redirects: { '/album/list': { status: 307, destination: '/maintenance' }, - // '/last-added': { status: 307, destination: '/maintenance' }, '/anim': { status: 307, destination: '/maintenance' }, '/anim/[id]': { status: 307, destination: '/maintenance' }, '/anim/list': { status: 307, destination: '/maintenance' }, diff --git a/package.json b/package.json index 7db56cf..88da508 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "astro": "^5.3.0", "astro-icon": "^1.1.1", "better-auth": "^1.1.11", + "axios": "^1.8.1", "clsx": "^2.1.1", "nodemailer": "^6.10.0", "prisma": "^6.4.1", @@ -29,6 +30,8 @@ "react-hot-toast": "^2.4.1", "react-svg-spinners": "^0.3.1", "sharp": "^0.33.5", + "slugify": "^1.6.6", + "superstruct": "^2.0.2", "tailwindcss": "^4.0.7", "typescript": "^5.6.2" }, @@ -44,5 +47,6 @@ "neostandard": "^0.11.6", "prettier": "^3.3.3", "prettier-config-standard": "^7.0.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/prisma/migrations/20250227050444_album_model_cleanup/migration.sql b/prisma/migrations/20250227050444_album_model_cleanup/migration.sql new file mode 100644 index 0000000..9b11968 --- /dev/null +++ b/prisma/migrations/20250227050444_album_model_cleanup/migration.sql @@ -0,0 +1,19 @@ +/* + Warnings: + + - You are about to drop the column `placeholder` on the `albums` table. All the data in the column will be lost. + - Made the column `status` on table `albums` required. This step will fail if there are existing NULL values in that column. + - Made the column `headerColor` on table `albums` required. This step will fail if there are existing NULL values in that column. + +*/ +DELETE FROM `albums` WHERE `status` = "Published"; +UPDATE `albums` SET `status` = "Show" WHERE LOWER(`status`) = "show"; +UPDATE `albums` SET `status` = "Hidden" WHERE LOWER(`status`) = "hidden"; + +-- AlterTable +ALTER TABLE `albums` DROP COLUMN `placeholder`, + ADD COLUMN `publishedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + MODIFY `createdAt` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + MODIFY `updatedAt` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + MODIFY `status` ENUM('Show', 'Hidden') NOT NULL DEFAULT 'Hidden', + MODIFY `headerColor` VARCHAR(255) NOT NULL DEFAULT '#ffffff'; diff --git a/prisma/migrations/20250308032135_cleanup_tables/migration.sql b/prisma/migrations/20250308032135_cleanup_tables/migration.sql new file mode 100644 index 0000000..bc72622 --- /dev/null +++ b/prisma/migrations/20250308032135_cleanup_tables/migration.sql @@ -0,0 +1,43 @@ +/* + Warnings: + + - You are about to drop the column `createdAt` on the `artist` table. All the data in the column will be lost. + - You are about to drop the column `updatedAt` on the `artist` table. All the data in the column will be lost. + - You are about to drop the column `createdAt` on the `discs` table. All the data in the column will be lost. + - You are about to drop the column `updatedAt` on the `discs` table. All the data in the column will be lost. + - You are about to drop the column `createdAt` on the `downloads` table. All the data in the column will be lost. + - You are about to drop the column `updatedAt` on the `downloads` table. All the data in the column will be lost. + - You are about to drop the `Album_Type` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `availables` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `type` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE `Album_Type` DROP FOREIGN KEY `Album_Type_ibfk_1`; + +-- DropForeignKey +ALTER TABLE `Album_Type` DROP FOREIGN KEY `Album_Type_ibfk_2`; + +-- DropForeignKey +ALTER TABLE `availables` DROP FOREIGN KEY `availables_ibfk_1`; + +-- AlterTable +ALTER TABLE `artist` DROP COLUMN `createdAt`, + DROP COLUMN `updatedAt`; + +-- AlterTable +ALTER TABLE `discs` DROP COLUMN `createdAt`, + DROP COLUMN `updatedAt`; + +-- AlterTable +ALTER TABLE `downloads` DROP COLUMN `createdAt`, + DROP COLUMN `updatedAt`; + +-- DropTable +DROP TABLE `Album_Type`; + +-- DropTable +DROP TABLE `availables`; + +-- DropTable +DROP TABLE `type`; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f0d6762..041f50c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -62,15 +62,6 @@ model Album_Platform { @@id([albumId, platformId]) } -model Album_Type { - albumId Int - typeId Int - album albums @relation(fields: [albumId], references: [id], onDelete: Cascade, map: "Album_Type_ibfk_1") - type type @relation(fields: [typeId], references: [id], onDelete: Cascade, map: "Album_Type_ibfk_2") - - @@id([albumId, typeId]) -} - model Game_Platform { gameSlug String @db.VarChar(255) platformId Int @@ -127,6 +118,11 @@ model albumHistories { album albums? @relation(fields: [albumId], references: [id], map: "albumHistories_ibfk_2") } +enum AlbumStatus { + SHOW @map("Show") + HIDDEN @map("Hidden") +} + model albums { id Int @id @default(autoincrement()) title String? @db.VarChar(255) @@ -134,22 +130,20 @@ model albums { releaseDate DateTime? @db.Date label String? @db.VarChar(255) vgmdb String? @db.VarChar(255) - createdAt DateTime @db.DateTime(0) - updatedAt DateTime @db.DateTime(0) + createdAt DateTime @default(now()) @db.DateTime(0) + updatedAt DateTime @default(now()) @db.DateTime(0) + publishedAt DateTime @default(now()) description String? @db.VarChar(255) createdBy String? @db.VarChar(255) - status String? @db.VarChar(255) - placeholder String? @db.Text - headerColor String? @default("#ffffff") @db.VarChar(255) + status AlbumStatus @default(HIDDEN) + headerColor String @default("#ffffff") @db.VarChar(255) animations Album_Animation[] artists Album_Artist[] categories Album_Category[] classifications Album_Classification[] games Album_Game[] platforms Album_Platform[] - types Album_Type[] histories albumHistories[] - availables availables[] comments comments[] discs discs[] downloads downloads[] @@ -176,21 +170,9 @@ model animation { } model artist { - slug String @id @db.VarChar(255) - name String? @db.VarChar(255) - createdAt DateTime @db.DateTime(0) - updatedAt DateTime @db.DateTime(0) - albums Album_Artist[] -} - -model availables { - id Int @id @default(autoincrement()) - url String? @db.VarChar(255) - provider String? @db.VarChar(255) - createdAt DateTime @db.DateTime(0) - updatedAt DateTime @db.DateTime(0) - albumId Int? - album albums? @relation(fields: [albumId], references: [id], map: "availables_ibfk_1") + slug String @id @db.VarChar(255) + name String? @db.VarChar(255) + albums Album_Artist[] } model category { @@ -227,24 +209,20 @@ model config { } model discs { - id Int @id @default(autoincrement()) - number Int? - body String? @db.Text - createdAt DateTime @db.DateTime(0) - updatedAt DateTime @db.DateTime(0) - albumId Int - album albums? @relation(fields: [albumId], references: [id], map: "discs_ibfk_1") + id Int @id @default(autoincrement()) + number Int? + body String? @db.Text + albumId Int + album albums? @relation(fields: [albumId], references: [id], map: "discs_ibfk_1") } model downloads { - id Int @id @default(autoincrement()) - title String? @db.VarChar(255) - small Boolean? - createdAt DateTime @db.DateTime(0) - updatedAt DateTime @db.DateTime(0) - albumId Int - album albums? @relation(fields: [albumId], references: [id], onDelete: Cascade, map: "downloads_ibfk_1") - links links[] + id Int @id @default(autoincrement()) + title String? @db.VarChar(255) + small Boolean? + albumId Int + album albums? @relation(fields: [albumId], references: [id], onDelete: Cascade, map: "downloads_ibfk_1") + links links[] } model favorites { @@ -425,12 +403,6 @@ model submissions { request requests? @relation(fields: [requestId], references: [id], map: "submissions_ibfk_2") } -model type { - id Int @id @default(autoincrement()) - name String @db.VarChar(255) - albums Album_Type[] -} - model users { id String @id @db.VarChar(255) name String @db.VarChar(20) diff --git a/src/auth.ts b/src/auth.ts index af95db4..362d2cb 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,16 +1,16 @@ import { betterAuth } from 'better-auth' import { prismaAdapter } from 'better-auth/adapters/prisma' -import { username } from 'better-auth/plugins' +import { username, bearer } from 'better-auth/plugins' import prismaClient from './utils/prisma-client' -import { sendEmail } from 'utils/email' -import forgorTemplate from 'utils/forgorTemplate' -import verifyTemplate from 'utils/verifyTemplate' +import { sendEmail } from './utils/email' +import forgorTemplate from './utils/forgorTemplate' +import verifyTemplate from './utils/verifyTemplate' export const auth = betterAuth({ database: prismaAdapter(prismaClient, { provider: 'mysql' }), user: { modelName: 'users' }, - plugins: [username()], + plugins: [username(), bearer()], emailVerification: { sendOnSignUp: true, autoSignInAfterVerification: true, diff --git a/src/integrations/requestCat.ts b/src/integrations/requestCat.ts new file mode 100644 index 0000000..840995c --- /dev/null +++ b/src/integrations/requestCat.ts @@ -0,0 +1,58 @@ +import { Prisma } from '@prisma/client' +import axios from 'axios' +import prismaClient from 'utils/prisma-client' +import { getImage } from 'astro:assets' + +import { WEBHOOK_URL } from 'astro:env/server' + +const albumArtistNames = Prisma.validator()({ + include: { artists: { include: { artist: { select: { name: true } } } } } +}) +type AlbumArtistNames = Prisma.albumsGetPayload + +async function postWebhook(album: AlbumArtistNames, userText = '') { + const url = `https://www.sittingonclouds.net/album/${album.id}` + const content = `${url}${userText}` + const artistNames = album.artists.map((a) => a.artist.name) + const coverImage = await getImage({ + src: `https://cdn.sittingonclouds.net/album/${album.id}.png`, + height: 150, + width: 150 + }) + const embeds = [ + { + title: album.title, + type: 'rich', + description: album.subTitle || artistNames.join(' - '), + url, + color: parseInt(album.headerColor.substring(1), 16), + thumbnail: { url: `https://www.sittingonclouds.net${coverImage.src}` } + } + ] + + const payload = { content, embeds } + await axios.post(WEBHOOK_URL, payload) +} + +export const requestPOST = (operation: string, body: any) => axios.post(`http://localhost:7001/${operation}`, body) + +export async function handleComplete(album: AlbumArtistNames, requestId?: number) { + if (requestId) { + const request = await prismaClient.requests.findUnique({ + where: { id: requestId }, + select: { state: true, id: true, userID: true, user: true } + }) + if (!request || request.state === 'complete') return + + await requestPOST('complete', { requestId: request.id }) + + const userText = + request.userID || request.user + ? ` ${request.userID ? `<@${request.userID}>` : `@${request.user}`} :arrow_down:` + : '' + + await postWebhook(album, userText) + } else { + await postWebhook(album) + } +} diff --git a/src/pages/album/[id].astro b/src/pages/album/[id].astro index 5a786fb..1183bf9 100644 --- a/src/pages/album/[id].astro +++ b/src/pages/album/[id].astro @@ -2,6 +2,7 @@ import prismaClient from 'utils/prisma-client' import * as m from 'paraglide/messages' import { Image } from 'astro:assets' +import { AlbumStatus } from '@prisma/client' import BaseLayout from 'layouts/base.astro' import TrackList from 'components/albumPage/TrackList' @@ -16,10 +17,13 @@ import ouoIcon from 'img/assets/ouo-icon.png' const { id } = Astro.params const { permissions } = Astro.locals + const hasDirect = permissions.includes('SKIP_ADS') +const hasUpdate = permissions.includes('UPDATE') +const hiddenCondition = !hasUpdate ? { status: AlbumStatus.SHOW } : {} const album = await prismaClient.albums.findUnique({ - where: { id: Number(id) }, + where: { id: Number(id), ...hiddenCondition }, include: { artists: { select: { artist: true } }, categories: { select: { categoryName: true } }, diff --git a/src/pages/api/album/create.ts b/src/pages/api/album/create.ts new file mode 100644 index 0000000..bf93da2 --- /dev/null +++ b/src/pages/api/album/create.ts @@ -0,0 +1,111 @@ +import type { APIRoute } from 'astro' +import * as s from 'superstruct' +import { AlbumStatus } from '@prisma/client' + +import prismaClient from 'utils/prisma-client' +import { Status, formToObject, slug } from 'utils/form' +import { writeImg, getImgColor } from 'utils/img' +import { handleComplete } from 'integrations/requestCat' +import { DownloadInput } from 'schemas/album' + +const CreateAlbum = s.object({ + cover: s.instance(File), + title: s.optional(s.string()), + subTitle: s.optional(s.string()), + releaseDate: s.optional(s.date()), + label: s.optional(s.string()), + vgmdb: s.optional(s.string()), + description: s.optional(s.string()), + status: s.defaulted(s.enums(Object.values(AlbumStatus)), AlbumStatus.HIDDEN), + animations: s.defaulted(s.array(s.integer()), []), + artists: s.defaulted(s.array(s.string()), []), + categories: s.defaulted(s.array(s.string()), []), + classifications: s.defaulted(s.array(s.string()), []), + games: s.defaulted(s.array(s.string()), []), + platforms: s.defaulted(s.array(s.integer()), []), + discs: s.defaulted(s.array(s.object({ number: s.integer(), body: s.string() })), []), + downloads: s.defaulted(s.array(DownloadInput), []), + related: s.defaulted(s.array(s.number()), []), + stores: s.defaulted(s.array(s.object({ provider: s.string(), url: s.string() })), []), + request: s.optional(s.integer()) +}) + +export const POST: APIRoute = async ({ request, locals }) => { + const { session, permissions, user } = locals + + if (!session || !user) return Status(401) + if (!permissions.includes('CREATE')) return Status(403) + + let body + try { + const formData = await request.formData() + body = s.create(formToObject(formData), CreateAlbum) + } catch (err) { + return Status(422, (err as Error).message) + } + + try { + const albumRow = await prismaClient.$transaction(async (tx) => { + const artistRows = body.artists.map((name: string) => ({ slug: slug(name), name })) + + const albumRow = await tx.albums.create({ + data: { + title: body.title, + subTitle: body.subTitle, + releaseDate: body.releaseDate, + label: body.label, + vgmdb: body.vgmdb, + description: body.description, + createdBy: user.name, + status: body.status, + animations: { create: body.animations.map((id) => ({ animation: { connect: { id } } })) }, + artists: { + create: artistRows.map((a) => ({ + artist: { + connectOrCreate: { + create: a, + where: { slug: a.slug } + } + } + })) + }, + categories: { create: body.categories.map((c) => ({ category: { connect: { name: c } } })) }, + classifications: { create: body.classifications.map((name) => ({ classification: { connect: { name } } })) }, + games: { create: body.games.map((slug) => ({ game: { connect: { slug } } })) }, + platforms: { create: body.platforms.map((id) => ({ platform: { connect: { id } } })) }, + // albumHistories + discs: { createMany: { data: body.discs } }, + relatedAlbums: { create: body.related.map((id) => ({ relatedAlbum: { connect: { id } } })) } + }, + include: { artists: { include: { artist: { select: { name: true } } } } } + }) + + const handleCover = async () => { + const coverPath = await writeImg(body.cover, 'album', albumRow.id) + const headerColor = await getImgColor(coverPath) + await tx.albums.update({ where: { id: albumRow.id }, data: { headerColor } }) + albumRow.headerColor = headerColor + } + + await Promise.all([ + handleCover(), + tx.downloads.createMany({ + data: body.downloads.map((d) => ({ + title: d.title, + small: d.small, + albumId: albumRow.id, + links: { create: d.links } + })) + }) + ]) + + return albumRow + }) + + if (albumRow.status === AlbumStatus.SHOW) await handleComplete(albumRow, body.request) + + return Status(200, albumRow.id.toString()) + } catch (err) { + return Status(500, (err as Error).message) + } +} diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index e34b02a..1bee06d 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,10 +1,11 @@ import rss, { type RSSFeedItem } from '@astrojs/rss' +import { AlbumStatus } from '@prisma/client' import type { APIContext } from 'astro' import prismaClient from 'utils/prisma-client' export async function GET(context: APIContext) { const albums = await prismaClient.albums.findMany({ - where: { status: 'show' }, + where: { status: AlbumStatus.SHOW }, include: { artists: { include: { artist: { select: { name: true } } } } }, take: 15, orderBy: { createdAt: 'desc' } diff --git a/src/schemas/album.ts b/src/schemas/album.ts new file mode 100644 index 0000000..f532cc0 --- /dev/null +++ b/src/schemas/album.ts @@ -0,0 +1,15 @@ +import * as s from 'superstruct' + +const LinkInput = s.object({ + provider: s.string(), + custom: s.optional(s.string()), + url: s.optional(s.string()), + url2: s.optional(s.string()), + directUrl: s.optional(s.string()) +}) + +export const DownloadInput = s.object({ + title: s.string(), + small: s.defaulted(s.boolean(), false), + links: s.defaulted(s.array(LinkInput), []) +}) diff --git a/src/utils/form.ts b/src/utils/form.ts new file mode 100644 index 0000000..a645620 --- /dev/null +++ b/src/utils/form.ts @@ -0,0 +1,21 @@ +import slugify from 'slugify' + +export const Status = (status: number, statusText?: string) => new Response(null, { status, statusText }) +export const slug = (text: string) => slugify(text, { lower: true, strict: true }) + +export function formToObject(formData: FormData) { + const object: Record = {} + formData.forEach((value, key) => { + // Reflect.has in favor of: object.hasOwnProperty(key) + if (!Reflect.has(object, key)) { + object[key] = value + return + } + if (!Array.isArray(object[key])) { + object[key] = [object[key]] + } + object[key].push(value) + }) + + return object +} diff --git a/src/utils/img.ts b/src/utils/img.ts new file mode 100644 index 0000000..d5b5627 --- /dev/null +++ b/src/utils/img.ts @@ -0,0 +1,29 @@ +import path from 'node:path' +import fs from 'node:fs/promises' +import sharp from 'sharp' + +function colorToHex(color: number) { + const hexadecimal = color.toString(16) + return hexadecimal.length === 1 ? '0' + hexadecimal : hexadecimal +} + +function convertRGBtoHex(red: number, green: number, blue: number) { + return '#' + colorToHex(red) + colorToHex(green) + colorToHex(blue) +} + +export async function writeImg(file: File, folder: string, id: number | string) { + const pathString = path.join('/var/www/soc_img/img', folder) + const fullPath = path.join(pathString, `${id}.png`) + + const fileArray = Buffer.from(await file.arrayBuffer()) + await fs.writeFile(fullPath, fileArray) + + return fullPath +} + +export async function getImgColor(filePath: string) { + const { dominant } = await sharp(filePath).stats() + const { r, g, b } = dominant + + return convertRGBtoHex(r, g, b) +} diff --git a/tsconfig.json b/tsconfig.json index 37f6a82..e55115b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "react", - "baseUrl": "src" + "baseUrl": "src", + "strictNullChecks": true } } diff --git a/yarn.lock b/yarn.lock index 6527360..00abb65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -271,15 +271,10 @@ dependencies: uncrypto "^0.1.3" -"@better-fetch/fetch@1.1.12": - version "1.1.12" - resolved "https://registry.yarnpkg.com/@better-fetch/fetch/-/fetch-1.1.12.tgz#1b2e4e1bda3230ae7b9b9c15259ff0ce105caa90" - integrity sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA== - -"@better-fetch/fetch@^1.1.4": - version "1.1.13" - resolved "https://registry.yarnpkg.com/@better-fetch/fetch/-/fetch-1.1.13.tgz#f19049a96b894c0c13510cfa04247651bed8fe26" - integrity sha512-C1NtueyCx6tqVITOoxScEEQyFn0R0dpqQBHsuDBhRACa+sl7rY9oirCNpRWNnYu7FmVwTAY9BHbeRdvqSJI5Og== +"@better-fetch/fetch@^1.1.15", "@better-fetch/fetch@^1.1.4": + version "1.1.15" + resolved "https://registry.yarnpkg.com/@better-fetch/fetch/-/fetch-1.1.15.tgz#d4170657315191cf7b15ab93e0f103776ee9b9ad" + integrity sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA== "@emnapi/runtime@^1.2.0": version "1.3.1" @@ -2300,6 +2295,15 @@ axios@^1.6.0, axios@^1.7.4, axios@^1.7.9: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.1.tgz#7c118d2146e9ebac512b7d1128771cdd738d11e3" + integrity sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" @@ -2326,32 +2330,33 @@ before-after-hook@^2.2.0: integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== better-auth@^1.1.11: - version "1.1.18" - resolved "https://registry.yarnpkg.com/better-auth/-/better-auth-1.1.18.tgz#339348af4a0988813ee06514323463640d17e541" - integrity sha512-cU/u7/qlSiR/OO1O53gh/bHvxyB53DE+5ZOw9DtpDI8IyIQUBqDnQ06MOwNtj8JwjXHcMFNSNd5u/W8DED0bJw== + version "1.2.3" + resolved "https://registry.yarnpkg.com/better-auth/-/better-auth-1.2.3.tgz#af6fe1ab76cbe4f029e6c17ae74bb50a1d8d258b" + integrity sha512-y97/ah2SOWaW81IRg36m7xMSMVl7ATaHie/nhQ0in/reVlEX/6juVPszNqq0gcTwQtFsB8oe15wQKgdf4yHP9Q== dependencies: "@better-auth/utils" "0.2.3" - "@better-fetch/fetch" "1.1.12" + "@better-fetch/fetch" "^1.1.15" "@noble/ciphers" "^0.6.0" "@noble/hashes" "^1.6.1" "@simplewebauthn/browser" "^13.0.0" "@simplewebauthn/server" "^13.0.0" - better-call "0.3.3" + better-call "^1.0.3" defu "^6.1.4" jose "^5.9.6" kysely "^0.27.4" nanostores "^0.11.3" + valibot "1.0.0-beta.15" zod "^3.24.1" -better-call@0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/better-call/-/better-call-0.3.3.tgz#297c0e6502cf51012626d2c6d81351b7a880dec8" - integrity sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA== +better-call@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/better-call/-/better-call-1.0.4.tgz#e80e933d5927e5bb3d933e78258b9b05411891f7" + integrity sha512-NdAihYdkS0IOz1mtz8mw1gWacCxR9r921U8YqB+VB6++rt8edMG13vVL16Y4TBL4XkjMK/DUewEsOOFkw9LJYQ== dependencies: "@better-fetch/fetch" "^1.1.4" rou3 "^0.5.1" + set-cookie-parser "^2.7.1" uncrypto "^0.1.3" - zod "^3.24.1" binary-extensions@^2.0.0: version "2.3.0" @@ -4430,9 +4435,9 @@ kolorist@^1.8.0: integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== kysely@^0.27.4: - version "0.27.5" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.5.tgz#e4662be051dd023eda2f68ee3d7e2647c94b0ef7" - integrity sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA== + version "0.27.6" + resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.6.tgz#b2a908e160e510f9945586ca9de1616c187a2a8e" + integrity sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ== language-subtag-registry@^0.3.20: version "0.3.23" @@ -6105,6 +6110,11 @@ server-destroy@^1.0.1: resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" integrity sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ== +set-cookie-parser@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -6261,6 +6271,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slugify@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" + integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== + smol-toml@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.1.tgz#d9084a9e212142e3cab27ef4e2b8e8ba620bfe15" @@ -6421,6 +6436,11 @@ strnum@^1.0.5: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +superstruct@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" + integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -6783,6 +6803,11 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +valibot@1.0.0-beta.15: + version "1.0.0-beta.15" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.0.0-beta.15.tgz#b53aa983ccec0afb0973440484cf5c066cd33a24" + integrity sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw== + vfile-location@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3"