правки по админ тулу
This commit is contained in:
parent
365d04b678
commit
a8241d93c6
|
|
@ -54,6 +54,29 @@
|
||||||
<button @click="handleResetVillage" :disabled="isSubmittingAdminAction">Reset Village</button>
|
<button @click="handleResetVillage" :disabled="isSubmittingAdminAction">Reset Village</button>
|
||||||
<button @click="handleCompleteClearing" :disabled="isSubmittingAdminAction">Complete All Clearing</button>
|
<button @click="handleCompleteClearing" :disabled="isSubmittingAdminAction">Complete All Clearing</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Event Log -->
|
||||||
|
<div v-if="villageEvents?.length" class="event-log-container">
|
||||||
|
<h4>Activity Log</h4>
|
||||||
|
<table class="event-log-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Event</th>
|
||||||
|
<th>Coins</th>
|
||||||
|
<th>EXP</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="event in villageEvents" :key="event.id">
|
||||||
|
<td>{{ new Date(event.createdAt).toLocaleString() }}</td>
|
||||||
|
<td>{{ event.message }}</td>
|
||||||
|
<td>{{ event.coins }}</td>
|
||||||
|
<td>{{ event.exp }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -65,6 +88,11 @@ const { data: villageData, pending, error, refresh: refreshVillageData } = await
|
||||||
server: false, // Ensure this runs on the client-side
|
server: false, // Ensure this runs on the client-side
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: villageEvents, refresh: refreshEvents } = await useFetch('/api/village/events', {
|
||||||
|
lazy: true,
|
||||||
|
server: false,
|
||||||
|
});
|
||||||
|
|
||||||
const selectedTile = ref(null);
|
const selectedTile = ref(null);
|
||||||
|
|
||||||
const getTileEmoji = (tile) => {
|
const getTileEmoji = (tile) => {
|
||||||
|
|
@ -122,6 +150,7 @@ const handleActionClick = async (action) => {
|
||||||
} else {
|
} else {
|
||||||
villageData.value = response.data.value;
|
villageData.value = response.data.value;
|
||||||
selectedTile.value = null;
|
selectedTile.value = null;
|
||||||
|
await refreshEvents(); // Refresh the event log
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to perform action:', e);
|
console.error('Failed to perform action:', e);
|
||||||
|
|
@ -133,7 +162,7 @@ const handleActionClick = async (action) => {
|
||||||
|
|
||||||
const isSubmittingAdminAction = ref(false);
|
const isSubmittingAdminAction = ref(false);
|
||||||
|
|
||||||
async function handleAdminAction(url: string) {
|
async function handleAdminAction(url) {
|
||||||
if (isSubmittingAdminAction.value) return;
|
if (isSubmittingAdminAction.value) return;
|
||||||
isSubmittingAdminAction.value = true;
|
isSubmittingAdminAction.value = true;
|
||||||
|
|
||||||
|
|
@ -142,7 +171,8 @@ async function handleAdminAction(url: string) {
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
alert(error.value.data?.statusMessage || 'An admin action failed.');
|
alert(error.value.data?.statusMessage || 'An admin action failed.');
|
||||||
} else {
|
} else {
|
||||||
await refreshVillageData();
|
// Refresh both data sources in parallel
|
||||||
|
await Promise.all([refreshVillageData(), refreshEvents()]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to perform admin action:', e);
|
console.error('Failed to perform admin action:', e);
|
||||||
|
|
@ -355,4 +385,31 @@ const handleCompleteClearing = () => handleAdminAction('/api/admin/village/compl
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.event-log-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-log-container h4 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-log-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-log-table th, .event-log-table td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-log-table th {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "VillageEvent" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"villageId" INTEGER NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"message" TEXT NOT NULL,
|
||||||
|
"tileX" INTEGER,
|
||||||
|
"tileY" INTEGER,
|
||||||
|
"coins" INTEGER NOT NULL,
|
||||||
|
"exp" INTEGER NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "VillageEvent_villageId_fkey" FOREIGN KEY ("villageId") REFERENCES "Village" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
@ -113,6 +113,7 @@ model Village {
|
||||||
userId Int @unique // Each user has only one village
|
userId Int @unique // Each user has only one village
|
||||||
objects VillageObject[]
|
objects VillageObject[]
|
||||||
tiles VillageTile[]
|
tiles VillageTile[]
|
||||||
|
events VillageEvent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// VillageObject: An object (e.g., house, field) placed on a village tile.
|
// VillageObject: An object (e.g., house, field) placed on a village tile.
|
||||||
|
|
@ -148,3 +149,21 @@ model VillageTile {
|
||||||
|
|
||||||
@@unique([villageId, x, y])
|
@@unique([villageId, x, y])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model VillageEvent {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
villageId Int
|
||||||
|
|
||||||
|
type String
|
||||||
|
message String
|
||||||
|
|
||||||
|
tileX Int?
|
||||||
|
tileY Int?
|
||||||
|
|
||||||
|
coins Int
|
||||||
|
exp Int
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
village Village @relation(fields: [villageId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// server/api/admin/village/complete-clearing.post.ts
|
// server/api/admin/village/complete-clearing.post.ts
|
||||||
import { getUserIdFromSession } from '../../../utils/auth';
|
import { getUserIdFromSession } from '../../../utils/auth';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { REWARDS } from '../../../services/villageService';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
|
@ -25,27 +26,40 @@ export default defineEventHandler(async (event) => {
|
||||||
return { success: true, message: 'No clearing tasks to complete.' };
|
return { success: true, message: 'No clearing tasks to complete.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.$transaction([
|
const totalCoins = tilesToComplete.length * REWARDS.CLEARING.coins;
|
||||||
// Complete the tiles
|
const totalExp = tilesToComplete.length * REWARDS.CLEARING.exp;
|
||||||
prisma.villageTile.updateMany({
|
|
||||||
where: {
|
await prisma.$transaction(async (tx) => {
|
||||||
id: { in: tilesToComplete.map(t => t.id) },
|
// 1. Update user totals
|
||||||
},
|
await tx.user.update({
|
||||||
data: {
|
|
||||||
terrainState: 'IDLE',
|
|
||||||
terrainType: 'EMPTY',
|
|
||||||
clearingStartedAt: null,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Give the user the rewards (1 coin and 1 exp per tile)
|
|
||||||
prisma.user.update({
|
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
data: {
|
data: {
|
||||||
coins: { increment: tilesToComplete.length },
|
coins: { increment: totalCoins },
|
||||||
exp: { increment: tilesToComplete.length },
|
exp: { increment: totalExp },
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
]);
|
|
||||||
|
// 2. Update all the tiles
|
||||||
|
await tx.villageTile.updateMany({
|
||||||
|
where: { id: { in: tilesToComplete.map(t => t.id) } },
|
||||||
|
data: { terrainState: 'IDLE', terrainType: 'EMPTY', clearingStartedAt: null },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create an event for each completed tile
|
||||||
|
for (const tile of tilesToComplete) {
|
||||||
|
await tx.villageEvent.create({
|
||||||
|
data: {
|
||||||
|
villageId: village.id,
|
||||||
|
type: tile.terrainType === 'BLOCKED_TREE' ? 'CLEAR_TREE' : 'CLEAR_STONE',
|
||||||
|
message: `Finished clearing ${tile.terrainType === 'BLOCKED_TREE' ? 'a tree' : 'a stone'} at (${tile.x}, ${tile.y})`,
|
||||||
|
tileX: tile.x,
|
||||||
|
tileY: tile.y,
|
||||||
|
coins: REWARDS.CLEARING.coins,
|
||||||
|
exp: REWARDS.CLEARING.exp,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { success: true, message: `Completed ${tilesToComplete.length} clearing tasks.` };
|
return { success: true, message: `Completed ${tilesToComplete.length} clearing tasks.` };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
30
server/api/village/events.get.ts
Normal file
30
server/api/village/events.get.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// server/api/village/events.get.ts
|
||||||
|
import { getUserIdFromSession } from '../../utils/auth';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const userId = await getUserIdFromSession(event);
|
||||||
|
|
||||||
|
const village = await prisma.village.findUnique({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!village) {
|
||||||
|
// Or return empty array, depending on desired behavior for users without villages yet
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = await prisma.villageEvent.findMany({
|
||||||
|
where: {
|
||||||
|
villageId: village.id,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
take: 50, // Limit to the last 50 events to avoid overloading the client
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
});
|
||||||
|
|
@ -21,6 +21,10 @@ export const PRODUCING_BUILDINGS: string[] = [
|
||||||
'QUARRY',
|
'QUARRY',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const REWARDS = {
|
||||||
|
CLEARING: { coins: 1, exp: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
// Helper to get the start of a given date for daily EXP checks
|
// Helper to get the start of a given date for daily EXP checks
|
||||||
const getStartOfDay = (date: Date) => {
|
const getStartOfDay = (date: Date) => {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
|
|
@ -118,21 +122,40 @@ export async function getVillageState(userId: number): Promise<FullVillage> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (finishedClearingTiles.length > 0) {
|
if (finishedClearingTiles.length > 0) {
|
||||||
await prisma.$transaction([
|
const totalCoins = finishedClearingTiles.length * REWARDS.CLEARING.coins;
|
||||||
prisma.user.update({
|
const totalExp = finishedClearingTiles.length * REWARDS.CLEARING.exp;
|
||||||
|
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
// 1. Update user totals
|
||||||
|
await tx.user.update({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
data: {
|
data: {
|
||||||
coins: { increment: finishedClearingTiles.length },
|
coins: { increment: totalCoins },
|
||||||
exp: { increment: finishedClearingTiles.length },
|
exp: { increment: totalExp },
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
...finishedClearingTiles.map(t =>
|
|
||||||
prisma.villageTile.update({
|
// 2. Update all the tiles
|
||||||
where: { id: t.id },
|
await tx.villageTile.updateMany({
|
||||||
|
where: { id: { in: finishedClearingTiles.map(t => t.id) } },
|
||||||
data: { terrainType: 'EMPTY', terrainState: 'IDLE', clearingStartedAt: null },
|
data: { terrainType: 'EMPTY', terrainState: 'IDLE', clearingStartedAt: null },
|
||||||
})
|
});
|
||||||
),
|
|
||||||
]);
|
// 3. Create an event for each completed tile
|
||||||
|
for (const tile of finishedClearingTiles) {
|
||||||
|
await tx.villageEvent.create({
|
||||||
|
data: {
|
||||||
|
villageId: villageSnapshot.id,
|
||||||
|
type: tile.terrainType === 'BLOCKED_TREE' ? 'CLEAR_TREE' : 'CLEAR_STONE',
|
||||||
|
message: `Finished clearing ${tile.terrainType === 'BLOCKED_TREE' ? 'a tree' : 'a stone'} at (${tile.x}, ${tile.y})`,
|
||||||
|
tileX: tile.x,
|
||||||
|
tileY: tile.y,
|
||||||
|
coins: REWARDS.CLEARING.coins,
|
||||||
|
exp: REWARDS.CLEARING.exp,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Step 3: Refetch for next logic step ---
|
// --- Step 3: Refetch for next logic step ---
|
||||||
|
|
@ -378,23 +401,87 @@ export async function getVillageState(userId: number): Promise<FullVillage> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await tx.villageObject.create({
|
await tx.villageObject.create({
|
||||||
|
|
||||||
data: {
|
|
||||||
|
|
||||||
type: buildingType as keyof typeof VillageObjectType,
|
data: {
|
||||||
|
|
||||||
villageId: tile.villageId,
|
|
||||||
|
|
||||||
tileId: tileId,
|
type: buildingType as keyof typeof VillageObjectType,
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
villageId: tile.villageId,
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
tileId: tileId,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await tx.villageEvent.create({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data: {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
villageId: tile.villageId,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type: `BUILD_${buildingType}`,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message: `Built a ${buildingType} at (${tile.x}, ${tile.y})`,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tileX: tile.x,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tileY: tile.y,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
coins: -cost,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exp: 0,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user