This commit is contained in:
Pieter Vander Vennet 2024-05-24 15:04:34 +02:00
parent ad5b5128c0
commit b8bd13834e
2 changed files with 35 additions and 25 deletions

View file

@ -3,7 +3,6 @@ import { MangroveReviews, Review } from "mangrove-reviews-typescript"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { Feature, Position } from "geojson" import { Feature, Position } from "geojson"
import { GeoOperations } from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import ScriptUtils from "../../../scripts/ScriptUtils"
export class MangroveIdentity { export class MangroveIdentity {
private readonly keypair: UIEventSource<CryptoKeyPair> = new UIEventSource<CryptoKeyPair>( private readonly keypair: UIEventSource<CryptoKeyPair> = new UIEventSource<CryptoKeyPair>(
@ -28,7 +27,7 @@ export class MangroveIdentity {
} }
private async setKeypair(data: string) { private async setKeypair(data: string) {
console.log("Setting keypair from", data) console.debug("Setting keypair from", data)
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)) const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data))
this.keypair.setData(keypair) this.keypair.setData(keypair)
const pem = await MangroveReviews.publicToPem(keypair.publicKey) const pem = await MangroveReviews.publicToPem(keypair.publicKey)
@ -80,7 +79,6 @@ export class MangroveIdentity {
public getGeoReviews(): Store<(Review & { kid: string; signature: string })[] | undefined> { public getGeoReviews(): Store<(Review & { kid: string; signature: string })[] | undefined> {
if (!this.geoReviewsById) { if (!this.geoReviewsById) {
const all = this.getAllReviews()
this.geoReviewsById = this.getAllReviews().mapD((reviews) => this.geoReviewsById = this.getAllReviews().mapD((reviews) =>
reviews.filter((review) => { reviews.filter((review) => {
try { try {
@ -112,12 +110,12 @@ export class MangroveIdentity {
return [] return []
} }
const allReviews = await MangroveReviews.getReviews({ const allReviews = await MangroveReviews.getReviews({
kid: pem, kid: pem
}) })
this.allReviewsById.setData( this.allReviewsById.setData(
allReviews.reviews.map((r) => ({ allReviews.reviews.map((r) => ({
...r, ...r,
...r.payload, ...r.payload
})) }))
) )
}) })
@ -130,7 +128,9 @@ export class MangroveIdentity {
} }
/** /**
* Tracks all reviews of a given feature, allows to create a new review * Tracks all reviews of a given feature, allows to create a new review (and inserts this into the list)
*
* This object will start fetching the reviews as soon as it is constructed
*/ */
export default class FeatureReviews { export default class FeatureReviews {
/** /**
@ -169,7 +169,9 @@ export default class FeatureReviews {
this._testmode = testmode ?? new ImmutableStore(false) this._testmode = testmode ?? new ImmutableStore(false)
const nameKey = options?.nameKey ?? "name" const nameKey = options?.nameKey ?? "name"
if (feature.geometry.type === "Point") { if (options.uncertaintyRadius) {
this._uncertainty = options.uncertaintyRadius
} else if (feature.geometry.type === "Point") {
this._uncertainty = options?.uncertaintyRadius ?? 10 this._uncertainty = options?.uncertaintyRadius ?? 10
} else { } else {
let coordss: Position[][] let coordss: Position[][]
@ -180,6 +182,10 @@ export default class FeatureReviews {
feature.geometry.type === "Polygon" feature.geometry.type === "Polygon"
) { ) {
coordss = feature.geometry.coordinates coordss = feature.geometry.coordinates
}else if(feature.geometry.type === "MultiPolygon"){
coordss = feature.geometry.coordinates[0]
}else{
throw "Invalid feature type: "+feature.geometry.type
} }
let maxDistance = 0 let maxDistance = 0
for (const coords of coordss) { for (const coords of coordss) {
@ -191,16 +197,15 @@ export default class FeatureReviews {
} }
} }
this._uncertainty = options?.uncertaintyRadius ?? maxDistance this._uncertainty = maxDistance
} }
this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName) this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName)
this.subjectUri = this.ConstructSubjectUri() this.subjectUri = this.ConstructSubjectUri()
const self = this
this.subjectUri.addCallbackAndRunD(async (sub) => { this.subjectUri.addCallbackAndRunD(async (sub) => {
const reviews = await MangroveReviews.getReviews({ sub }) const reviews = await MangroveReviews.getReviews({ sub })
self.addReviews(reviews.reviews) this.addReviews(reviews.reviews)
}) })
/* We also construct all subject queries _without_ encoding the name to work around a previous bug /* We also construct all subject queries _without_ encoding the name to work around a previous bug
* See https://github.com/giggls/opencampsitemap/issues/30 * See https://github.com/giggls/opencampsitemap/issues/30
@ -208,7 +213,7 @@ export default class FeatureReviews {
this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => { this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => {
try { try {
const reviews = await MangroveReviews.getReviews({ sub }) const reviews = await MangroveReviews.getReviews({ sub })
self.addReviews(reviews.reviews) this.addReviews(reviews.reviews)
} catch (e) { } catch (e) {
console.log("Could not fetch reviews for partially incorrect query ", sub) console.log("Could not fetch reviews for partially incorrect query ", sub)
} }
@ -234,6 +239,11 @@ export default class FeatureReviews {
/** /**
* Construct a featureReviewsFor or fetches it from the cache * Construct a featureReviewsFor or fetches it from the cache
*
* @param feature The feature that we want reviews of. Various properties are used to link reviews to the feature, namely the centerpoint and size and optionally the name
* @param tagsSource Dynamic tags of the feature
* @param mangroveIdentity Identity with which new REviews will be mad
* @param options If options.nameKey is given, this key will be used as subject to fetch reviews
*/ */
public static construct( public static construct(
feature: Feature, feature: Feature,
@ -278,7 +288,7 @@ export default class FeatureReviews {
} }
const r: Review = { const r: Review = {
sub: this.subjectUri.data, sub: this.subjectUri.data,
...review, ...review
} }
const keypair: CryptoKeyPair = await this._identity.getKeypair() const keypair: CryptoKeyPair = await this._identity.getKeypair()
const jwt = await MangroveReviews.signReview(keypair, r) const jwt = await MangroveReviews.signReview(keypair, r)
@ -293,7 +303,7 @@ export default class FeatureReviews {
...r, ...r,
kid, kid,
signature: jwt, signature: jwt,
madeByLoggedInUser: new ImmutableStore(true), madeByLoggedInUser: new ImmutableStore(true)
} }
this._reviews.data.push(reviewWithKid) this._reviews.data.push(reviewWithKid)
this._reviews.ping() this._reviews.ping()
@ -306,8 +316,7 @@ export default class FeatureReviews {
* @private * @private
*/ */
private addReviews(reviews: { payload: Review; kid: string; signature: string }[]) { private addReviews(reviews: { payload: Review; kid: string; signature: string }[]) {
const self = this const alreadyKnown = new Set(this._reviews.data.map((r) => r.rating + " " + r.opinion))
const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion))
let hasNew = false let hasNew = false
for (const reviewData of reviews) { for (const reviewData of reviews) {
@ -335,20 +344,20 @@ export default class FeatureReviews {
if (alreadyKnown.has(key)) { if (alreadyKnown.has(key)) {
continue continue
} }
self._reviews.data.push({ this._reviews.data.push({
...review, ...review,
kid: reviewData.kid, kid: reviewData.kid,
signature: reviewData.signature, signature: reviewData.signature,
madeByLoggedInUser: this._identity.getKeyId().map((user_key_id) => { madeByLoggedInUser: this._identity.getKeyId().map((user_key_id) => {
return reviewData.kid === user_key_id return reviewData.kid === user_key_id
}), })
}) })
hasNew = true hasNew = true
} }
if (hasNew) { if (hasNew) {
self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first this._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first
self._reviews.ping() this._reviews.ping()
} }
} }
@ -356,16 +365,17 @@ export default class FeatureReviews {
* Gets an URI which represents the item in a mangrove-compatible way * Gets an URI which represents the item in a mangrove-compatible way
* *
* See https://mangrove.reviews/standard#mangrove-core-uri-schemes * See https://mangrove.reviews/standard#mangrove-core-uri-schemes
* @constructor
*/ */
private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> { private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> {
// https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2 // https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2
// `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3 // `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3
const self = this return this._name.map(name => {
return this._name.map(function (name) { let uri = `geo:${this._lat},${this._lon}?u=${Math.round(this._uncertainty)}`
let uri = `geo:${self._lat},${self._lon}?u=${Math.round(self._uncertainty)}`
if (name) { if (name) {
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)) uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))
}else if(this._uncertainty > 1000){
console.error("Not fetching reviews. Only got a point and a very big uncertainty range ("+this._uncertainty+"), so you'd probably only get garbage. Specify a name")
return undefined
} }
return uri return uri
}) })

View file

@ -651,13 +651,13 @@ console.log(">>> ",helpTexts.join("\n\n"))
}, },
{ {
funcName: "rating", funcName: "rating",
docs: "Shows stars which represent the avarage rating on mangrove.reviews", docs: "Shows stars which represent the average rating on mangrove.",
needsUrls: [MangroveReviews.ORIGINAL_API], needsUrls: [MangroveReviews.ORIGINAL_API],
args: [ args: [
{ {
name: "subjectKey", name: "subjectKey",
defaultValue: "name", defaultValue: "name",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>" doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews."
}, },
{ {
name: "fallback", name: "fallback",