diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8d7547d..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee55cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Scripts locales de arranque (Windows), no se versionan +*.bat + +# Sistema operativo +.DS_Store +Thumbs.db + +# Dependencias y variables de entorno (cada subproyecto ya las ignora, +# se cubre también aquí por si se generan en la raíz) +node_modules/ +.env +.env.* +!.env.example + +# Editores +.vscode/ +.idea/ + +# Configuración local de Claude Code (no necesaria para producción) +.claude/ diff --git a/backend/src/bootstrapEasyFestPermissions.js b/backend/src/bootstrapEasyFestPermissions.js index 7ff6152..7ed54b0 100644 --- a/backend/src/bootstrapEasyFestPermissions.js +++ b/backend/src/bootstrapEasyFestPermissions.js @@ -1,306 +1,299 @@ +// Este bootstrap se ejecuta en cada arranque de Strapi (ver `src/index.js`), incluido +// cada despliegue. `roleService.updateRole` REEMPLAZA el árbol de permisos completo del +// rol al que se llama: cualquier permiso activado a mano desde el panel de admin que no +// esté aquí listado se desactiva en el siguiente arranque. Por eso este archivo debe ser +// la fuente de verdad de TODO lo que necesita el rol Public y el rol Authenticated: si +// falta algo aquí, "se borra solo" al desplegar. + +// Permisos de solo lectura que necesitan tanto Public como Authenticated (un usuario +// logueado sigue viendo el mismo contenido público de la web). +const readOnlyApiPermissions = { + 'api::about-us': { + controllers: { + 'about-us': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::accommodation-info': { + controllers: { + 'accommodation-info': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::agenda': { + controllers: { + agenda: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::code-of-conduct': { + controllers: { + 'code-of-conduct': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::contact-info': { + controllers: { + 'contact-info': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::contact-type': { + controllers: { + 'contact-type': { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::cookie-policy': { + controllers: { + 'cookie-policy': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::enrollment': { + controllers: { + enrollment: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::faq': { + controllers: { + faq: { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::homepage': { + controllers: { + homepage: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::news-item': { + controllers: { + 'news-item': { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::newsletter-subscription': { + controllers: { + 'newsletter-subscription': { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::org-team': { + controllers: { + 'org-team': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::press-kit': { + controllers: { + 'press-kit': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::privacy-policy': { + controllers: { + 'privacy-policy': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::profile-contact': { + controllers: { + 'profile-contact': { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::public-profile': { + controllers: { + 'public-profile': { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::registration-info': { + controllers: { + 'registration-info': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::seo': { + controllers: { + seo: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::setting': { + controllers: { + setting: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::simple-page': { + controllers: { + 'simple-page': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::site': { + controllers: { + site: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::speakers': { + controllers: { + speakers: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::sponsor': { + controllers: { + sponsor: { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::sponsors-info': { + controllers: { + 'sponsors-info': { + find: { enabled: true, policy: '' } + } + } + }, + 'api::streaming': { + controllers: { + streaming: { + find: { enabled: true, policy: '' } + } + } + }, + 'api::track': { + controllers: { + track: { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' } + } + } + }, + 'api::venue-info': { + controllers: { + 'venue-info': { + find: { enabled: true, policy: '' } + } + } + } +}; + +// `activity` necesita distinto alcance según el rol: cualquier visitante puede leer la +// agenda, pero solo un usuario logueado puede apuntarse/desapuntarse (update, usado por +// `/me` para conectar/desconectar `registered_users`/`queued_users`). +const activityPermissions = (withUpdate) => ({ + 'api::activity': { + controllers: { + activity: { + find: { enabled: true, policy: '' }, + findOne: { enabled: true, policy: '' }, + ...(withUpdate ? { update: { enabled: true, policy: '' } } : {}) + } + } + } +}); + +const publicOnlyPermissions = { + // Formulario de inscripción (create-only, sin lectura pública de inscripciones). + 'api::registration': { + controllers: { + registration: { + find: { enabled: false, policy: '' }, + findOne: { enabled: false, policy: '' }, + create: { enabled: true, policy: '' } + } + } + }, + // Login, "olvidé mi contraseña" y "restablecer contraseña" del plugin de usuarios: + // sin esto, esas pantallas devuelven 403 tras cada despliegue. + 'plugin::users-permissions': { + controllers: { + auth: { + callback: { enabled: true, policy: '' }, + forgotPassword: { enabled: true, policy: '' }, + resetPassword: { enabled: true, policy: '' } + } + } + }, + // Necesario para la búsqueda de ficheros subidos que usa la home (`/api/upload/files`). + 'plugin::upload': { + controllers: { + 'content-api': { + find: { enabled: true, policy: '' } + } + } + } +}; + +const authenticatedOnlyPermissions = { + // `/me` usa esta acción para leer los datos y actividades del usuario logueado. + 'plugin::users-permissions': { + controllers: { + user: { + me: { enabled: true, policy: '' } + } + } + } +}; + const grantEasyFestPublicApiPermissions = (strapi) => async () => { + const roleService = strapi.plugin('users-permissions').service('role'); + const publicRole = await strapi .query('plugin::users-permissions.role') .findOne({ where: { type: 'public' } }); - const publicRoleId = publicRole.id; + await roleService.updateRole(publicRole.id, { + permissions: { + ...readOnlyApiPermissions, + ...activityPermissions(false), + ...publicOnlyPermissions + } + }); - const roleService = strapi.plugin('users-permissions').service('role'); - await roleService.updateRole(publicRoleId, { + const authenticatedRole = await strapi + .query('plugin::users-permissions.role') + .findOne({ where: { type: 'authenticated' } }); + await roleService.updateRole(authenticatedRole.id, { permissions: { - 'api::about-us': { - controllers: { - 'about-us': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::accommodation-info': { - controllers: { - 'accommodation-info': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::activity': { - controllers: { - activity: { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::agenda': { - controllers: { - agenda: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::code-of-conduct': { - controllers: { - 'code-of-conduct': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::contact-info': { - controllers: { - 'contact-info': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::contact-type': { - controllers: { - 'contact-type': { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::cookie-policy': { - controllers: { - 'cookie-policy': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::faq': { - controllers: { - faq: { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::homepage': { - controllers: { - homepage: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::news-item': { - controllers: { - 'news-item': { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::newsletter-subscription': { - controllers: { - 'newsletter-subscription': { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::org-team': { - controllers: { - 'org-team': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::press-kit': { - controllers: { - 'press-kit': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::privacy-policy': { - controllers: { - 'privacy-policy': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::profile-contact': { - controllers: { - 'profile-contact': { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::public-profile': { - controllers: { - 'public-profile': { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::registration': { - controllers: { - registration: { - find: { enabled: false, policy: '' }, - findOne: { enabled: false, policy: '' }, - create: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::registration-info': { - controllers: { - 'registration-info': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::seo': { - controllers: { - seo: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::setting': { - controllers: { - setting: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::site': { - controllers: { - site: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' } - } - } - }, - 'api::speakers': { - controllers: { - speakers: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::sponsor': { - controllers: { - sponsor: { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::sponsors-info': { - controllers: { - 'sponsors-info': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::streaming': { - controllers: { - streaming: { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::track': { - controllers: { - track: { - find: { enabled: true, policy: '' }, - findOne: { enabled: true, policy: '' } - // create: { enabled: false, policy: '' }, - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - }, - 'api::venue-info': { - controllers: { - 'venue-info': { - find: { enabled: true, policy: '' } - // update: { enabled: false, policy: '' }, - // delete: { enabled: false, policy: '' }, - // createLocalization: { enabled: false, policy: '' } - } - } - } + ...readOnlyApiPermissions, + ...activityPermissions(true), + ...authenticatedOnlyPermissions } }); }; diff --git a/backend/src/index.js b/backend/src/index.js index 519e389..17ef69c 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,7 +1,6 @@ 'use strict'; const saveAndPublishEasyFestDefaultEntities = require('./bootstrapEasyFestDefaultEntities.js') -const grantEasyFestPublicApiPermissions = require('./bootstrapEasyFestPermissions.js'); module.exports = { /** @@ -22,6 +21,10 @@ module.exports = { async bootstrap({ strapi }) { await saveAndPublishEasyFestDefaultEntities(strapi)() - await grantEasyFestPublicApiPermissions(strapi)() + // Los permisos de Public/Authenticated ya NO se reescriben en cada arranque: + // lo que esté configurado en Strapi (Settings > Users & Permissions > Roles) + // se respeta tal cual, no se sobrescribe ni se borra en el despliegue. + // `bootstrapEasyFestPermissions.js` se deja en el repo sin usar por si hace + // falta consultarlo o reactivarlo más adelante. }, }; diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 391a1ae..741ed6c 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -56,7 +56,7 @@ "privacy_policy": "Política de privacidad", "register": "Registro", "registration": "Inscripción", - "reset_password_sent": "Si tu correo está registrado, hemos enviado un mensaje con un enlace para establecer una contraseña nueva.", + "reset_password_sent": "Si tu correo está registrado, se enviará un mensaje con un enlace para establecer una contraseña nueva.", "send": "Enviar", "speakers": "Invitadas", "sponsors": "EstelTienda", diff --git a/frontend/src/app.scss b/frontend/src/app.scss index da12dc6..26dfdfc 100644 --- a/frontend/src/app.scss +++ b/frontend/src/app.scss @@ -82,6 +82,52 @@ $control-border-width: 0.125rem; // 2px font-family: $font-family; } +// Controles de formulario (input/textarea/select): texto y placeholder con +// la tipografia secundaria (Lora) del cuerpo. Los navegadores no hacen que +// estos elementos hereden la fuente del body por defecto, así que hay que +// fijarlo explícitamente; se hace aquí una sola vez para toda la web en vez +// de repetirlo formulario a formulario. +.input, +.textarea, +.select select { + font-family: $font-family-secondary; +} + +.input::placeholder, +.textarea::placeholder { + font-family: $font-family-secondary; +} + +// Frases-enlace bajo los campos de un formulario (p.ej. "¿Olvidaste tu +// contraseña? Pulsa aquí."): tipografia secundaria, centradas como bloque y +// adaptadas al ancho del contenedor para que el texto pase a otra linea si +// no cabe en una sola, en vez del inline-flex sin ajustar de Bulma. +.button.is-text { + display: block; + width: 100%; + height: auto; + min-height: 2.5em; + white-space: normal; + text-align: center; + line-height: 1.3; + font-family: $font-family-secondary; +} + +// Botones: texto con la tipografia primaria (Cinzel) en vez de heredar la +// secundaria (Lora) del body. Se usa la custom property que expone Bulma +// (--bulma-family-primary) en vez de fijar el valor a fuego, siguiendo el +// mismo patron ya usado en varios botones sueltos del sitio. +.button { + font-family: var(--bulma-family-primary, #{$font-family-primary}); +} + +// Ayudas de formulario (.help): las de error de validacion (is-danger) +// mantienen el rojo de Bulma; el resto (mensajes informativos/de exito) +// usan el color secundario del sistema de diseno. +.help:not(.is-danger) { + color: $color-secondary; +} + .header--large { @include ec-typography(1.2, 3em, $font-family-secondary); @include mixins.tablet { diff --git a/frontend/src/lib/ActivityModal.svelte b/frontend/src/lib/ActivityModal.svelte index fead85b..cd1d8cf 100644 --- a/frontend/src/lib/ActivityModal.svelte +++ b/frontend/src/lib/ActivityModal.svelte @@ -1,17 +1,13 @@ @@ -32,30 +36,30 @@ {activityHour(activity)} · {activity.title}
- {#if activity.short_description || (activity.public_faces && activity.public_faces.length > 0)} + {#if activity.short_description || hasPublicFaces(activity)}{activity.short_description}
- {/if} - - {#each activity.public_faces as pf} - {#if activity.public_faces} - -{activity.short_description}
+ {/if} {#if activity.tag2}