Support description for location points.
This commit is contained in:
parent
eb9e6abd22
commit
53a9a2afeb
12 changed files with 275 additions and 28 deletions
|
@ -84,6 +84,10 @@ ADMIN_EMAIL=admin@example.com
|
||||||
# Comment or leave empty if the postal code is not available.
|
# Comment or leave empty if the postal code is not available.
|
||||||
# DB_LOCATION__POSTAL_CODE=postalCode
|
# DB_LOCATION__POSTAL_CODE=postalCode
|
||||||
|
|
||||||
|
# The name of the column that contains the description of each location point
|
||||||
|
# Comment or leave empty if the description is not available.
|
||||||
|
# DB_LOCATION__DESCRIPTION=description
|
||||||
|
|
||||||
###
|
###
|
||||||
### Frontend configuration.
|
### Frontend configuration.
|
||||||
### This is only required if you want to run the frontend in development mode
|
### This is only required if you want to run the frontend in development mode
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
<PointInfo :point="selectedPoint"
|
<PointInfo :point="selectedPoint"
|
||||||
ref="popup"
|
ref="popup"
|
||||||
@remove="onRemove"
|
@remove="onRemove"
|
||||||
@close="selectedPoint = null" />
|
@edit="editPoint"
|
||||||
|
@close="clearSelectedPoint" />
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="form-container" v-if="showControls">
|
<div class="form-container" v-if="showControls">
|
||||||
|
@ -135,6 +136,7 @@ export default {
|
||||||
routesLayer: null as Optional<VectorLayer>,
|
routesLayer: null as Optional<VectorLayer>,
|
||||||
selectedFeature: null as Optional<Feature>,
|
selectedFeature: null as Optional<Feature>,
|
||||||
selectedPoint: null as Optional<GPSPoint>,
|
selectedPoint: null as Optional<GPSPoint>,
|
||||||
|
selectedPointIndex: null as Optional<number>,
|
||||||
showControls: false,
|
showControls: false,
|
||||||
showMetrics: new TimelineMetricsConfiguration(),
|
showMetrics: new TimelineMetricsConfiguration(),
|
||||||
}
|
}
|
||||||
|
@ -206,11 +208,20 @@ export default {
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.pointToRemove = null
|
this.pointToRemove = null
|
||||||
this.selectedPoint = null
|
this.clearSelectedPoint()
|
||||||
this.selectedFeature = null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async editPoint(value: GPSPoint) {
|
||||||
|
const index = this.selectedPointIndex
|
||||||
|
if (index === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updatePoints([value])
|
||||||
|
this.gpsPoints[index] = value
|
||||||
|
},
|
||||||
|
|
||||||
onRemove(point: GPSPoint) {
|
onRemove(point: GPSPoint) {
|
||||||
this.pointToRemove = point
|
this.pointToRemove = point
|
||||||
},
|
},
|
||||||
|
@ -273,9 +284,14 @@ export default {
|
||||||
|
|
||||||
if (feature) {
|
if (feature) {
|
||||||
this.selectedFeature = feature as Feature
|
this.selectedFeature = feature as Feature
|
||||||
const point = this.gpsPoints.find((gps: GPSPoint) => {
|
const point = this.gpsPoints.find((gps: GPSPoint, index: number) => {
|
||||||
const [longitude, latitude] = (feature.getGeometry() as any).getCoordinates()
|
const [longitude, latitude] = (feature.getGeometry() as any).getCoordinates()
|
||||||
return gps.longitude === longitude && gps.latitude === latitude
|
if (gps.longitude === longitude && gps.latitude === latitude) {
|
||||||
|
this.selectedPointIndex = index
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if (point) {
|
if (point) {
|
||||||
|
@ -286,12 +302,17 @@ export default {
|
||||||
map.getView().setCenter(event.coordinate)
|
map.getView().setCenter(event.coordinate)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectedPoint = null
|
this.clearSelectedPoint()
|
||||||
this.selectedFeature = null
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearSelectedPoint() {
|
||||||
|
this.selectedPoint = null
|
||||||
|
this.selectedPointIndex = null
|
||||||
|
this.selectedFeature = null
|
||||||
|
},
|
||||||
|
|
||||||
refreshShowMetricsFromURL() {
|
refreshShowMetricsFromURL() {
|
||||||
this.showMetrics = new TimelineMetricsConfiguration(this.parseQuery(window.location.href))
|
this.showMetrics = new TimelineMetricsConfiguration(this.parseQuery(window.location.href))
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,34 @@
|
||||||
<font-awesome-icon icon="fas fa-mountain" />
|
<font-awesome-icon icon="fas fa-mountain" />
|
||||||
{{ Math.round(point.altitude) }} m
|
{{ Math.round(point.altitude) }} m
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<form class="description editor" @submit.prevent="editPoint" v-if="editDescription">
|
||||||
|
<div class="row">
|
||||||
|
<textarea
|
||||||
|
:value="point.description"
|
||||||
|
@keydown.enter="editPoint"
|
||||||
|
@blur="onDescriptionBlur"
|
||||||
|
ref="description"
|
||||||
|
placeholder="Enter a description" />
|
||||||
|
|
||||||
|
<button type="submit" title="Save">
|
||||||
|
<font-awesome-icon icon="fas fa-save" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="description"
|
||||||
|
:class="{ 'no-content': !point.description?.length }"
|
||||||
|
@click="editDescription = true"
|
||||||
|
v-else>
|
||||||
|
<span class="icon">
|
||||||
|
<font-awesome-icon icon="fas fa-edit" />
|
||||||
|
</span>
|
||||||
|
<span class="text">
|
||||||
|
{{ point.description?.length ? point.description : 'No description' }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p class="locality" v-if="point.locality">{{ point.locality }}</p>
|
<p class="locality" v-if="point.locality">{{ point.locality }}</p>
|
||||||
<p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p>
|
<p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p>
|
||||||
<p class="country" v-if="country">
|
<p class="country" v-if="country">
|
||||||
|
@ -51,7 +79,7 @@ import Dates from '../mixins/Dates.vue';
|
||||||
import GPSPoint from '../models/GPSPoint';
|
import GPSPoint from '../models/GPSPoint';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emit: ['close', 'remove'],
|
emit: ['close', 'edit', 'remove'],
|
||||||
mixins: [Dates],
|
mixins: [Dates],
|
||||||
props: {
|
props: {
|
||||||
point: {
|
point: {
|
||||||
|
@ -61,6 +89,8 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
newValue: null,
|
||||||
|
editDescription: false,
|
||||||
popup: null as Overlay | null,
|
popup: null as Overlay | null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -99,12 +129,44 @@ export default {
|
||||||
map.addOverlay(this.popup)
|
map.addOverlay(this.popup)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
editPoint() {
|
||||||
|
this.newValue.description = (this.$refs.description as HTMLTextAreaElement).value
|
||||||
|
this.$emit('edit', this.newValue)
|
||||||
|
this.editDescription = false
|
||||||
|
},
|
||||||
|
|
||||||
|
onDescriptionBlur() {
|
||||||
|
// Give it a moment to allow relevant click events to trigger
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editDescription = false
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
|
||||||
setPosition(coordinates: number[]) {
|
setPosition(coordinates: number[]) {
|
||||||
if (this.popup) {
|
if (this.popup) {
|
||||||
this.popup.setPosition(coordinates)
|
this.popup.setPosition(coordinates)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
point: {
|
||||||
|
immediate: true,
|
||||||
|
handler(point: GPSPoint | null) {
|
||||||
|
if (point) {
|
||||||
|
this.newValue = point
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
editDescription(edit: boolean) {
|
||||||
|
if (edit) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.description?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -128,7 +190,7 @@ export default {
|
||||||
.header {
|
.header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5em;
|
top: 0.5em;
|
||||||
right: 0.5em;
|
right: 0;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -158,6 +220,57 @@ export default {
|
||||||
margin: -0.25em 0 0.25em 0;
|
margin: -0.25em 0 0.25em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.no-content) {
|
||||||
|
.text {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-content {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: var(--color-accent);
|
||||||
|
border: none;
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-heading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.timestamp {
|
.timestamp {
|
||||||
color: var(--color-heading);
|
color: var(--color-heading);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -12,13 +12,13 @@ export default {
|
||||||
}) || []
|
}) || []
|
||||||
|
|
||||||
return points.map((gps: any) =>
|
return points.map((gps: any) =>
|
||||||
new GPSPoint({
|
new GPSPoint({
|
||||||
...gps,
|
...gps,
|
||||||
// Normalize timestamp to Date object
|
// Normalize timestamp to Date object
|
||||||
timestamp: new Date(gps.timestamp),
|
timestamp: new Date(gps.timestamp),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.sort((a: GPSPoint, b: GPSPoint) => a.timestamp.getTime() - b.timestamp.getTime())
|
.sort((a: GPSPoint, b: GPSPoint) => a.timestamp.getTime() - b.timestamp.getTime())
|
||||||
},
|
},
|
||||||
|
|
||||||
async deletePoints(points: GPSPoint[]) {
|
async deletePoints(points: GPSPoint[]) {
|
||||||
|
@ -27,6 +27,13 @@ export default {
|
||||||
body: points.map((point: GPSPoint) => point.id),
|
body: points.map((point: GPSPoint) => point.id),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updatePoints(points: GPSPoint[]) {
|
||||||
|
await this.request('/gpsdata', {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: points,
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,22 +3,50 @@ class GPSPoint {
|
||||||
public latitude: number;
|
public latitude: number;
|
||||||
public longitude: number;
|
public longitude: number;
|
||||||
public altitude: number;
|
public altitude: number;
|
||||||
|
public deviceId: string;
|
||||||
public address: string;
|
public address: string;
|
||||||
public locality: string;
|
public locality: string;
|
||||||
public country: string;
|
public country: string;
|
||||||
public postalCode: string;
|
public postalCode: string;
|
||||||
|
public description?: string;
|
||||||
public timestamp: Date;
|
public timestamp: Date;
|
||||||
|
|
||||||
constructor(public data: any) {
|
constructor({
|
||||||
this.id = data.id;
|
id,
|
||||||
this.latitude = data.latitude;
|
latitude,
|
||||||
this.longitude = data.longitude;
|
longitude,
|
||||||
this.altitude = data.altitude;
|
altitude,
|
||||||
this.address = data.address;
|
deviceId,
|
||||||
this.locality = data.locality;
|
address,
|
||||||
this.country = data.country;
|
locality,
|
||||||
this.postalCode = data.postalCode;
|
country,
|
||||||
this.timestamp = data.timestamp;
|
postalCode,
|
||||||
|
description,
|
||||||
|
timestamp,
|
||||||
|
}: {
|
||||||
|
id: number;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
altitude: number;
|
||||||
|
deviceId: string;
|
||||||
|
address: string;
|
||||||
|
locality: string;
|
||||||
|
country: string;
|
||||||
|
postalCode: string;
|
||||||
|
description?: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}) {
|
||||||
|
this.id = id;
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.altitude = altitude;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.address = address;
|
||||||
|
this.locality = locality;
|
||||||
|
this.country = country;
|
||||||
|
this.postalCode = postalCode;
|
||||||
|
this.description = description;
|
||||||
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,8 @@ class Db {
|
||||||
'address',
|
'address',
|
||||||
'locality',
|
'locality',
|
||||||
'country',
|
'country',
|
||||||
'postalCode'
|
'postalCode',
|
||||||
|
'description',
|
||||||
].reduce((acc: any, name: string) => {
|
].reduce((acc: any, name: string) => {
|
||||||
acc[name] = process.env[this.prefixedEnv(name)];
|
acc[name] = process.env[this.prefixedEnv(name)];
|
||||||
if (!acc[name]?.length && (requiredColumns[name] || opts.locationUrl === opts.url)) {
|
if (!acc[name]?.length && (requiredColumns[name] || opts.locationUrl === opts.url)) {
|
||||||
|
|
|
@ -218,6 +218,10 @@ async function createLocationHistoryTable(query: { context: any }) {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
},
|
},
|
||||||
|
[$db.locationTableColumns['description']]: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
[$db.locationTableColumns['timestamp']]: {
|
[$db.locationTableColumns['timestamp']]: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
defaultValue: DataTypes.NOW,
|
defaultValue: DataTypes.NOW,
|
||||||
|
|
|
@ -66,6 +66,14 @@ function GPSData(locationTableColumns: Record<string, string>): Record<string, a
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const descriptionCol: string = locationTableColumns['description'];
|
||||||
|
if (descriptionCol?.length) {
|
||||||
|
typeDef[descriptionCol] = {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
typeDef[locationTableColumns['timestamp']] = {
|
typeDef[locationTableColumns['timestamp']] = {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
defaultValue: DataTypes.NOW
|
defaultValue: DataTypes.NOW
|
||||||
|
|
|
@ -8,6 +8,7 @@ class GPSPoint {
|
||||||
public locality: string | null;
|
public locality: string | null;
|
||||||
public country: string | null;
|
public country: string | null;
|
||||||
public postalCode: string | null;
|
public postalCode: string | null;
|
||||||
|
public description: string | null;
|
||||||
public timestamp: Date;
|
public timestamp: Date;
|
||||||
|
|
||||||
constructor(record: any) {
|
constructor(record: any) {
|
||||||
|
@ -20,6 +21,7 @@ class GPSPoint {
|
||||||
this.locality = record.locality;
|
this.locality = record.locality;
|
||||||
this.country = record.country;
|
this.country = record.country;
|
||||||
this.postalCode = record.postalCode;
|
this.postalCode = record.postalCode;
|
||||||
|
this.description = record.description;
|
||||||
this.timestamp = record.timestamp;
|
this.timestamp = record.timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ class Location {
|
||||||
locality: data[mappings.locality],
|
locality: data[mappings.locality],
|
||||||
country: data[mappings.country],
|
country: data[mappings.country],
|
||||||
postalCode: data[mappings.postalCode],
|
postalCode: data[mappings.postalCode],
|
||||||
|
description: data[mappings.description],
|
||||||
timestamp: data[mappings.timestamp],
|
timestamp: data[mappings.timestamp],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -62,6 +63,7 @@ class Location {
|
||||||
locality: data[mappings.locality],
|
locality: data[mappings.locality],
|
||||||
country: data[mappings.country],
|
country: data[mappings.country],
|
||||||
postalCode: data[mappings.postalCode],
|
postalCode: data[mappings.postalCode],
|
||||||
|
description: data[mappings.description],
|
||||||
timestamp: data[mappings.timestamp],
|
timestamp: data[mappings.timestamp],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -94,6 +96,7 @@ class Location {
|
||||||
[mappings.locality]: p.locality,
|
[mappings.locality]: p.locality,
|
||||||
[mappings.country]: p.country,
|
[mappings.country]: p.country,
|
||||||
[mappings.postalCode]: p.postalcode,
|
[mappings.postalCode]: p.postalcode,
|
||||||
|
[mappings.description]: p.description,
|
||||||
[mappings.timestamp]: p.timestamp
|
[mappings.timestamp]: p.timestamp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -111,6 +114,7 @@ class Location {
|
||||||
locality: data[mappings.locality],
|
locality: data[mappings.locality],
|
||||||
country: data[mappings.country],
|
country: data[mappings.country],
|
||||||
postalCode: data[mappings.postalCode],
|
postalCode: data[mappings.postalCode],
|
||||||
|
description: data[mappings.description],
|
||||||
timestamp: data[mappings.timestamp],
|
timestamp: data[mappings.timestamp],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -119,6 +123,41 @@ class Location {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updatePoints(points: GPSPoint[]): Promise<void> {
|
||||||
|
const mappings: any = $db.locationTableColumns;
|
||||||
|
// Lowercase the keys of the mappings object -
|
||||||
|
// some databases are case-insensitive and this will help with consistency
|
||||||
|
const normalizedPoints = points.map((p) =>
|
||||||
|
Object.entries(p).reduce((acc, [key, value]) => {
|
||||||
|
acc[key.toLowerCase()] = value;
|
||||||
|
return acc;
|
||||||
|
} , {} as Record<string, any>)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $db.GPSData().bulkCreate(
|
||||||
|
normalizedPoints.map((p) => {
|
||||||
|
return {
|
||||||
|
[mappings.id]: p.id,
|
||||||
|
[mappings.deviceId]: p.deviceid,
|
||||||
|
[mappings.latitude]: p.latitude,
|
||||||
|
[mappings.longitude]: p.longitude,
|
||||||
|
[mappings.altitude]: p.altitude,
|
||||||
|
[mappings.address]: p.address,
|
||||||
|
[mappings.locality]: p.locality,
|
||||||
|
[mappings.country]: p.country,
|
||||||
|
[mappings.postalCode]: p.postalcode,
|
||||||
|
[mappings.description]: p.description,
|
||||||
|
[mappings.timestamp]: p.timestamp
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ updateOnDuplicate: Object.keys(mappings) }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error updating data: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async deletePoints(points: number[]): Promise<void> {
|
public async deletePoints(points: number[]): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await $db.GPSData().destroy({
|
await $db.GPSData().destroy({
|
||||||
|
|
|
@ -14,6 +14,7 @@ class LocationRequest {
|
||||||
country: Optional<string> = null;
|
country: Optional<string> = null;
|
||||||
locality: Optional<string> = null;
|
locality: Optional<string> = null;
|
||||||
postalCode: Optional<string> = null;
|
postalCode: Optional<string> = null;
|
||||||
|
description: Optional<string> = null;
|
||||||
orderBy: string = 'timestamp';
|
orderBy: string = 'timestamp';
|
||||||
order: string = 'DESC';
|
order: string = 'DESC';
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ class LocationRequest {
|
||||||
this.country = req.country;
|
this.country = req.country;
|
||||||
this.locality = req.locality;
|
this.locality = req.locality;
|
||||||
this.postalCode = req.postalCode;
|
this.postalCode = req.postalCode;
|
||||||
|
this.description = req.description;
|
||||||
this.orderBy = req.orderBy || this.orderBy;
|
this.orderBy = req.orderBy || this.orderBy;
|
||||||
this.order = req.order || this.order;
|
this.order = req.order || this.order;
|
||||||
}
|
}
|
||||||
|
@ -95,6 +97,10 @@ class LocationRequest {
|
||||||
where[colMapping.postalCode || 'postalCode'] = this.postalCode;
|
where[colMapping.postalCode || 'postalCode'] = this.postalCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.description != null) {
|
||||||
|
where[colMapping.description || 'description'] = {[Op.like]: `%${this.description}%`};
|
||||||
|
}
|
||||||
|
|
||||||
queryMap.where = where;
|
queryMap.where = where;
|
||||||
queryMap.order = [[colMapping[this.orderBy], this.order.toUpperCase()]];
|
queryMap.order = [[colMapping[this.orderBy], this.order.toUpperCase()]];
|
||||||
return queryMap;
|
return queryMap;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { authenticate } from '../../../auth';
|
||||||
import { AuthInfo } from '../../../auth';
|
import { AuthInfo } from '../../../auth';
|
||||||
import { LocationRequest } from '../../../requests';
|
import { LocationRequest } from '../../../requests';
|
||||||
import { Optional } from '../../../types';
|
import { Optional } from '../../../types';
|
||||||
import { RoleName } from '../../../models';
|
import { GPSPoint, RoleName } from '../../../models';
|
||||||
import ApiV1Route from './Route';
|
import ApiV1Route from './Route';
|
||||||
|
|
||||||
class GPSData extends ApiV1Route {
|
class GPSData extends ApiV1Route {
|
||||||
|
@ -48,6 +48,20 @@ class GPSData extends ApiV1Route {
|
||||||
res.status(201).send();
|
res.status(201).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@authenticate()
|
||||||
|
patch = async (req: Request, res: Response, auth: Optional<AuthInfo>) => {
|
||||||
|
const points = (req.body as GPSPoint[]).map((p) => {
|
||||||
|
const descr = p.description?.trim()
|
||||||
|
p.description = descr?.length ? descr : null;
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceIds = points.map((p: any) => p.deviceId).filter((d: any) => !!d);
|
||||||
|
this.validateOwnership(deviceIds, auth!);
|
||||||
|
await $repos.location.updatePoints(points);
|
||||||
|
res.status(204).send();
|
||||||
|
}
|
||||||
|
|
||||||
@authenticate()
|
@authenticate()
|
||||||
delete = async (req: Request, res: Response, auth: Optional<AuthInfo>) => {
|
delete = async (req: Request, res: Response, auth: Optional<AuthInfo>) => {
|
||||||
const pointIds = req.body as number[];
|
const pointIds = req.body as number[];
|
||||||
|
|
Loading…
Add table
Reference in a new issue