mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
use id in places url (ics and rss feeds, place page) with a reasonable backward compatible fallback
This commit is contained in:
parent
0899e71684
commit
8f9bc7e9de
12 changed files with 107 additions and 17 deletions
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<div class='p-location' itemprop="location" itemscope itemtype="https://schema.org/Place">
|
<div class='p-location' itemprop="location" itemscope itemtype="https://schema.org/Place">
|
||||||
<nuxt-link class='place d-block pl-0' text
|
<nuxt-link class='place d-block pl-0' text
|
||||||
:to='`/place/${encodeURIComponent(event.place.name)}`'>
|
:to='`/place/${event.place.id}/${encodeURIComponent(event.place.name)}`'>
|
||||||
<v-icon v-text='mdiMapMarker'></v-icon>
|
<v-icon v-text='mdiMapMarker'></v-icon>
|
||||||
<span itemprop='name'>{{ event.place.name }}</span>
|
<span itemprop='name'>{{ event.place.name }}</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
|
@ -7,7 +7,7 @@ v-card
|
||||||
v-row.my-4.d-flex.flex-column.align-center.text-center
|
v-row.my-4.d-flex.flex-column.align-center.text-center
|
||||||
.text-h6
|
.text-h6
|
||||||
v-icon(v-text='mdiMapMarker' )
|
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}`")
|
.mx-2(v-text="`${place.address}`")
|
||||||
v-card-actions.py-4
|
v-card-actions.py-4
|
||||||
HowToArriveNav.pl-1(:place='place')
|
HowToArriveNav.pl-1(:place='place')
|
||||||
|
|
|
@ -84,7 +84,7 @@ v-container
|
||||||
template(v-slot:item.actions='{ item }')
|
template(v-slot:item.actions='{ item }')
|
||||||
v-btn(@click='editPlace(item)' color='primary' icon)
|
v-btn(@click='editPlace(item)' color='primary' icon)
|
||||||
v-icon(v-text='mdiPencil')
|
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')
|
v-icon(v-text='mdiEye')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||||
v-icon(v-text='mdiMapMarker' small)
|
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}}
|
span(itemprop='name') {{event?.place?.name}}
|
||||||
.font-weight-light.p-street-address(v-if='event?.place?.name !=="online"' itemprop='address') {{event?.place?.address}}
|
.font-weight-light.p-street-address(v-if='event?.place?.name !=="online"' itemprop='address') {{event?.place?.address}}
|
||||||
|
|
||||||
|
|
65
pages/place/_id/_place.vue
Normal file
65
pages/place/_id/_place.vue
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<v-container id='home' class='px-2 px-sm-6 pt-0'>
|
||||||
|
<h1 class='d-block text-h4 font-weight-black text-center text-uppercase mt-10 mx-auto w-100 text-underline'>
|
||||||
|
<u>{{ place.name }}</u>
|
||||||
|
</h1>
|
||||||
|
<span v-if='place.name!=="online"' class="d-block text-subtitle text-center w-100">{{ place.address }}</span>
|
||||||
|
|
||||||
|
<!-- Map -->
|
||||||
|
<div v-if='settings.allow_geolocation && place.latitude && place.longitude' >
|
||||||
|
<div class="mt-4 mx-auto px-4" >
|
||||||
|
<Map :place='place' :height='mapHeight' />
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<HowToArriveNav :place='place' class="justify-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Events -->
|
||||||
|
<div id="events" class='mt-14'>
|
||||||
|
<v-lazy class='event v-card' :value='idx<9' v-for='(event, idx) in events' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }" :class="{ 'theme--dark': is_dark }">
|
||||||
|
<Event :event='event' :lazy='idx > 9' />
|
||||||
|
</v-lazy>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { mapState, mapGetters } from 'vuex'
|
||||||
|
import Event from '@/components/Event'
|
||||||
|
import HowToArriveNav from '@/components/HowToArriveNav.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Place',
|
||||||
|
components: {
|
||||||
|
Event,
|
||||||
|
HowToArriveNav,
|
||||||
|
[process.client && 'Map']: () => import('@/components/Map.vue')
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { mapHeight: "14rem" }
|
||||||
|
},
|
||||||
|
head() {
|
||||||
|
const title = `${this.settings.title} - ${this.place.name}`
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
link: [
|
||||||
|
{ 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}` }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
|
...mapGetters(['hide_thumbs', 'is_dark']),
|
||||||
|
},
|
||||||
|
async asyncData({ $axios, params, error }) {
|
||||||
|
try {
|
||||||
|
const { events, place } = await $axios.$get(`/place/${params.id}`)
|
||||||
|
return { place, events }
|
||||||
|
} catch (e) {
|
||||||
|
error({ statusCode: 404, message: 'Place not found!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -44,8 +44,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
link: [
|
link: [
|
||||||
{ rel: 'alternate', type: 'application/rss+xml', title, href: this.settings.baseurl + `/feed/rss/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.name}` }
|
{ rel: 'alternate', type: 'text/calendar', title, href: this.settings.baseurl + `/feed/ics/place/${this.place.id}` }
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,22 +9,22 @@ const { Op, where, col, fn, cast } = require('sequelize')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
async getEvents (req, res) {
|
async getEvents (req, res) {
|
||||||
const placeName = req.params.placeName
|
const placeNameOrId = req.params.placeNameOrId
|
||||||
const place = await Place.findOne({ where: { name: placeName }})
|
const place = await Place.findOne({ where: { [Op.or]: { id: placeNameOrId, name: placeNameOrId }}})
|
||||||
if (!place) {
|
if (!place) {
|
||||||
log.warn(`Place ${placeName} not found`)
|
log.warn(`Place ${placeNameOrId} not found`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const format = req.params.format || 'json'
|
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 })
|
const events = await eventController._select({ places: String(place.id), show_recurrent: true, show_multidate: true })
|
||||||
|
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'rss':
|
case 'rss':
|
||||||
return exportController.feed(req, res, events,
|
return exportController.feed(req, res, events,
|
||||||
`${res.locals.settings.title} - Place @${place.name}`,
|
`${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':
|
case 'ics':
|
||||||
return exportController.ics(req, res, events)
|
return exportController.ics(req, res, events)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -190,7 +190,7 @@ module.exports = () => {
|
||||||
|
|
||||||
// - PLACES
|
// - PLACES
|
||||||
api.get('/places', isAdmin, placeController.getAll)
|
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.get('/place', cors, placeController.search)
|
||||||
api.put('/place', isAdmin, placeController.updatePlace)
|
api.put('/place', isAdmin, placeController.updatePlace)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ module.exports = (sequelize, DataTypes) => {
|
||||||
const Place = sequelize.define('place', {
|
const Place = sequelize.define('place', {
|
||||||
name: {
|
name: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
unique: true,
|
|
||||||
index: true,
|
index: true,
|
||||||
allowNull: false
|
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
|
* @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 () {
|
Place.prototype.toAP = function () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -288,7 +288,9 @@ const Helpers = {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (place) {
|
||||||
await event.setPlace(place)
|
await event.setPlace(place)
|
||||||
|
}
|
||||||
|
|
||||||
// create/assign tags
|
// create/assign tags
|
||||||
let tags = []
|
let tags = []
|
||||||
|
|
23
server/migrations/20250109092712-removeNameUniqueness.js
Normal file
23
server/migrations/20250109092712-removeNameUniqueness.js
Normal file
|
@ -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 })
|
||||||
|
}
|
||||||
|
};
|
|
@ -42,7 +42,7 @@ async function main () {
|
||||||
// rss / ics feed
|
// rss / ics feed
|
||||||
app.use(helpers.feedRedirect)
|
app.use(helpers.feedRedirect)
|
||||||
app.get('/feed/:format/tag/:tag', cors(), tagController.getEvents)
|
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/collection/:name', cors(), collectionController.getEvents)
|
||||||
app.get('/feed/:format', cors(), exportController.export)
|
app.get('/feed/:format', cors(), exportController.export)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue