feat: return json format of organization page with .json extension and

http content negotiation
chore(deps): add negotiator and @types/negotiator
refactor: put obfuscating logic in a twig template filter
This commit is contained in:
Matthieu Bessat 2022-09-02 19:21:24 +02:00
parent 5dcd4f4f6d
commit 2a56067362
6 changed files with 104 additions and 71 deletions

View file

@ -18,6 +18,7 @@
"@types/multer": "^1.4.3",
"@types/multer-s3": "^2.7.7",
"@types/mustache": "^4.0.1",
"@types/negotiator": "^0.6.1",
"@types/nodemailer": "^6.4.0",
"@types/papaparse": "^5.0.6",
"@types/sanitize-html": "^1.23.3",

View file

@ -23,4 +23,36 @@ export default class Utils {
}
return bool
}
}
public static obfuscate(original: string) {
if (original.length === 0) { return original }
let cipher: string[] = []
let data: string = Buffer.from(original, 'utf-8').toString('base64')
data = data.replace(/A/gm, '$')
data = data.replace(/c/gm, '£')
data = data.replace(/b/gm, 'ù')
data = data.replace(/d/gm, '#')
data = data.replace(/e/gm, 'µ')
data = data.replace(/z/gm, '§')
data = data.replace(/i/gm, 'à')
data = data.replace(/f/gm, '|')
let remain = data
let take: number = 0
while (remain.length != 0) {
take = remain.length
if (take > 5) { take = 5 }
take = Math.floor(Math.random() * (take + 1))
if (take === 0) { take = 2 }
if (take > remain.length) {
take = remain.length
}
cipher.push('%_' + remain.substr(0, take) + '%_')
remain = remain.substr(take)
}
return JSON.stringify(cipher).replace(/"/gm, "'")
}
}

View file

@ -89,6 +89,9 @@ let main = async () => {
console.log('> App: Connected to mongodb')
})
// add custom twig extensions (filters and functions)
twig.extendFilter('obfuscate', Utils.obfuscate)
app.set('twig options', {
allow_async: true,
strict_variables: false

View file

@ -12,6 +12,7 @@ import ErrorController from './ErrorController'
import Utils from '../Utils'
import mongoose from 'mongoose'
import MediaService from '../MediaService'
import Negotiator from 'negotiator'
export default class PublicController {
@ -72,7 +73,25 @@ export default class PublicController {
}
static async organization(req: express.Request, res: express.Response) {
Organization.find({ slugs: { '$in': req.params.slug } }).then(data => {
const rawSlug = req.params.slug
let format = 'html'
let slug = rawSlug
// allow json format by using 'file' extension
if (rawSlug.endsWith('.json')) {
slug = rawSlug.slice(0, -('.json'.length))
format = 'json'
}
// allow json format by Accept header
if (req.headers.accept) {
let neg = new Negotiator(req)
let negotiationResults = neg.mediaTypes(['text/html', 'application/json'])
if (negotiationResults.length > 0 && negotiationResults[0] === 'application/json') {
format = 'json'
}
}
Organization.find({ slugs: { '$in': slug } }).then(data => {
if (data.length === 0) {
return ErrorController.notFoundHandler()(req, res)
} else {
@ -80,6 +99,7 @@ export default class PublicController {
let version = org.get('publishedVersion')
let lastPublished = org.get('publishedAt')
let isProposed = false
if (req.query.version === 'proposed') {
isProposed = true
lastPublished = org.get('updatedAt')
@ -87,62 +107,23 @@ export default class PublicController {
} else if (lastPublished === undefined || lastPublished === null) {
return ErrorController.notFoundHandler()(req, res)
}
if (!Utils.isStrUsable(version, 'cover.location')) {
version.cover = { location: "https://fva-condorcet.s3.fr-par.scw.cloud/default-cover-plz-get-you-a-real-image.jpg" }
}
let offuscate = (original: string) => {
if (original.length === 0) { return original }
let cipher: string[] = []
let data: string = Buffer.from(original, 'utf-8').toString('base64')
data = data.replace(/A/gm, '$')
data = data.replace(/c/gm, '£')
data = data.replace(/b/gm, 'ù')
data = data.replace(/d/gm, '#')
data = data.replace(/e/gm, 'µ')
data = data.replace(/z/gm, '§')
data = data.replace(/i/gm, 'à')
data = data.replace(/f/gm, '|')
let remain = data
let take: number = 0
while (remain.length != 0) {
take = remain.length
if (take > 5) { take = 5 }
take = Math.floor(Math.random() * (take + 1))
if (take === 0) { take = 2 }
if (take > remain.length) {
take = remain.length
}
cipher.push('%_' + remain.substr(0, take) + '%_')
remain = remain.substr(take)
}
return JSON.stringify(cipher).replace(/"/gm, "'")
}
/**
* Parse contact
*/
if (Utils.isUsable(version.contacts)) {
if (Utils.isStrUsable(version.contacts, 'email')) {
version.contacts.email = offuscate(version.contacts.email)
}
if (Utils.isStrUsable(version.contacts, 'address')) {
version.contacts.address = version.contacts.address.replace('%postalsep%', ' ').split('\n')
}
if (Utils.isStrUsable(version.contacts, 'phone')) {
//let formated: any = formatPhone(version.contacts.phone)
version.contacts.phone = offuscate(version.contacts.phone)
}
if (Utils.isUsable(version.contacts, 'peoples') && Array.isArray(version.contacts.peoples)) {
version.contacts.peoples = version.contacts.peoples.map((p: any) => {
// let formated: any = formatPhone(p.phone)
// p.phoneInt = formated[0]
// p.phoneSplit = formated[1]
p.email = offuscate(p.email)
p.phone = offuscate(p.phone)
return p
})
}
@ -202,8 +183,6 @@ export default class PublicController {
media.isVideo = media.contentType.indexOf('video/') !== -1
return media
})
version.firstGallery = version.gallery.slice(0, 5)
version.secondGallery = version.gallery.slice(5)
}
/**
@ -217,7 +196,6 @@ export default class PublicController {
when: days.map((day: any) => {
return {
day,
wow: 'wow',
hours: s.when
.filter((w: any) => w.day === day)
.map((hour: any) => { return { from: hour.from, to: hour.to } })
@ -229,18 +207,6 @@ export default class PublicController {
})
}
if (version.schedule.length > 8) {
let isFirst = true;
version.scheduleParts = [[], []]
version.schedule.forEach((schedule: any) => {
version.scheduleParts[isFirst ? 0 : 1].push(schedule)
isFirst = !isFirst
})
} else {
version.scheduleParts = [version.schedule]
}
version.cutDescription = Utils.isStrUsable(version, 'descriptionLong') && version.descriptionLong.length > 800
// if (version.cutDescription) {
// version.descriptionFirstHalf = version.descriptionLong.substr(0, 200) // not gonna lie
@ -251,18 +217,44 @@ export default class PublicController {
// hour = '0' + hour
// }
/**
* Obfuscate emails and phone
*/
if (format == 'html') {
// Do some adjustements that are specifics to the html format
// TODO: Clarify this piece of code
// TODO: don't split up in two use directly the display grid or any other better way of doing this on the view side
if (version.schedule.length > 8) {
let isFirst = true;
version.scheduleParts = [[], []]
version.schedule.forEach((schedule: any) => {
version.scheduleParts[isFirst ? 0 : 1].push(schedule)
isFirst = !isFirst
})
} else {
version.scheduleParts = [version.schedule]
}
version.cutDescription = Utils.isStrUsable(version, 'descriptionLong') && version.descriptionLong.length > 800
// Split gallery in two parts to only show at max 5 media on the page
if (Array.isArray(version.gallery)) {
version.firstGallery = version.gallery.slice(0, 5)
version.secondGallery = version.gallery.slice(5)
}
res.render('organization.twig', {
layout: 'standalone',
data: version,
//lastPublished: lastPublished.toLocaleDateString('fr-FR') + ' ' + hour.substr(0, 5),
isProposed,
id: org.get('_id')
})
}
if (format == 'json') {
res.json({
...version
})
}
res.render('organization.twig', {
layout: 'standalone',
data: version,
//lastPublished: lastPublished.toLocaleDateString('fr-FR') + ' ' + hour.substr(0, 5),
isProposed,
id: org.get('_id')
})
}
}).catch(err => () => {
console.error(err)

View file

@ -256,7 +256,7 @@
href="mailto:data"
class="people-email-content offuscated"
data-source-type="email"
data-source="{{ data.contacts.email }}">
data-source="{{ data.contacts.email | obfuscate }}">
</a>
</div>
{% if data.contacts.phone is not empty %}
@ -268,7 +268,7 @@
href="telto:data"
class="people-email-content offuscated"
data-source-type="phone"
data-source="{{ data.contacts.phone }}">
data-source="{{ data.contacts.phone | obfuscate }}">
</a>
</div>
{% endif %}
@ -293,7 +293,7 @@
href="mailto:data"
class="people-email-content offuscated"
data-source-type="email"
data-source="{{ people.email }}">
data-source="{{ people.email | obfuscate }}">
</a>
</div>
{% if people.phone is not empty %}
@ -305,7 +305,7 @@
href="telto:data"
class="people-email-content offuscated"
data-source-type="phone"
data-source="{{ people.phone }}">
data-source="{{ people.phone | obfuscate }}">
</a>
</div>
{% endif %}

View file

@ -759,6 +759,11 @@
resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.0.1.tgz#e4d421ed2d06d463b120621774185a5cd1b92d77"
integrity sha512-wH6Tu9mbiOt0n5EvdoWy0VGQaJMHfLIxY/6wS0xLC7CV1taM6gESEzcYy0ZlWvxxiiljYvfDIvz4hHbUUDRlhw==
"@types/negotiator@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@types/negotiator/-/negotiator-0.6.1.tgz#4c75543f6ef87f427f4705e731a933595b7397f5"
integrity sha512-c4mvXFByghezQ/eVGN5HvH/jI63vm3B7FiE81BUzDAWmuiohRecCO6ddU60dfq29oKUMiQujsoB2h0JQC7JHKA==
"@types/node@*":
version "14.6.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499"