Implement requests page

This commit is contained in:
Jorge Vargas 2025-06-19 19:42:24 -06:00
parent 75eaf17346
commit d3439321ba
14 changed files with 10333 additions and 7522 deletions

2
.vscode/launch.json vendored
View file

@ -13,7 +13,7 @@
} }
}, },
{ {
"command": "yarn prisma generate --watch", "command": "yarn prisma generate --sql --watch",
"name": "Prisma", "name": "Prisma",
"request": "launch", "request": "launch",
"type": "node-terminal" "type": "node-terminal"

View file

@ -90,5 +90,10 @@
"variousGames": "Various Games", "variousGames": "Various Games",
"searchResultsFor": "Search results for", "searchResultsFor": "Search results for",
"firstResults": "Showing first {take} results", "firstResults": "Showing first {take} results",
"moreResults": "Showing {start}-{end} results" "moreResults": "Showing {start}-{end} results",
"requestID": "Request ID",
"userID": "userID",
"state": "State",
"createdAt": "Created At",
"updatedAt": "Updated At"
} }

View file

@ -5,9 +5,11 @@
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"build": "yarn run prisma:build && astro build", "build": "yarn run prisma:build && astro build",
"prisma:build": "prisma migrate deploy && prisma generate --sql && tsx ./prisma/migrate.ts" "prisma:migrate": "prisma migrate deploy && tsx ./prisma/migrate.ts",
"prisma:build": "yarn prisma:migrate && prisma generate --sql && "
}, },
"dependencies": { "dependencies": {
"@ag-grid-community/locale": "^33.3.2",
"@astrojs/node": "9.0.0", "@astrojs/node": "9.0.0",
"@astrojs/react": "4.1.3", "@astrojs/react": "4.1.3",
"@astrojs/rss": "4.0.11", "@astrojs/rss": "4.0.11",
@ -15,6 +17,8 @@
"@tailwindcss/vite": "^4.0.7", "@tailwindcss/vite": "^4.0.7",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"ag-grid-community": "^33.3.1",
"ag-grid-react": "^33.3.1",
"astro": "^5.3.0", "astro": "^5.3.0",
"astro-icon": "^1.1.1", "astro-icon": "^1.1.1",
"astro-seo": "^0.8.4", "astro-seo": "^0.8.4",

View file

@ -5,7 +5,7 @@ const LIMIT_PENDING = 5
export default async function MigrationFn(tx: Prisma.TransactionClient) { export default async function MigrationFn(tx: Prisma.TransactionClient) {
const donatorRequests = await tx.requests.findMany({ const donatorRequests = await tx.requests.findMany({
where: { donator: true, state: RequestState.PENDING, userID: { not: null } } where: { donator: true, state: RequestState.PENDING }
}) })
const donatorMap = new Map<string, typeof donatorRequests>() const donatorMap = new Map<string, typeof donatorRequests>()

View file

@ -0,0 +1,19 @@
/*
Warnings:
- You are about to drop the column `user` on the `requests` table. All the data in the column will be lost.
- You are about to drop the column `createdAt` on the `roles` table. All the data in the column will be lost.
- You are about to drop the column `updatedAt` on the `roles` table. All the data in the column will be lost.
- Made the column `userID` on table `requests` required. This step will fail if there are existing NULL values in that column.
*/
DELETE FROM `requests` WHERE `userId` IS NULL;
-- AlterTable
ALTER TABLE `requests` DROP COLUMN `user`,
MODIFY `userID` VARCHAR(255) NOT NULL;
-- AlterTable
ALTER TABLE `roles` DROP COLUMN `createdAt`,
DROP COLUMN `updatedAt`;

View file

@ -1,3 +1,3 @@
# Please do not edit this file manually # Please do not edit this file manually
# It should be added in your version-control system (e.g., Git) # It should be added in your version-control system (e.g., Git)
provider = "mysql" provider = "mysql"

View file

@ -344,8 +344,7 @@ model requests {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
title String? @db.VarChar(255) title String? @db.VarChar(255)
link String? @db.VarChar(255) link String? @db.VarChar(255)
user String? @db.VarChar(255) userID String @db.VarChar(255)
userID String? @db.VarChar(255)
state RequestState state RequestState
donator Boolean donator Boolean
reason String? @db.VarChar(255) reason String? @db.VarChar(255)
@ -359,8 +358,6 @@ model requests {
model roles { model roles {
name String @id @db.VarChar(255) name String @id @db.VarChar(255)
permissions Json? permissions Json?
createdAt DateTime @db.DateTime(0)
updatedAt DateTime @db.DateTime(0)
users User_Role[] users User_Role[]
} }

View file

@ -0,0 +1,45 @@
import {
ModuleRegistry,
ColumnAutoSizeModule,
ColumnHoverModule,
TextFilterModule,
NumberFilterModule,
DateFilterModule,
QuickFilterModule,
TextEditorModule,
LargeTextEditorModule,
SelectEditorModule,
NumberEditorModule,
DateEditorModule,
CheckboxEditorModule,
LocaleModule,
ClientSideRowModelModule,
colorSchemeDark,
themeQuartz
} from 'ag-grid-community'
ModuleRegistry.registerModules([
ColumnAutoSizeModule,
ColumnHoverModule,
TextFilterModule,
NumberFilterModule,
DateFilterModule,
QuickFilterModule,
TextEditorModule,
LargeTextEditorModule,
SelectEditorModule,
NumberEditorModule,
DateEditorModule,
CheckboxEditorModule,
LocaleModule,
ClientSideRowModelModule
])
const AgGridTheme = themeQuartz.withPart(colorSchemeDark)
export default AgGridTheme
import { AG_GRID_LOCALE_EN } from '@ag-grid-community/locale'
export const AgGridLocales = {
en: { AG_GRID_LOCALE_EN }
}

View file

@ -54,7 +54,6 @@ const { session } = Astro.locals
<a href='/'><NavButton>{m.home()}</NavButton></a> <a href='/'><NavButton>{m.home()}</NavButton></a>
<a href='/last-added'><NavButton>{m.lastaddednav()}</NavButton></a> <a href='/last-added'><NavButton>{m.lastaddednav()}</NavButton></a>
<a href='/album/list'><NavButton>{m.albumlist()}</NavButton></a> <a href='/album/list'><NavButton>{m.albumlist()}</NavButton></a>
<Dropdown> <Dropdown>
{m.games()} {m.games()}
<Fragment slot='items'> <Fragment slot='items'>
@ -65,7 +64,6 @@ const { session } = Astro.locals
<DropdownItem href='/platform/list'>{m.platforms()}</DropdownItem> <DropdownItem href='/platform/list'>{m.platforms()}</DropdownItem>
</Fragment> </Fragment>
</Dropdown> </Dropdown>
<Dropdown> <Dropdown>
{m.animation()} {m.animation()}
<Fragment slot='items'> <Fragment slot='items'>
@ -74,13 +72,13 @@ const { session } = Astro.locals
<DropdownItem href='/studio/list'>{m.studios()}</DropdownItem> <DropdownItem href='/studio/list'>{m.studios()}</DropdownItem>
</Fragment> </Fragment>
</Dropdown> </Dropdown>
<a href='/requests'>
<NavButton>{m.requests()}</NavButton>
</a>
{ {
session ? ( session ? (
<> <>
<a href='/requests'>
<NavButton>{m.requests()}</NavButton>
</a>
<NavButton>{m.submitalbum()}</NavButton> <NavButton>{m.submitalbum()}</NavButton>
<Dropdown> <Dropdown>
{m.adminGrounds()} {m.adminGrounds()}

View file

@ -0,0 +1,36 @@
import { type ColDef, type GridOptions } from 'ag-grid-community'
import { AgGridReact } from 'ag-grid-react'
import type { Prisma } from '@prisma/client'
import { m } from 'paraglide/messages.js'
import AgGridTheme from 'components/AgGrid/AgGridTheme'
const gridOptions: GridOptions = {
ensureDomOrder: true,
enableCellTextSelection: true
}
const colDefs: ColDef[] = [
{ field: 'id', headerName: m.requestID(), filter: true },
{ field: 'title', headerName: m.requests(), filter: true },
{ field: 'userID', headerName: m.userID(), filter: true },
{ field: 'state', headerName: m.state(), filter: true },
{ field: 'createdAt', headerName: m.createdAt() },
{ field: 'updatedAt', headerName: m.updatedAt() }
]
export default function RequestsTable(props: { initial: Prisma.requestsGetPayload<{}>[] }) {
const { initial } = props
return (
<div className='w-full px-4 py-4'>
<AgGridReact
gridOptions={gridOptions}
rowData={initial}
columnDefs={colDefs}
theme={AgGridTheme}
autoSizeStrategy={{ type: 'fitCellContents' }}
/>
</div>
)
}

View file

@ -0,0 +1,27 @@
import type { APIRoute } from 'astro'
import * as s from 'superstruct'
import prismaClient from 'utils/prisma-client'
import { Status, parseForm } from 'utils/form'
import { EditRequest } from 'schemas/requests'
export const POST: APIRoute = async ({ request, locals }) => {
const { permissions, user } = locals
if (!user || !permissions.includes('REQUESTS')) return Status(403)
let body
try {
const formData = await parseForm(await request.formData())
body = s.create(formData, EditRequest)
} catch (err) {
return Status(422, (err as Error).message)
}
try {
await prismaClient.requests.update({ where: { id: body.id }, data: { ...body, updatedAt: new Date() } })
return Status(200, body.id.toString())
} catch (err) {
console.error(err)
return Status(500, (err as Error).message)
}
}

14
src/pages/requests.astro Normal file
View file

@ -0,0 +1,14 @@
---
import BaseLayout from 'layouts/base.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
import RequestsTable from 'components/requests/RequestsTable'
import prismaClient from 'utils/prisma-client'
const initialRequests = await prismaClient.requests.findMany({ orderBy: { createdAt: 'desc' } })
---
<DefaultSEO />
<BaseLayout>
<RequestsTable client:only='react' initial={initialRequests} />
</BaseLayout>

12
src/schemas/requests.ts Normal file
View file

@ -0,0 +1,12 @@
import { RequestState } from '@prisma/client'
import { object, string, number, optional, enums } from 'superstruct'
export const EditRequest = object({
id: number(),
title: optional(string()),
link: optional(string()),
state: enums(Object.values(RequestState)),
reason: optional(string()),
comments: optional(string()),
message: optional(string())
})

17670
yarn.lock

File diff suppressed because it is too large Load diff