From 8f9bc7e9decd158273c5f0f1e64f854179eb5114 Mon Sep 17 00:00:00 2001 From: lesion Date: Fri, 10 Jan 2025 18:37:22 +0100 Subject: [PATCH] use id in places url (ics and rss feeds, place page) with a reasonable backward compatible fallback --- components/Event.vue | 2 +- components/EventMapDialog.vue | 2 +- components/admin/Places.vue | 2 +- pages/event/_slug.vue | 2 +- pages/place/_id/_place.vue | 65 +++++++++++++++++++ pages/place/_place.vue | 4 +- server/api/controller/place.js | 10 +-- server/api/index.js | 2 +- server/api/models/place.js | 6 +- server/federation/helpers.js | 4 +- .../20250109092712-removeNameUniqueness.js | 23 +++++++ server/routes.js | 2 +- 12 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 pages/place/_id/_place.vue create mode 100644 server/migrations/20250109092712-removeNameUniqueness.js diff --git a/components/Event.vue b/components/Event.vue index dd15ce83..f080b4de 100644 --- a/components/Event.vue +++ b/components/Event.vue @@ -20,7 +20,7 @@
+ :to='`/place/${event.place.id}/${encodeURIComponent(event.place.name)}`'> {{ event.place.name }} diff --git a/components/EventMapDialog.vue b/components/EventMapDialog.vue index 4bae03b6..ebe4d23d 100644 --- a/components/EventMapDialog.vue +++ b/components/EventMapDialog.vue @@ -7,7 +7,7 @@ v-card v-row.my-4.d-flex.flex-column.align-center.text-center .text-h6 v-icon(v-text='mdiMapMarker' ) - nuxt-link.ml-2.text-decoration-none(v-text="place.name" :to='`/place/${place.name}`') + nuxt-link.ml-2.text-decoration-none(v-text="place.name" :to='`/place/${place.id}/${encodeURIComponent(place.name)}`') .mx-2(v-text="`${place.address}`") v-card-actions.py-4 HowToArriveNav.pl-1(:place='place') diff --git a/components/admin/Places.vue b/components/admin/Places.vue index 94172cd4..5a939ecb 100644 --- a/components/admin/Places.vue +++ b/components/admin/Places.vue @@ -84,7 +84,7 @@ v-container template(v-slot:item.actions='{ item }') v-btn(@click='editPlace(item)' color='primary' icon) v-icon(v-text='mdiPencil') - nuxt-link(:to='`/place/${item.name}`') + nuxt-link(:to='`/place/${item.id}/${encodeURIComponent(item.name)}`') v-icon(v-text='mdiEye') diff --git a/pages/event/_slug.vue b/pages/event/_slug.vue index ba5461a8..8bc13ae8 100644 --- a/pages/event/_slug.vue +++ b/pages/event/_slug.vue @@ -24,7 +24,7 @@ .p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place") v-icon(v-text='mdiMapMarker' small) - nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-uppercase(:to='`/place/${encodeURIComponent(event?.place?.name)}`') + nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-uppercase(:to='`/place/${event?.place?.id}/${encodeURIComponent(event?.place?.name)}`') span(itemprop='name') {{event?.place?.name}} .font-weight-light.p-street-address(v-if='event?.place?.name !=="online"' itemprop='address') {{event?.place?.address}} diff --git a/pages/place/_id/_place.vue b/pages/place/_id/_place.vue new file mode 100644 index 00000000..4bd83f68 --- /dev/null +++ b/pages/place/_id/_place.vue @@ -0,0 +1,65 @@ + + diff --git a/pages/place/_place.vue b/pages/place/_place.vue index 8049b2ec..f3108932 100644 --- a/pages/place/_place.vue +++ b/pages/place/_place.vue @@ -44,8 +44,8 @@ export default { return { title, link: [ - { rel: 'alternate', type: 'application/rss+xml', title, href: this.settings.baseurl + `/feed/rss/place/${this.place.name}` }, - { rel: 'alternate', type: 'text/calendar', title, href: this.settings.baseurl + `/feed/ics/place/${this.place.name}` } + { rel: 'alternate', type: 'application/rss+xml', title, href: this.settings.baseurl + `/feed/rss/place/${this.place.id}` }, + { rel: 'alternate', type: 'text/calendar', title, href: this.settings.baseurl + `/feed/ics/place/${this.place.id}` } ], } }, diff --git a/server/api/controller/place.js b/server/api/controller/place.js index 5b88b572..b705dd0b 100644 --- a/server/api/controller/place.js +++ b/server/api/controller/place.js @@ -9,22 +9,22 @@ const { Op, where, col, fn, cast } = require('sequelize') module.exports = { async getEvents (req, res) { - const placeName = req.params.placeName - const place = await Place.findOne({ where: { name: placeName }}) + const placeNameOrId = req.params.placeNameOrId + const place = await Place.findOne({ where: { [Op.or]: { id: placeNameOrId, name: placeNameOrId }}}) if (!place) { - log.warn(`Place ${placeName} not found`) + log.warn(`Place ${placeNameOrId} not found`) return res.sendStatus(404) } const format = req.params.format || 'json' - log.debug(`Events for place: ${placeName}`) + log.debug(`Events for place: ${place.name}`) const events = await eventController._select({ places: String(place.id), show_recurrent: true, show_multidate: true }) switch (format) { case 'rss': return exportController.feed(req, res, events, `${res.locals.settings.title} - Place @${place.name}`, - `${res.locals.settings.baseurl}/feed/rss/place/${place.name}`) + `${res.locals.settings.baseurl}/feed/rss/place/${place.id}`) case 'ics': return exportController.ics(req, res, events) default: diff --git a/server/api/index.js b/server/api/index.js index 5c7cc2d5..09748fa2 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -190,7 +190,7 @@ module.exports = () => { // - PLACES api.get('/places', isAdmin, placeController.getAll) - api.get('/place/:placeName', cors, placeController.getEvents) + api.get('/place/:placeNameOrId', cors, placeController.getEvents) api.get('/place', cors, placeController.search) api.put('/place', isAdmin, placeController.updatePlace) diff --git a/server/api/models/place.js b/server/api/models/place.js index 0bd8e6b9..1ce669e9 100644 --- a/server/api/models/place.js +++ b/server/api/models/place.js @@ -4,7 +4,6 @@ module.exports = (sequelize, DataTypes) => { const Place = sequelize.define('place', { name: { type: DataTypes.STRING, - unique: true, index: true, allowNull: false }, @@ -18,9 +17,10 @@ module.exports = (sequelize, DataTypes) => { }) /** - * @description WIP -> https://codeberg.org/fediverse/fep/src/commit/4a75a1bc50bc6d19fc1e6112f02c52621bc178fe/fep/8a8e/fep-8a8e.md#location - * @todo support PostalAddress type * @returns ActivityStream location representation + * @link https://www.w3.org/TR/activitystreams-vocabulary/#places + * @todo support PostalAddress type + * @link WIP -> https://codeberg.org/fediverse/fep/src/commit/4a75a1bc50bc6d19fc1e6112f02c52621bc178fe/fep/8a8e/fep-8a8e.md#location */ Place.prototype.toAP = function () { return { diff --git a/server/federation/helpers.js b/server/federation/helpers.js index baa14079..3ffcaac9 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -288,7 +288,9 @@ const Helpers = { return false }) - await event.setPlace(place) + if (place) { + await event.setPlace(place) + } // create/assign tags let tags = [] diff --git a/server/migrations/20250109092712-removeNameUniqueness.js b/server/migrations/20250109092712-removeNameUniqueness.js new file mode 100644 index 00000000..f852d418 --- /dev/null +++ b/server/migrations/20250109092712-removeNameUniqueness.js @@ -0,0 +1,23 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up (queryInterface, Sequelize) { + // needed as in sequelize there is no support for alter table and sequelize simulate this by creating a backup table and dropping the old one: + // this will cause a foreign key error + const dialect = queryInterface.sequelize.getDialect() + if (dialect === 'sqlite') { + await queryInterface.sequelize.query('PRAGMA foreign_keys = OFF') + } + + await queryInterface.changeColumn('places', 'name', { type: Sequelize.STRING, index: true, allowNull: false, unique: false }) + }, + + async down (queryInterface, Sequelize) { + const dialect = queryInterface.sequelize.getDialect() + if (dialect === 'sqlite') { + await queryInterface.sequelize.query('PRAGMA foreign_keys = OFF') + } + await queryInterface.changeColumn('places', 'name', { type: Sequelize.STRING, index: true, allowNull: false, unique: true }) + } +}; diff --git a/server/routes.js b/server/routes.js index 62399f31..e91cab33 100644 --- a/server/routes.js +++ b/server/routes.js @@ -42,7 +42,7 @@ async function main () { // rss / ics feed app.use(helpers.feedRedirect) app.get('/feed/:format/tag/:tag', cors(), tagController.getEvents) - app.get('/feed/:format/place/:placeName', cors(), placeController.getEvents) + app.get('/feed/:format/place/:placeNameOrId', cors(), placeController.getEvents) app.get('/feed/:format/collection/:name', cors(), collectionController.getEvents) app.get('/feed/:format', cors(), exportController.export)