fix(api,frontend): delete is the true inverse of adopt, no reseed
CI/CD Pipeline / test (push) Successful in 10s
CI/CD Pipeline / build-push-deploy (push) Successful in 9s

/creatures/:id/delete now just removes the row, matching how
POST /creatures (adopt) just inserts one — no side effects on anything
else. The auto-reseed was scope creep for a feature that only ever made
sense as a public marketing-site action; now that delete lives in the app
itself as a per-creature control, that safety net isn't needed.

Adds a Delete button next to Feed/Play/Sleep/Revive on every creature
card, same creatureAction() plumbing as the existing actions.
This commit is contained in:
khalil
2026-08-13 21:17:15 +00:00
parent e3850c93b4
commit 787a094c59
3 changed files with 19 additions and 20 deletions
+6 -20
View File
@@ -368,14 +368,11 @@ app.post('/api/creatures/:id/kill', async (req, res) => {
} }
}); });
// POST /creatures/:id/delete — Permanently delete a creature (Chaos Button // POST /creatures/:id/delete — Permanently delete a creature. The exact
// demo). Unlike /kill, this never touches tamagotchi_creatures_dead_total — // inverse of POST /creatures (adopt): adopt inserts a row with no side
// the row is gone, not marked dead — so it does NOT trigger the Prometheus // effects on anything else, delete removes one the same way — no reseed,
// alert or n8n. That's intentional: it demonstrates a different resilience // no gauge update. It never touches tamagotchi_creatures_dead_total, so it
// layer, the app defending its own roster instead of relying on external // does not trigger the Prometheus alert or n8n; deleting is not dying.
// monitoring automation. A public, unauthenticated action that only ever
// shrinks the roster would eventually empty it, so a replacement hatches
// immediately.
app.post('/api/creatures/:id/delete', async (req, res) => { app.post('/api/creatures/:id/delete', async (req, res) => {
try { try {
const { rows } = await pool.query('SELECT * FROM creatures WHERE id = $1', [req.params.id]); const { rows } = await pool.query('SELECT * FROM creatures WHERE id = $1', [req.params.id]);
@@ -385,18 +382,7 @@ app.post('/api/creatures/:id/delete', async (req, res) => {
await pool.query('DELETE FROM creatures WHERE id = $1', [req.params.id]); await pool.query('DELETE FROM creatures WHERE id = $1', [req.params.id]);
const seedNames = ['Pixel', 'Nimbus', 'Sprocket', 'Blossom', 'Byte', 'Echo', 'Flux', 'Glitch']; res.json({ message: `${deletedName} has been deleted permanently 🗑️` });
const allowedTypes = ['dragon', 'cat', 'robot', 'plant', 'alien'];
const newId = uuidv4();
const newName = seedNames[Math.floor(Math.random() * seedNames.length)];
const newType = allowedTypes[Math.floor(Math.random() * allowedTypes.length)];
await pool.query('INSERT INTO creatures (id, name, type) VALUES ($1, $2, $3)', [newId, newName, newType]);
const result = await pool.query('SELECT * FROM creatures WHERE id = $1', [newId]);
res.json({
message: `${deletedName} was deleted permanently. ${newName} the ${newType} hatched to take its place 🥚`,
creature: result.rows[0]
});
} catch (err) { } catch (err) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
+5
View File
@@ -29,6 +29,8 @@ const i18n = {
play: '🎮 Play', play: '🎮 Play',
sleep: '💤 Sleep', sleep: '💤 Sleep',
revive: '✨ Revive', revive: '✨ Revive',
delete: '🗑️',
delete_title: 'Delete permanently — the opposite of Adopt',
alive: 'Alive', alive: 'Alive',
dead: 'Dead', dead: 'Dead',
empty_title: 'No creatures yet!', empty_title: 'No creatures yet!',
@@ -47,6 +49,8 @@ const i18n = {
play: '🎮 Jouer', play: '🎮 Jouer',
sleep: '💤 Dormir', sleep: '💤 Dormir',
revive: '✨ Ressusciter', revive: '✨ Ressusciter',
delete: '🗑️',
delete_title: "Supprimer definitivement — l'oppose d'Adopter",
alive: 'Vivant', alive: 'Vivant',
dead: 'Mort', dead: 'Mort',
empty_title: 'Pas encore de créatures !', empty_title: 'Pas encore de créatures !',
@@ -226,6 +230,7 @@ function renderCreatures() {
` : ` ` : `
<button class="action-btn revive" onclick="creatureAction('${c.id}', 'revive')">${t('revive')}</button> <button class="action-btn revive" onclick="creatureAction('${c.id}', 'revive')">${t('revive')}</button>
`} `}
<button class="action-btn delete" title="${t('delete_title')}" onclick="creatureAction('${c.id}', 'delete')">${t('delete')}</button>
</div> </div>
</div> </div>
`; `;
+8
View File
@@ -295,6 +295,14 @@ body::before {
color: var(--accent-green); color: var(--accent-green);
} }
.action-btn.delete {
background: rgba(255, 68, 102, 0.1);
border-color: rgba(255, 68, 102, 0.2);
color: var(--accent-red);
flex: 0 0 auto;
width: 44px;
}
/* ---- FAB ---- */ /* ---- FAB ---- */
.fab { .fab {
position: fixed; position: fixed;