mirror of
https://github.com/jorgev259/soc_site-astro.git
synced 2025-06-29 07:57:41 +00:00
Create Album endpoint
This commit is contained in:
parent
c3fca425cb
commit
967a6d1587
15 changed files with 388 additions and 85 deletions
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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`;
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
10
src/auth.ts
10
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,
|
||||
|
|
|
|||
58
src/integrations/requestCat.ts
Normal file
58
src/integrations/requestCat.ts
Normal file
|
|
@ -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<Prisma.albumsDefaultArgs>()({
|
||||
include: { artists: { include: { artist: { select: { name: true } } } } }
|
||||
})
|
||||
type AlbumArtistNames = Prisma.albumsGetPayload<typeof albumArtistNames>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 } },
|
||||
|
|
|
|||
111
src/pages/api/album/create.ts
Normal file
111
src/pages/api/album/create.ts
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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' }
|
||||
|
|
|
|||
15
src/schemas/album.ts
Normal file
15
src/schemas/album.ts
Normal file
|
|
@ -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), [])
|
||||
})
|
||||
21
src/utils/form.ts
Normal file
21
src/utils/form.ts
Normal file
|
|
@ -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<string, any> = {}
|
||||
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
|
||||
}
|
||||
29
src/utils/img.ts
Normal file
29
src/utils/img.ts
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": "src"
|
||||
"baseUrl": "src",
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
yarn.lock
69
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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue