릴리스: v0.1.9 MariaDB 전용 코드베이스 정리
This commit is contained in:
@@ -1,341 +0,0 @@
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"id": "nsTJTtyrDHSqfmRu5glSN",
|
||||
"email": "zenn.message@gmail.com",
|
||||
"passwordHash": "$2b$10$DxSEaZctF5u8A5rYDQRZOu4rkRNEaCytX0m0raQn6Fwjx.G0h9k8K",
|
||||
"isAdmin": true,
|
||||
"createdAt": 1773887700454,
|
||||
"avatarSrc": "/uploads/avatars/1773889900538-fef80900076ae-áá
³áá
³á
á
µá«áá
£áº 2026-03-19 áá
©áá
¥á« 11.38.05.png"
|
||||
}
|
||||
],
|
||||
"games": [
|
||||
{
|
||||
"id": "example-game",
|
||||
"name": "예시 게임",
|
||||
"createdAt": 1773887395598
|
||||
},
|
||||
{
|
||||
"id": "another-game",
|
||||
"name": "다른 예시 게임",
|
||||
"createdAt": 1773887395598
|
||||
},
|
||||
{
|
||||
"id": "stellasora",
|
||||
"name": "스텔라소라",
|
||||
"createdAt": 1773887727309,
|
||||
"thumbnailSrc": "/uploads/games/1773890252955-fbdff9a881edf-áá
³áá
³á
á
µá«áá
£áº 2026-03-19 áá
©áá
¥á« 11.23.48(2).png"
|
||||
}
|
||||
],
|
||||
"gameImages": [
|
||||
{
|
||||
"id": "img-1",
|
||||
"gameId": "example-game",
|
||||
"src": "/uploads/seeds/example1.png",
|
||||
"label": "샘플 1",
|
||||
"createdAt": 1773887395598
|
||||
},
|
||||
{
|
||||
"id": "img-2",
|
||||
"gameId": "example-game",
|
||||
"src": "/uploads/seeds/example2.png",
|
||||
"label": "샘플 2",
|
||||
"createdAt": 1773887395598
|
||||
},
|
||||
{
|
||||
"id": "dVx9t49q3CbKe_dnY9Zit",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773887834722-a3ce962de80228-Chitose-head-xxl.webp",
|
||||
"label": "치토세",
|
||||
"createdAt": 1773887834727
|
||||
},
|
||||
{
|
||||
"id": "Tf3MgX_4Pk4YZszOTfGEQ",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773887843354-3dd04644093c5-Freesia-head-xxl.webp",
|
||||
"label": "프리지아",
|
||||
"createdAt": 1773887843356
|
||||
},
|
||||
{
|
||||
"id": "ceaMtFXf7Jq-83f66T-UM",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773887854172-ea9752e9139b-Donna-head-xxl.webp",
|
||||
"label": "도나",
|
||||
"createdAt": 1773887854174
|
||||
},
|
||||
{
|
||||
"id": "2odX6CHJOyFroPZ8s57oe",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773887863903-3635cbc2e569e8-Fuyuka-head-xxl.webp",
|
||||
"label": "후유카",
|
||||
"createdAt": 1773887863905
|
||||
},
|
||||
{
|
||||
"id": "OfDKYixqM12e57ZAAcGlW",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773887879061-801daab97ebb98-Snowish_Laru-head-xxl.webp",
|
||||
"label": "라루(크리스마스)",
|
||||
"createdAt": 1773887879064
|
||||
},
|
||||
{
|
||||
"id": "P1BOwa2sMv_YbAOkhMPVS",
|
||||
"gameId": "stellasora",
|
||||
"src": "/uploads/games/1773890202889-54aedfa2cea6e-áá
³áá
³á
á
µá«áá
£áº 2026-03-16 áá
©áá
® 6.35.30.png",
|
||||
"label": "기본이미지?",
|
||||
"createdAt": 1773890202906
|
||||
}
|
||||
],
|
||||
"tierLists": [
|
||||
{
|
||||
"id": "PiAKeKNvjJb68IPYiUv-r",
|
||||
"authorId": "nsTJTtyrDHSqfmRu5glSN",
|
||||
"gameId": "stellasora",
|
||||
"title": "새 티어표1",
|
||||
"isPublic": false,
|
||||
"groups": [
|
||||
{
|
||||
"id": "gS",
|
||||
"name": "S",
|
||||
"itemIds": [
|
||||
"dVx9t49q3CbKe_dnY9Zit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gA",
|
||||
"name": "A",
|
||||
"itemIds": [
|
||||
"ceaMtFXf7Jq-83f66T-UM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gB",
|
||||
"name": "B",
|
||||
"itemIds": [
|
||||
"2odX6CHJOyFroPZ8s57oe",
|
||||
"OfDKYixqM12e57ZAAcGlW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gC",
|
||||
"name": "C",
|
||||
"itemIds": [
|
||||
"Tf3MgX_4Pk4YZszOTfGEQ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gD",
|
||||
"name": "D",
|
||||
"itemIds": []
|
||||
}
|
||||
],
|
||||
"pool": [
|
||||
{
|
||||
"id": "dVx9t49q3CbKe_dnY9Zit",
|
||||
"src": "http://localhost:5179/uploads/games/1773887834722-a3ce962de80228-Chitose-head-xxl.webp",
|
||||
"label": "치토세",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "Tf3MgX_4Pk4YZszOTfGEQ",
|
||||
"src": "http://localhost:5179/uploads/games/1773887843354-3dd04644093c5-Freesia-head-xxl.webp",
|
||||
"label": "프리지아",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "ceaMtFXf7Jq-83f66T-UM",
|
||||
"src": "http://localhost:5179/uploads/games/1773887854172-ea9752e9139b-Donna-head-xxl.webp",
|
||||
"label": "도나",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "2odX6CHJOyFroPZ8s57oe",
|
||||
"src": "http://localhost:5179/uploads/games/1773887863903-3635cbc2e569e8-Fuyuka-head-xxl.webp",
|
||||
"label": "후유카",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "OfDKYixqM12e57ZAAcGlW",
|
||||
"src": "http://localhost:5179/uploads/games/1773887879061-801daab97ebb98-Snowish_Laru-head-xxl.webp",
|
||||
"label": "라루(크리스마스)",
|
||||
"origin": "game"
|
||||
}
|
||||
],
|
||||
"createdAt": 1773888446012,
|
||||
"updatedAt": 1773888446013
|
||||
},
|
||||
{
|
||||
"id": "F1qD3tWgW1aPJkLPjeWWA",
|
||||
"authorId": "nsTJTtyrDHSqfmRu5glSN",
|
||||
"gameId": "stellasora",
|
||||
"title": "새 티어표1",
|
||||
"isPublic": true,
|
||||
"groups": [
|
||||
{
|
||||
"id": "gS",
|
||||
"name": "S",
|
||||
"itemIds": [
|
||||
"dVx9t49q3CbKe_dnY9Zit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gA",
|
||||
"name": "A",
|
||||
"itemIds": [
|
||||
"ceaMtFXf7Jq-83f66T-UM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gB",
|
||||
"name": "B",
|
||||
"itemIds": [
|
||||
"2odX6CHJOyFroPZ8s57oe",
|
||||
"OfDKYixqM12e57ZAAcGlW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gC",
|
||||
"name": "C",
|
||||
"itemIds": [
|
||||
"Tf3MgX_4Pk4YZszOTfGEQ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gD",
|
||||
"name": "D",
|
||||
"itemIds": [
|
||||
"c-1773888464832-e963464a5f73e8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pool": [
|
||||
{
|
||||
"id": "dVx9t49q3CbKe_dnY9Zit",
|
||||
"src": "http://localhost:5179/uploads/games/1773887834722-a3ce962de80228-Chitose-head-xxl.webp",
|
||||
"label": "치토세",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "Tf3MgX_4Pk4YZszOTfGEQ",
|
||||
"src": "http://localhost:5179/uploads/games/1773887843354-3dd04644093c5-Freesia-head-xxl.webp",
|
||||
"label": "프리지아",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "ceaMtFXf7Jq-83f66T-UM",
|
||||
"src": "http://localhost:5179/uploads/games/1773887854172-ea9752e9139b-Donna-head-xxl.webp",
|
||||
"label": "도나",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "2odX6CHJOyFroPZ8s57oe",
|
||||
"src": "http://localhost:5179/uploads/games/1773887863903-3635cbc2e569e8-Fuyuka-head-xxl.webp",
|
||||
"label": "후유카",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "OfDKYixqM12e57ZAAcGlW",
|
||||
"src": "http://localhost:5179/uploads/games/1773887879061-801daab97ebb98-Snowish_Laru-head-xxl.webp",
|
||||
"label": "라루(크리스마스)",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "c-1773888464832-e963464a5f73e8",
|
||||
"src": "blob:http://localhost:5174/10e01324-bbc9-403f-bfe4-0dd7e47eace7",
|
||||
"label": "Chixia-head-xxl.webp",
|
||||
"origin": "custom"
|
||||
}
|
||||
],
|
||||
"createdAt": 1773888448774,
|
||||
"updatedAt": 1773890284754,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "zq8qdUD6q541v5l6X0wAg",
|
||||
"authorId": "nsTJTtyrDHSqfmRu5glSN",
|
||||
"gameId": "stellasora",
|
||||
"title": "스텔라소라 개쩜",
|
||||
"description": "설명 이렇게 적어봄 이건 개쩌는 티어표임",
|
||||
"isPublic": true,
|
||||
"groups": [
|
||||
{
|
||||
"id": "gS",
|
||||
"name": "S",
|
||||
"itemIds": [
|
||||
"dVx9t49q3CbKe_dnY9Zit",
|
||||
"ceaMtFXf7Jq-83f66T-UM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gA",
|
||||
"name": "A",
|
||||
"itemIds": [
|
||||
"Tf3MgX_4Pk4YZszOTfGEQ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gC",
|
||||
"name": "C",
|
||||
"itemIds": [
|
||||
"OfDKYixqM12e57ZAAcGlW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gB",
|
||||
"name": "B",
|
||||
"itemIds": [
|
||||
"2odX6CHJOyFroPZ8s57oe"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gD",
|
||||
"name": "쓰레기",
|
||||
"itemIds": [
|
||||
"P1BOwa2sMv_YbAOkhMPVS"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pool": [
|
||||
{
|
||||
"id": "dVx9t49q3CbKe_dnY9Zit",
|
||||
"src": "http://localhost:5179/uploads/games/1773887834722-a3ce962de80228-Chitose-head-xxl.webp",
|
||||
"label": "치토세",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "Tf3MgX_4Pk4YZszOTfGEQ",
|
||||
"src": "http://localhost:5179/uploads/games/1773887843354-3dd04644093c5-Freesia-head-xxl.webp",
|
||||
"label": "프리지아",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "ceaMtFXf7Jq-83f66T-UM",
|
||||
"src": "http://localhost:5179/uploads/games/1773887854172-ea9752e9139b-Donna-head-xxl.webp",
|
||||
"label": "도나",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "2odX6CHJOyFroPZ8s57oe",
|
||||
"src": "http://localhost:5179/uploads/games/1773887863903-3635cbc2e569e8-Fuyuka-head-xxl.webp",
|
||||
"label": "후유카",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "OfDKYixqM12e57ZAAcGlW",
|
||||
"src": "http://localhost:5179/uploads/games/1773887879061-801daab97ebb98-Snowish_Laru-head-xxl.webp",
|
||||
"label": "라루(크리스마스)",
|
||||
"origin": "game"
|
||||
},
|
||||
{
|
||||
"id": "P1BOwa2sMv_YbAOkhMPVS",
|
||||
"src": "http://localhost:5179/uploads/games/1773890202889-54aedfa2cea6e-áá
³áá
³á
á
µá«áá
£áº 2026-03-16 áá
©áá
® 6.35.30.png",
|
||||
"label": "기본이미지?",
|
||||
"origin": "game"
|
||||
}
|
||||
],
|
||||
"createdAt": 1773890445513,
|
||||
"updatedAt": 1773890445513
|
||||
}
|
||||
],
|
||||
"customItems": [],
|
||||
"gameSuggestions": []
|
||||
}
|
||||
28
backend/package-lock.json
generated
28
backend/package-lock.json
generated
@@ -13,7 +13,6 @@
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"multer": "^2.1.1",
|
||||
"mysql2": "^3.20.0",
|
||||
"nanoid": "^5.1.7",
|
||||
@@ -889,21 +888,6 @@
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lowdb": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz",
|
||||
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"steno": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
|
||||
@@ -1589,18 +1573,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/steno": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz",
|
||||
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "DB_CLIENT=mariadb DB_HOST=127.0.0.1 DB_PORT=3307 DB_USER=tier_cursor DB_PASSWORD=tier_cursor1234 DB_NAME=tier_cursor nodemon --legacy-watch --watch index.js --watch src index.js",
|
||||
"start": "DB_CLIENT=mariadb DB_HOST=127.0.0.1 DB_PORT=3307 DB_USER=tier_cursor DB_PASSWORD=tier_cursor1234 DB_NAME=tier_cursor node index.js",
|
||||
"dev:lowdb": "nodemon --legacy-watch --watch index.js --watch src index.js",
|
||||
"start:lowdb": "node index.js",
|
||||
"migrate:lowdb": "DB_CLIENT=mariadb DB_HOST=127.0.0.1 DB_PORT=3307 DB_USER=tier_cursor DB_PASSWORD=tier_cursor1234 DB_NAME=tier_cursor node scripts/migrate-lowdb-to-mariadb.js"
|
||||
"dev": "DB_HOST=127.0.0.1 DB_PORT=3307 DB_USER=tier_cursor DB_PASSWORD=tier_cursor1234 DB_NAME=tier_cursor nodemon --legacy-watch --watch index.js --watch src index.js",
|
||||
"start": "DB_HOST=127.0.0.1 DB_PORT=3307 DB_USER=tier_cursor DB_PASSWORD=tier_cursor1234 DB_NAME=tier_cursor node index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -19,7 +16,6 @@
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"multer": "^2.1.1",
|
||||
"mysql2": "^3.20.0",
|
||||
"nanoid": "^5.1.7",
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const {
|
||||
ensureData,
|
||||
findUserByEmail,
|
||||
createUser,
|
||||
findGameById,
|
||||
createGame,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
createCustomItem,
|
||||
findTierListById,
|
||||
saveTierList,
|
||||
createGameSuggestion,
|
||||
} = require('../src/db')
|
||||
|
||||
async function run() {
|
||||
const sourcePath = path.join(__dirname, '..', 'data', 'db.json')
|
||||
const raw = fs.readFileSync(sourcePath, 'utf8')
|
||||
const data = JSON.parse(raw)
|
||||
|
||||
await ensureData()
|
||||
|
||||
for (const user of data.users || []) {
|
||||
const existing = await findUserByEmail(user.email)
|
||||
if (!existing) {
|
||||
await createUser({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
nickname: user.nickname || '',
|
||||
passwordHash: user.passwordHash,
|
||||
isAdmin: !!user.isAdmin,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const game of data.games || []) {
|
||||
const existing = await findGameById(game.id)
|
||||
if (!existing) {
|
||||
await createGame({ id: game.id, name: game.name })
|
||||
}
|
||||
if (game.thumbnailSrc) {
|
||||
await updateGameThumbnail(game.id, game.thumbnailSrc)
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of data.gameImages || []) {
|
||||
try {
|
||||
await createGameItem({
|
||||
id: item.id,
|
||||
gameId: item.gameId,
|
||||
src: item.src,
|
||||
label: item.label,
|
||||
})
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
for (const suggestion of data.gameSuggestions || []) {
|
||||
try {
|
||||
await createGameSuggestion({ id: suggestion.id, name: suggestion.name })
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const seenCustomIds = new Set()
|
||||
for (const tierList of data.tierLists || []) {
|
||||
for (const item of tierList.pool || []) {
|
||||
if (item.origin !== 'custom' || seenCustomIds.has(item.id)) continue
|
||||
seenCustomIds.add(item.id)
|
||||
try {
|
||||
await createCustomItem({
|
||||
id: item.id,
|
||||
ownerId: tierList.authorId,
|
||||
src: item.src,
|
||||
label: item.label,
|
||||
})
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
for (const tierList of data.tierLists || []) {
|
||||
const existing = await findTierListById(tierList.id)
|
||||
if (!existing) {
|
||||
await saveTierList({
|
||||
id: tierList.id,
|
||||
authorId: tierList.authorId,
|
||||
gameId: tierList.gameId,
|
||||
title: tierList.title,
|
||||
description: tierList.description || '',
|
||||
isPublic: !!tierList.isPublic,
|
||||
groups: tierList.groups || [],
|
||||
pool: tierList.pool || [],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log('migrate-lowdb-to-mariadb: done')
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,9 +1,4 @@
|
||||
const path = require('path')
|
||||
const mysql = require('mysql2/promise')
|
||||
const { Low } = require('lowdb')
|
||||
const { JSONFile } = require('lowdb/node')
|
||||
|
||||
const DB_CLIENT = process.env.DB_CLIENT || 'lowdb'
|
||||
|
||||
const DB_HOST = process.env.DB_HOST || '127.0.0.1'
|
||||
const DB_PORT = process.env.DB_PORT ? Number(process.env.DB_PORT) : 3306
|
||||
@@ -12,9 +7,6 @@ const DB_PASSWORD = process.env.DB_PASSWORD || ''
|
||||
const DB_NAME = process.env.DB_NAME || 'tier_cursor'
|
||||
const DB_CONNECTION_LIMIT = process.env.DB_CONNECTION_LIMIT ? Number(process.env.DB_CONNECTION_LIMIT) : 10
|
||||
|
||||
const LOWDB_PATH = path.join(__dirname, '..', 'data', 'db.json')
|
||||
|
||||
let lowdbPromise = null
|
||||
let poolPromise = null
|
||||
let initPromise = null
|
||||
|
||||
@@ -22,23 +14,6 @@ function now() {
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
function defaultData() {
|
||||
return {
|
||||
users: [],
|
||||
games: [
|
||||
{ id: 'example-game', name: '예시 게임', thumbnailSrc: '', createdAt: now() },
|
||||
{ id: 'another-game', name: '다른 예시 게임', thumbnailSrc: '', createdAt: now() },
|
||||
],
|
||||
gameImages: [
|
||||
{ id: 'img-1', gameId: 'example-game', src: '/uploads/seeds/example1.png', label: '샘플 1', createdAt: now() },
|
||||
{ id: 'img-2', gameId: 'example-game', src: '/uploads/seeds/example2.png', label: '샘플 2', createdAt: now() },
|
||||
],
|
||||
customItems: [],
|
||||
tierLists: [],
|
||||
gameSuggestions: [],
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson(value, fallback) {
|
||||
if (!value) return fallback
|
||||
try {
|
||||
@@ -101,25 +76,6 @@ function mapTierListRow(row) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getLowdb() {
|
||||
if (lowdbPromise) return lowdbPromise
|
||||
lowdbPromise = (async () => {
|
||||
const adapter = new JSONFile(LOWDB_PATH)
|
||||
const db = new Low(adapter, defaultData())
|
||||
await db.read()
|
||||
db.data ||= defaultData()
|
||||
db.data.users ||= []
|
||||
db.data.games ||= []
|
||||
db.data.gameImages ||= []
|
||||
db.data.customItems ||= []
|
||||
db.data.tierLists ||= []
|
||||
db.data.gameSuggestions ||= []
|
||||
await db.write()
|
||||
return db
|
||||
})()
|
||||
return lowdbPromise
|
||||
}
|
||||
|
||||
async function createPool() {
|
||||
const rootConnection = await mysql.createConnection({
|
||||
host: DB_HOST,
|
||||
@@ -158,7 +114,7 @@ async function query(sql, params = []) {
|
||||
return rows
|
||||
}
|
||||
|
||||
async function ensureMariaSchema() {
|
||||
async function ensureSchema() {
|
||||
if (initPromise) return initPromise
|
||||
initPromise = (async () => {
|
||||
await query(`
|
||||
@@ -274,154 +230,69 @@ async function ensureMariaSchema() {
|
||||
}
|
||||
|
||||
async function ensureData() {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
await ensureMariaSchema()
|
||||
return
|
||||
}
|
||||
await getLowdb()
|
||||
await ensureSchema()
|
||||
}
|
||||
|
||||
async function countUsers() {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query('SELECT COUNT(*) AS count FROM users')
|
||||
return Number(rows[0]?.count || 0)
|
||||
}
|
||||
const db = await getLowdb()
|
||||
return db.data.users.length
|
||||
const rows = await query('SELECT COUNT(*) AS count FROM users')
|
||||
return Number(rows[0]?.count || 0)
|
||||
}
|
||||
|
||||
async function findUserByEmail(email) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query(
|
||||
'SELECT id, email, nickname, password_hash, is_admin, avatar_src, created_at FROM users WHERE email = ? LIMIT 1',
|
||||
[email]
|
||||
)
|
||||
const row = rows[0]
|
||||
if (!row) return null
|
||||
return { ...mapUserRow(row), passwordHash: row.password_hash }
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
return db.data.users.find((user) => user.email === email) || null
|
||||
const rows = await query(
|
||||
'SELECT id, email, nickname, password_hash, is_admin, avatar_src, created_at FROM users WHERE email = ? LIMIT 1',
|
||||
[email]
|
||||
)
|
||||
const row = rows[0]
|
||||
if (!row) return null
|
||||
return { ...mapUserRow(row), passwordHash: row.password_hash }
|
||||
}
|
||||
|
||||
async function findUserById(id) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query(
|
||||
'SELECT id, email, nickname, is_admin, avatar_src, created_at FROM users WHERE id = ? LIMIT 1',
|
||||
[id]
|
||||
)
|
||||
return mapUserRow(rows[0])
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const user = db.data.users.find((entry) => entry.id === id)
|
||||
if (!user) return null
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
nickname: user.nickname || '',
|
||||
isAdmin: !!user.isAdmin,
|
||||
avatarSrc: user.avatarSrc || '',
|
||||
createdAt: Number(user.createdAt),
|
||||
}
|
||||
const rows = await query(
|
||||
'SELECT id, email, nickname, is_admin, avatar_src, created_at FROM users WHERE id = ? LIMIT 1',
|
||||
[id]
|
||||
)
|
||||
return mapUserRow(rows[0])
|
||||
}
|
||||
|
||||
async function createUser({ id, email, nickname, passwordHash, isAdmin }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const createdAt = now()
|
||||
await query(
|
||||
`
|
||||
INSERT INTO users (id, email, nickname, password_hash, is_admin, avatar_src, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[id, email, nickname || '', passwordHash, isAdmin ? 1 : 0, '', createdAt]
|
||||
)
|
||||
return findUserById(id)
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const user = {
|
||||
id,
|
||||
email,
|
||||
nickname: nickname || '',
|
||||
passwordHash,
|
||||
isAdmin: !!isAdmin,
|
||||
avatarSrc: '',
|
||||
createdAt: now(),
|
||||
}
|
||||
db.data.users.push(user)
|
||||
await db.write()
|
||||
const createdAt = now()
|
||||
await query(
|
||||
`
|
||||
INSERT INTO users (id, email, nickname, password_hash, is_admin, avatar_src, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[id, email, nickname || '', passwordHash, isAdmin ? 1 : 0, '', createdAt]
|
||||
)
|
||||
return findUserById(id)
|
||||
}
|
||||
|
||||
async function updateUserProfile({ id, nickname, avatarSrc }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
if (typeof avatarSrc === 'string') {
|
||||
await query('UPDATE users SET nickname = ?, avatar_src = ? WHERE id = ?', [nickname || '', avatarSrc, id])
|
||||
} else {
|
||||
await query('UPDATE users SET nickname = ? WHERE id = ?', [nickname || '', id])
|
||||
}
|
||||
return findUserById(id)
|
||||
if (typeof avatarSrc === 'string') {
|
||||
await query('UPDATE users SET nickname = ?, avatar_src = ? WHERE id = ?', [nickname || '', avatarSrc, id])
|
||||
} else {
|
||||
await query('UPDATE users SET nickname = ? WHERE id = ?', [nickname || '', id])
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const user = db.data.users.find((entry) => entry.id === id)
|
||||
if (!user) return null
|
||||
user.nickname = nickname || ''
|
||||
if (typeof avatarSrc === 'string') user.avatarSrc = avatarSrc
|
||||
await db.write()
|
||||
return findUserById(id)
|
||||
}
|
||||
|
||||
async function listGames() {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games ORDER BY created_at ASC, name ASC')
|
||||
return rows.map(mapGameRow)
|
||||
}
|
||||
const db = await getLowdb()
|
||||
return db.data.games.map((game) => ({
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
thumbnailSrc: game.thumbnailSrc || '',
|
||||
createdAt: Number(game.createdAt),
|
||||
}))
|
||||
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games ORDER BY created_at ASC, name ASC')
|
||||
return rows.map(mapGameRow)
|
||||
}
|
||||
|
||||
async function findGameById(id) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games WHERE id = ? LIMIT 1', [id])
|
||||
return mapGameRow(rows[0])
|
||||
}
|
||||
const db = await getLowdb()
|
||||
const game = db.data.games.find((entry) => entry.id === id)
|
||||
if (!game) return null
|
||||
return {
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
thumbnailSrc: game.thumbnailSrc || '',
|
||||
createdAt: Number(game.createdAt),
|
||||
}
|
||||
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games WHERE id = ? LIMIT 1', [id])
|
||||
return mapGameRow(rows[0])
|
||||
}
|
||||
|
||||
async function listGameItems(gameId) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query(
|
||||
'SELECT id, game_id, src, label, created_at FROM game_items WHERE game_id = ? ORDER BY created_at ASC',
|
||||
[gameId]
|
||||
)
|
||||
return rows.map(mapGameItemRow)
|
||||
}
|
||||
const db = await getLowdb()
|
||||
return db.data.gameImages
|
||||
.filter((item) => item.gameId === gameId)
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
gameId: item.gameId,
|
||||
src: item.src,
|
||||
label: item.label,
|
||||
createdAt: Number(item.createdAt),
|
||||
}))
|
||||
const rows = await query(
|
||||
'SELECT id, game_id, src, label, created_at FROM game_items WHERE game_id = ? ORDER BY created_at ASC',
|
||||
[gameId]
|
||||
)
|
||||
return rows.map(mapGameItemRow)
|
||||
}
|
||||
|
||||
async function getGameDetail(gameId) {
|
||||
@@ -432,276 +303,184 @@ async function getGameDetail(gameId) {
|
||||
}
|
||||
|
||||
async function createGame({ id, name }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
await query('INSERT INTO games (id, name, thumbnail_src, created_at) VALUES (?, ?, ?, ?)', [id, name, '', now()])
|
||||
return findGameById(id)
|
||||
}
|
||||
const db = await getLowdb()
|
||||
db.data.games.push({ id, name, thumbnailSrc: '', createdAt: now() })
|
||||
await db.write()
|
||||
await query('INSERT INTO games (id, name, thumbnail_src, created_at) VALUES (?, ?, ?, ?)', [id, name, '', now()])
|
||||
return findGameById(id)
|
||||
}
|
||||
|
||||
async function updateGameThumbnail(gameId, thumbnailSrc) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
await query('UPDATE games SET thumbnail_src = ? WHERE id = ?', [thumbnailSrc, gameId])
|
||||
return findGameById(gameId)
|
||||
}
|
||||
const db = await getLowdb()
|
||||
const game = db.data.games.find((entry) => entry.id === gameId)
|
||||
if (!game) return null
|
||||
game.thumbnailSrc = thumbnailSrc
|
||||
await db.write()
|
||||
await query('UPDATE games SET thumbnail_src = ? WHERE id = ?', [thumbnailSrc, gameId])
|
||||
return findGameById(gameId)
|
||||
}
|
||||
|
||||
async function createGameItem({ id, gameId, src, label }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO game_items (id, game_id, src, label, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
gameId,
|
||||
src,
|
||||
label,
|
||||
createdAt,
|
||||
])
|
||||
const rows = await query('SELECT id, game_id, src, label, created_at FROM game_items WHERE id = ? LIMIT 1', [id])
|
||||
return mapGameItemRow(rows[0])
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const item = { id, gameId, src, label, createdAt: now() }
|
||||
db.data.gameImages.push(item)
|
||||
await db.write()
|
||||
return {
|
||||
id: item.id,
|
||||
gameId: item.gameId,
|
||||
src: item.src,
|
||||
label: item.label,
|
||||
createdAt: Number(item.createdAt),
|
||||
}
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO game_items (id, game_id, src, label, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
gameId,
|
||||
src,
|
||||
label,
|
||||
createdAt,
|
||||
])
|
||||
const rows = await query('SELECT id, game_id, src, label, created_at FROM game_items WHERE id = ? LIMIT 1', [id])
|
||||
return mapGameItemRow(rows[0])
|
||||
}
|
||||
|
||||
async function createCustomItem({ id, ownerId, src, label }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO custom_items (id, owner_id, src, label, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
ownerId,
|
||||
src,
|
||||
label,
|
||||
createdAt,
|
||||
])
|
||||
return { id, ownerId, src, label, origin: 'custom', createdAt }
|
||||
}
|
||||
async function deleteGameItem(itemId) {
|
||||
const gameItemRows = await query('SELECT game_id FROM game_items WHERE id = ? LIMIT 1', [itemId])
|
||||
const gameId = gameItemRows[0]?.game_id
|
||||
|
||||
const db = await getLowdb()
|
||||
const item = { id, ownerId, src, label, origin: 'custom', createdAt: now() }
|
||||
db.data.customItems.push(item)
|
||||
await db.write()
|
||||
return item
|
||||
}
|
||||
|
||||
async function createGameSuggestion({ id, name }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO game_suggestions (id, name, created_at) VALUES (?, ?, ?)', [id, name, createdAt])
|
||||
return { id, name, createdAt }
|
||||
}
|
||||
const db = await getLowdb()
|
||||
const suggestion = { id, name, createdAt: now() }
|
||||
db.data.gameSuggestions.push(suggestion)
|
||||
await db.write()
|
||||
return suggestion
|
||||
}
|
||||
|
||||
async function listPublicTierLists(gameId) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const params = []
|
||||
let whereClause = 'WHERE t.is_public = 1'
|
||||
if (gameId) {
|
||||
whereClause += ' AND t.game_id = ?'
|
||||
params.push(gameId)
|
||||
}
|
||||
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.id,
|
||||
t.game_id,
|
||||
t.title,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.author_id,
|
||||
u.nickname,
|
||||
u.email
|
||||
FROM tierlists t
|
||||
INNER JOIN users u ON u.id = t.author_id
|
||||
${whereClause}
|
||||
ORDER BY t.updated_at DESC
|
||||
LIMIT 50
|
||||
`,
|
||||
params
|
||||
)
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
authorId: row.author_id,
|
||||
authorName: row.nickname || row.email,
|
||||
}))
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
return db.data.tierLists
|
||||
.filter((tierList) => tierList.isPublic && (!gameId || tierList.gameId === gameId))
|
||||
.sort((a, b) => Number(b.updatedAt) - Number(a.updatedAt))
|
||||
.slice(0, 50)
|
||||
.map((tierList) => {
|
||||
const author = db.data.users.find((user) => user.id === tierList.authorId)
|
||||
return {
|
||||
id: tierList.id,
|
||||
gameId: tierList.gameId,
|
||||
title: tierList.title,
|
||||
createdAt: Number(tierList.createdAt),
|
||||
updatedAt: Number(tierList.updatedAt),
|
||||
authorId: tierList.authorId,
|
||||
authorName: author?.nickname || author?.email || '알 수 없음',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function listUserTierLists(userId) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, game_id, title, created_at, updated_at, is_public
|
||||
FROM tierlists
|
||||
WHERE author_id = ?
|
||||
ORDER BY updated_at DESC
|
||||
`,
|
||||
[userId]
|
||||
)
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
isPublic: !!row.is_public,
|
||||
}))
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
return db.data.tierLists
|
||||
.filter((tierList) => tierList.authorId === userId)
|
||||
.sort((a, b) => Number(b.updatedAt) - Number(a.updatedAt))
|
||||
.map((tierList) => ({
|
||||
id: tierList.id,
|
||||
gameId: tierList.gameId,
|
||||
title: tierList.title,
|
||||
createdAt: Number(tierList.createdAt),
|
||||
updatedAt: Number(tierList.updatedAt),
|
||||
isPublic: !!tierList.isPublic,
|
||||
}))
|
||||
}
|
||||
|
||||
async function findTierListById(id) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const rows = await query(
|
||||
if (gameId) {
|
||||
const tierListRows = await query(
|
||||
`
|
||||
SELECT id, author_id, game_id, title, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
FROM tierlists
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
WHERE game_id = ?
|
||||
`,
|
||||
[id]
|
||||
[gameId]
|
||||
)
|
||||
return mapTierListRow(rows[0])
|
||||
|
||||
for (const row of tierListRows) {
|
||||
const tierList = mapTierListRow(row)
|
||||
const nextGroups = (tierList.groups || []).map((group) => ({
|
||||
...group,
|
||||
itemIds: (group.itemIds || []).filter((id) => id !== itemId),
|
||||
}))
|
||||
const nextPool = (tierList.pool || []).filter((item) => item.id !== itemId)
|
||||
|
||||
await query(
|
||||
'UPDATE tierlists SET groups_json = ?, pool_json = ?, updated_at = ? WHERE id = ?',
|
||||
[serializeJson(nextGroups), serializeJson(nextPool), now(), tierList.id]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const tierList = db.data.tierLists.find((entry) => entry.id === id)
|
||||
if (!tierList) return null
|
||||
return {
|
||||
id: tierList.id,
|
||||
authorId: tierList.authorId,
|
||||
gameId: tierList.gameId,
|
||||
title: tierList.title,
|
||||
description: tierList.description || '',
|
||||
isPublic: !!tierList.isPublic,
|
||||
groups: Array.isArray(tierList.groups) ? tierList.groups : [],
|
||||
pool: Array.isArray(tierList.pool) ? tierList.pool : [],
|
||||
createdAt: Number(tierList.createdAt),
|
||||
updatedAt: Number(tierList.updatedAt),
|
||||
await query('DELETE FROM game_items WHERE id = ?', [itemId])
|
||||
}
|
||||
|
||||
async function deleteGame(gameId) {
|
||||
await query('DELETE FROM games WHERE id = ?', [gameId])
|
||||
}
|
||||
|
||||
async function createCustomItem({ id, ownerId, src, label }) {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO custom_items (id, owner_id, src, label, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
ownerId,
|
||||
src,
|
||||
label,
|
||||
createdAt,
|
||||
])
|
||||
return { id, ownerId, src, label, origin: 'custom', createdAt }
|
||||
}
|
||||
|
||||
async function createGameSuggestion({ id, name }) {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO game_suggestions (id, name, created_at) VALUES (?, ?, ?)', [id, name, createdAt])
|
||||
return { id, name, createdAt }
|
||||
}
|
||||
|
||||
async function listPublicTierLists(gameId) {
|
||||
const params = []
|
||||
let whereClause = 'WHERE t.is_public = 1'
|
||||
if (gameId) {
|
||||
whereClause += ' AND t.game_id = ?'
|
||||
params.push(gameId)
|
||||
}
|
||||
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.id,
|
||||
t.game_id,
|
||||
t.title,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.author_id,
|
||||
u.nickname,
|
||||
u.email
|
||||
FROM tierlists t
|
||||
INNER JOIN users u ON u.id = t.author_id
|
||||
${whereClause}
|
||||
ORDER BY t.updated_at DESC
|
||||
LIMIT 50
|
||||
`,
|
||||
params
|
||||
)
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
authorId: row.author_id,
|
||||
authorName: row.nickname || row.email,
|
||||
}))
|
||||
}
|
||||
|
||||
async function listUserTierLists(userId) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, game_id, title, created_at, updated_at, is_public
|
||||
FROM tierlists
|
||||
WHERE author_id = ?
|
||||
ORDER BY updated_at DESC
|
||||
`,
|
||||
[userId]
|
||||
)
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
isPublic: !!row.is_public,
|
||||
}))
|
||||
}
|
||||
|
||||
async function findTierListById(id) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, author_id, game_id, title, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
FROM tierlists
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[id]
|
||||
)
|
||||
return mapTierListRow(rows[0])
|
||||
}
|
||||
|
||||
async function saveTierList({ id, authorId, gameId, title, description, isPublic, groups, pool }) {
|
||||
if (DB_CLIENT === 'mariadb') {
|
||||
const existing = id ? await findTierListById(id) : null
|
||||
const existing = id ? await findTierListById(id) : null
|
||||
|
||||
if (existing) {
|
||||
await query(
|
||||
`
|
||||
UPDATE tierlists
|
||||
SET title = ?, description = ?, is_public = ?, groups_json = ?, pool_json = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`,
|
||||
[title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), now(), existing.id]
|
||||
)
|
||||
return findTierListById(existing.id)
|
||||
}
|
||||
|
||||
const createdAt = now()
|
||||
if (existing) {
|
||||
await query(
|
||||
`
|
||||
INSERT INTO tierlists (
|
||||
id, author_id, game_id, title, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
UPDATE tierlists
|
||||
SET title = ?, description = ?, is_public = ?, groups_json = ?, pool_json = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`,
|
||||
[id, authorId, gameId, title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
[title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), now(), existing.id]
|
||||
)
|
||||
return findTierListById(id)
|
||||
}
|
||||
|
||||
const db = await getLowdb()
|
||||
const existing = db.data.tierLists.find((entry) => entry.id === id)
|
||||
if (existing) {
|
||||
existing.title = title
|
||||
existing.description = description || ''
|
||||
existing.isPublic = !!isPublic
|
||||
existing.groups = groups
|
||||
existing.pool = pool
|
||||
existing.updatedAt = now()
|
||||
await db.write()
|
||||
return findTierListById(existing.id)
|
||||
}
|
||||
|
||||
const tierList = {
|
||||
id,
|
||||
authorId,
|
||||
gameId,
|
||||
title,
|
||||
description: description || '',
|
||||
isPublic: !!isPublic,
|
||||
groups,
|
||||
pool,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
}
|
||||
db.data.tierLists.push(tierList)
|
||||
await db.write()
|
||||
const createdAt = now()
|
||||
await query(
|
||||
`
|
||||
INSERT INTO tierlists (
|
||||
id, author_id, game_id, title, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[id, authorId, gameId, title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
)
|
||||
return findTierListById(id)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DB_CLIENT,
|
||||
DB_NAME,
|
||||
ensureData,
|
||||
countUsers,
|
||||
@@ -716,6 +495,8 @@ module.exports = {
|
||||
createGame,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
createCustomItem,
|
||||
createGameSuggestion,
|
||||
listPublicTierLists,
|
||||
|
||||
@@ -8,6 +8,8 @@ const {
|
||||
createGame,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
} = require('../db')
|
||||
const { requireAdmin } = require('../middleware/auth')
|
||||
|
||||
@@ -61,4 +63,18 @@ router.post('/games/:gameId/images', requireAdmin, upload.single('image'), async
|
||||
res.json({ item })
|
||||
})
|
||||
|
||||
router.delete('/games/:gameId/items/:itemId', requireAdmin, async (req, res) => {
|
||||
const game = await findGameById(req.params.gameId)
|
||||
if (!game) return res.status(404).json({ error: 'not_found' })
|
||||
await deleteGameItem(req.params.itemId)
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
router.delete('/games/:gameId', requireAdmin, async (req, res) => {
|
||||
const game = await findGameById(req.params.gameId)
|
||||
if (!game) return res.status(404).json({ error: 'not_found' })
|
||||
await deleteGame(req.params.gameId)
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
||||
Reference in New Issue
Block a user