правки по админ тулу
This commit is contained in:
parent
365d04b678
commit
a8241d93c6
|
|
@ -54,6 +54,29 @@
|
|||
<button @click="handleResetVillage" :disabled="isSubmittingAdminAction">Reset Village</button>
|
||||
<button @click="handleCompleteClearing" :disabled="isSubmittingAdminAction">Complete All Clearing</button>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
@ -65,6 +88,11 @@ const { data: villageData, pending, error, refresh: refreshVillageData } = await
|
|||
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 getTileEmoji = (tile) => {
|
||||
|
|
@ -122,6 +150,7 @@ const handleActionClick = async (action) => {
|
|||
} else {
|
||||
villageData.value = response.data.value;
|
||||
selectedTile.value = null;
|
||||
await refreshEvents(); // Refresh the event log
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to perform action:', e);
|
||||
|
|
@ -133,7 +162,7 @@ const handleActionClick = async (action) => {
|
|||
|
||||
const isSubmittingAdminAction = ref(false);
|
||||
|
||||
async function handleAdminAction(url: string) {
|
||||
async function handleAdminAction(url) {
|
||||
if (isSubmittingAdminAction.value) return;
|
||||
isSubmittingAdminAction.value = true;
|
||||
|
||||
|
|
@ -142,7 +171,8 @@ async function handleAdminAction(url: string) {
|
|||
if (error.value) {
|
||||
alert(error.value.data?.statusMessage || 'An admin action failed.');
|
||||
} else {
|
||||
await refreshVillageData();
|
||||
// Refresh both data sources in parallel
|
||||
await Promise.all([refreshVillageData(), refreshEvents()]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to perform admin action:', e);
|
||||
|
|
@ -355,4 +385,31 @@ const handleCompleteClearing = () => handleAdminAction('/api/admin/village/compl
|
|||
background-color: #e9ecef;
|
||||
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>
|
||||
|
|
@ -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
|
||||
objects VillageObject[]
|
||||
tiles VillageTile[]
|
||||
events VillageEvent[]
|
||||
}
|
||||
|
||||
// VillageObject: An object (e.g., house, field) placed on a village tile.
|
||||
|
|
@ -148,3 +149,21 @@ model VillageTile {
|
|||
|
||||
@@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
|
||||
import { getUserIdFromSession } from '../../../utils/auth';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { REWARDS } from '../../../services/villageService';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
|
@ -25,27 +26,40 @@ export default defineEventHandler(async (event) => {
|
|||
return { success: true, message: 'No clearing tasks to complete.' };
|
||||
}
|
||||
|
||||
await prisma.$transaction([
|
||||
// Complete the tiles
|
||||
prisma.villageTile.updateMany({
|
||||
where: {
|
||||
id: { in: tilesToComplete.map(t => t.id) },
|
||||
},
|
||||
data: {
|
||||
terrainState: 'IDLE',
|
||||
terrainType: 'EMPTY',
|
||||
clearingStartedAt: null,
|
||||
},
|
||||
}),
|
||||
// Give the user the rewards (1 coin and 1 exp per tile)
|
||||
prisma.user.update({
|
||||
const totalCoins = tilesToComplete.length * REWARDS.CLEARING.coins;
|
||||
const totalExp = tilesToComplete.length * REWARDS.CLEARING.exp;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// 1. Update user totals
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
coins: { increment: tilesToComplete.length },
|
||||
exp: { increment: tilesToComplete.length },
|
||||
coins: { increment: totalCoins },
|
||||
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.` };
|
||||
});
|
||||
|
|
|
|||
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',
|
||||
];
|
||||
|
||||
export const REWARDS = {
|
||||
CLEARING: { coins: 1, exp: 1 },
|
||||
};
|
||||
|
||||
// Helper to get the start of a given date for daily EXP checks
|
||||
const getStartOfDay = (date: Date) => {
|
||||
const d = new Date(date);
|
||||
|
|
@ -118,21 +122,40 @@ export async function getVillageState(userId: number): Promise<FullVillage> {
|
|||
);
|
||||
|
||||
if (finishedClearingTiles.length > 0) {
|
||||
await prisma.$transaction([
|
||||
prisma.user.update({
|
||||
const totalCoins = finishedClearingTiles.length * REWARDS.CLEARING.coins;
|
||||
const totalExp = finishedClearingTiles.length * REWARDS.CLEARING.exp;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// 1. Update user totals
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
coins: { increment: finishedClearingTiles.length },
|
||||
exp: { increment: finishedClearingTiles.length },
|
||||
coins: { increment: totalCoins },
|
||||
exp: { increment: totalExp },
|
||||
},
|
||||
}),
|
||||
...finishedClearingTiles.map(t =>
|
||||
prisma.villageTile.update({
|
||||
where: { id: t.id },
|
||||
});
|
||||
|
||||
// 2. Update all the tiles
|
||||
await tx.villageTile.updateMany({
|
||||
where: { id: { in: finishedClearingTiles.map(t => t.id) } },
|
||||
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 ---
|
||||
|
|
@ -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,
|
||||
|
||||
villageId: tile.villageId,
|
||||
data: {
|
||||
|
||||
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