feat: full locations CRUD + legal policies in settings

Backend:
- CRUD completo para countries, regions y cities
- GET /settings/policy/:key — página HTML pública para políticas
- GET/PATCH /settings/policies/:key — admin endpoints protegidos

Admin:
- /locations: árbol interactivo País → Región → Ciudad con add/edit/delete
- /settings: editor de Política de Privacidad y Términos con enlace público copiable
- Sidebar: Ciudades → Ubicaciones

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-27 07:45:53 -05:00
co-authored by Claude Sonnet 4.6
parent 5c740420e9
commit 39c4301d1b
9 changed files with 604 additions and 196 deletions
+25 -1
View File
@@ -1,6 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
const POLICY_KEYS = ['privacy', 'terms'] as const;
type PolicyKey = typeof POLICY_KEYS[number];
@Injectable()
export class SettingsService {
constructor(private prisma: PrismaService) {}
@@ -17,4 +20,25 @@ export class SettingsService {
update: { value },
});
}
async getPolicy(key: string): Promise<string | null> {
const setting = await this.prisma.settings.findUnique({ where: { key: `policy_${key}` } });
return (setting?.value as any)?.content ?? null;
}
async updatePolicy(key: PolicyKey, content: string) {
return this.prisma.settings.upsert({
where: { key: `policy_${key}` },
create: { key: `policy_${key}`, value: { content } },
update: { value: { content } },
});
}
async getPolicies() {
const [privacy, terms] = await Promise.all([
this.getPolicy('privacy'),
this.getPolicy('terms'),
]);
return { privacy, terms };
}
}