mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
change from using geojson object fullstack to use Location model in admin frontend
This commit is contained in:
parent
70f8b6d18b
commit
aadd00f175
@ -37,8 +37,8 @@
|
||||
<span v-if="isValidLocation">
|
||||
{{
|
||||
$t('geo-coordinates.format', {
|
||||
latitude: location.coordinates[1],
|
||||
longitude: location.coordinates[0],
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
@ -166,7 +166,7 @@ export default {
|
||||
return this.originalGmsApiKey !== this.gmsApiKey
|
||||
},
|
||||
isValidLocation() {
|
||||
return this.location && this.location.coordinates.length === 2
|
||||
return this.location && this.location.latitude && this.location.longitude
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -6,26 +6,26 @@ import VueI18n from 'vue-i18n'
|
||||
Vue.use(VueI18n)
|
||||
|
||||
const localVue = global.localVue
|
||||
const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
'geo-coordinates.format': '{latitude}, {longitude}',
|
||||
},
|
||||
},
|
||||
})
|
||||
const mocks = {
|
||||
$t: jest.fn((t, v) => {
|
||||
if (t === 'geo-coordinates.format') {
|
||||
return `${v.latitude}, ${v.longitude}`
|
||||
}
|
||||
return t
|
||||
}),
|
||||
}
|
||||
|
||||
describe('Coordinates', () => {
|
||||
let wrapper
|
||||
const value = {
|
||||
type: 'Point',
|
||||
coordinates: [12.34, 56.78],
|
||||
latitude: 56.78,
|
||||
longitude: 12.34,
|
||||
}
|
||||
|
||||
const createWrapper = (propsData) => {
|
||||
return mount(Coordinates, {
|
||||
localVue,
|
||||
i18n,
|
||||
mocks,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
@ -49,8 +49,10 @@ describe('Coordinates', () => {
|
||||
await latitudeInput.setValue('34.56')
|
||||
await longitudeInput.setValue('78.90')
|
||||
|
||||
expect(wrapper.vm.latitude).toBe('34.56')
|
||||
expect(wrapper.vm.longitude).toBe('78.90')
|
||||
expect(wrapper.vm.inputValue).toStrictEqual({
|
||||
latitude: 34.56,
|
||||
longitude: 78.9,
|
||||
})
|
||||
})
|
||||
|
||||
it('emits input event with updated values', async () => {
|
||||
@ -60,46 +62,28 @@ describe('Coordinates', () => {
|
||||
await latitudeInput.setValue('34.56')
|
||||
expect(wrapper.emitted().input).toBeTruthy()
|
||||
expect(wrapper.emitted().input[0][0]).toEqual({
|
||||
type: 'Point',
|
||||
coordinates: [12.34, 34.56],
|
||||
latitude: 34.56,
|
||||
longitude: 12.34,
|
||||
})
|
||||
|
||||
await longitudeInput.setValue('78.90')
|
||||
expect(wrapper.emitted().input).toBeTruthy()
|
||||
expect(wrapper.emitted().input[1][0]).toEqual({
|
||||
type: 'Point',
|
||||
coordinates: [78.9, 34.56],
|
||||
latitude: 34.56,
|
||||
longitude: 78.9,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates latitudeLongitude when latitude or longitude changes', async () => {
|
||||
const latitudeInput = wrapper.find('#home-community-latitude')
|
||||
const longitudeInput = wrapper.find('#home-community-longitude')
|
||||
|
||||
await latitudeInput.setValue('34.56')
|
||||
await longitudeInput.setValue('78.90')
|
||||
|
||||
expect(wrapper.vm.latitudeLongitude).toBe('34.56, 78.90')
|
||||
})
|
||||
|
||||
it('splits coordinates correctly when entering in latitudeLongitude input', async () => {
|
||||
const latitudeLongitudeInput = wrapper.find('#home-community-latitude-longitude-smart')
|
||||
|
||||
await latitudeLongitudeInput.setValue('34.56, 78.90')
|
||||
await latitudeLongitudeInput.trigger('input')
|
||||
|
||||
expect(wrapper.vm.latitude).toBe(34.56)
|
||||
expect(wrapper.vm.longitude).toBe(78.9)
|
||||
})
|
||||
|
||||
it('sets inputValue to null if coordinates are invalid', async () => {
|
||||
const latitudeInput = wrapper.find('#home-community-latitude')
|
||||
const longitudeInput = wrapper.find('#home-community-longitude')
|
||||
|
||||
await latitudeInput.setValue('invalid')
|
||||
await longitudeInput.setValue('78.90')
|
||||
|
||||
expect(wrapper.vm.inputValue).toBeNull()
|
||||
expect(wrapper.vm.inputValue).toStrictEqual({
|
||||
latitude: 34.56,
|
||||
longitude: 78.9,
|
||||
})
|
||||
})
|
||||
|
||||
it('validates coordinates correctly', async () => {
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
:description="$t('geo-coordinates.latitude-longitude-smart.describe')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="latitudeLongitude"
|
||||
v-model="locationString"
|
||||
id="home-community-latitude-longitude-smart"
|
||||
type="text"
|
||||
@input="splitCoordinates"
|
||||
@ -19,7 +19,7 @@
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('latitude')" label-for="home-community-latitude">
|
||||
<b-form-input
|
||||
v-model="latitude"
|
||||
v-model="inputValue.latitude"
|
||||
id="home-community-latitude"
|
||||
type="text"
|
||||
@input="valueUpdated"
|
||||
@ -27,7 +27,7 @@
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('longitude')" label-for="home-community-longitude">
|
||||
<b-form-input
|
||||
v-model="longitude"
|
||||
v-model="inputValue.longitude"
|
||||
id="home-community-longitude"
|
||||
type="text"
|
||||
@input="valueUpdated"
|
||||
@ -47,21 +47,20 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
inputValue: this.value,
|
||||
originalValueString: this.getLatitudeLongitudeString(this.value),
|
||||
longitude: this.value ? this.value.coordinates[0] : '',
|
||||
latitude: this.value ? this.value.coordinates[1] : '',
|
||||
latitudeLongitude: this.getLatitudeLongitudeString(this.value),
|
||||
originalValue: this.value,
|
||||
locationString: this.getLatitudeLongitudeString(this.value),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isValid() {
|
||||
return (
|
||||
(!isNaN(parseFloat(this.longitude)) && !isNaN(parseFloat(this.latitude))) ||
|
||||
(this.longitude === '' && this.latitude === '')
|
||||
(!isNaN(parseFloat(this.inputValue.longitude)) &&
|
||||
!isNaN(parseFloat(this.inputValue.latitude))) ||
|
||||
(this.inputValue.longitude === '' && this.inputValue.latitude === '')
|
||||
)
|
||||
},
|
||||
isChanged() {
|
||||
return this.getLatitudeLongitudeString(this.inputValue) !== this.originalValueString
|
||||
return this.inputValue !== this.originalValue
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -72,44 +71,32 @@ export default {
|
||||
if (parts.length === 2) {
|
||||
const [lat, lon] = parts
|
||||
if (!isNaN(parseFloat(lon) && !isNaN(parseFloat(lat)))) {
|
||||
this.longitude = parseFloat(lon)
|
||||
this.latitude = parseFloat(lat)
|
||||
this.inputValue.longitude = parseFloat(lon)
|
||||
this.inputValue.latitude = parseFloat(lat)
|
||||
}
|
||||
}
|
||||
this.valueUpdated()
|
||||
},
|
||||
getLatitudeLongitudeString(geoJSONPoint) {
|
||||
if (!geoJSONPoint || geoJSONPoint.coordinates.length !== 2) {
|
||||
return ''
|
||||
}
|
||||
return this.$t('geo-coordinates.format', {
|
||||
latitude: geoJSONPoint.coordinates[1],
|
||||
longitude: geoJSONPoint.coordinates[0],
|
||||
})
|
||||
},
|
||||
valueUpdated() {
|
||||
if (this.longitude && this.latitude) {
|
||||
this.inputValue = {
|
||||
type: 'Point',
|
||||
// format in geojson Point: coordinates[longitude, latitude]
|
||||
coordinates: [this.longitude, this.latitude],
|
||||
}
|
||||
} else {
|
||||
this.inputValue = null
|
||||
}
|
||||
this.latitudeLongitude = this.getLatitudeLongitudeString(this.inputValue)
|
||||
sanitizeLocation(location) {
|
||||
if (!location) return { latitude: '', longitude: '' }
|
||||
|
||||
if (this.inputValue) {
|
||||
// make sure all coordinates are numbers
|
||||
this.inputValue.coordinates = this.inputValue.coordinates
|
||||
.map((coord) => parseFloat(coord))
|
||||
// Remove null and NaN values
|
||||
.filter((coord) => coord !== null && !isNaN(coord))
|
||||
if (this.inputValue.coordinates.length !== 2) {
|
||||
this.inputValue = null
|
||||
}
|
||||
const parseNumber = (value) => {
|
||||
const number = parseFloat(value)
|
||||
return isNaN(number) ? '' : number
|
||||
}
|
||||
|
||||
return {
|
||||
latitude: parseNumber(location.latitude),
|
||||
longitude: parseNumber(location.longitude),
|
||||
}
|
||||
},
|
||||
getLatitudeLongitudeString({ latitude, longitude } = {}) {
|
||||
return latitude && longitude ? this.$t('geo-coordinates.format', { latitude, longitude }) : ''
|
||||
},
|
||||
valueUpdated(value) {
|
||||
this.locationString = this.getLatitudeLongitudeString(this.inputValue)
|
||||
this.inputValue = this.sanitizeLocation(this.inputValue)
|
||||
|
||||
if (this.isValid && this.isChanged) {
|
||||
if (this.$parent.valueChanged) {
|
||||
this.$parent.valueChanged()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const updateHomeCommunity = gql`
|
||||
mutation ($uuid: String!, $gmsApiKey: String, $location: Point) {
|
||||
mutation ($uuid: String!, $gmsApiKey: String, $location: Location) {
|
||||
updateHomeCommunity(uuid: $uuid, gmsApiKey: $gmsApiKey, location: $location) {
|
||||
id
|
||||
}
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
"email-templates": "^10.0.1",
|
||||
"express": "^4.17.1",
|
||||
"express-slow-down": "^2.0.1",
|
||||
"geojson": "^0.5.0",
|
||||
"gradido-database": "file:../database",
|
||||
"graphql": "^15.5.1",
|
||||
"graphql-request": "5.0.0",
|
||||
@ -58,7 +57,6 @@
|
||||
"@types/email-templates": "^10.0.1",
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/geojson": "^7946.0.13",
|
||||
"@types/i18n": "^0.13.4",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { IsString, IsUUID } from 'class-validator'
|
||||
import { ArgsType, Field, InputType } from 'type-graphql'
|
||||
|
||||
import { Point } from '@/graphql/model/Point'
|
||||
import { isValidPoint } from '@/graphql/validator/Point'
|
||||
import { Location } from '@/graphql/model/Location'
|
||||
import { isValidLocation } from '@/graphql/validator/Location'
|
||||
|
||||
@ArgsType()
|
||||
@InputType()
|
||||
@ -15,7 +15,7 @@ export class EditCommunityInput {
|
||||
@IsString()
|
||||
gmsApiKey?: string | null
|
||||
|
||||
@Field(() => Point, { nullable: true })
|
||||
@isValidPoint()
|
||||
location?: Point | null
|
||||
@Field(() => Location, { nullable: true })
|
||||
@isValidLocation()
|
||||
location?: Location | null
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { Point } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
import { Point2Location } from '@/graphql/resolver/util/Location2Point'
|
||||
|
||||
import { FederatedCommunity } from './FederatedCommunity'
|
||||
import { Point } from './Point'
|
||||
import { Location } from './Location'
|
||||
|
||||
@ObjectType()
|
||||
export class AdminCommunityView {
|
||||
@ -37,7 +40,9 @@ export class AdminCommunityView {
|
||||
this.uuid = dbCom.communityUuid
|
||||
this.authenticatedAt = dbCom.authenticatedAt
|
||||
this.gmsApiKey = dbCom.gmsApiKey
|
||||
this.location = dbCom.location
|
||||
if (dbCom.location) {
|
||||
this.location = Point2Location(dbCom.location as Point)
|
||||
}
|
||||
}
|
||||
|
||||
@Field(() => Boolean)
|
||||
@ -64,8 +69,8 @@ export class AdminCommunityView {
|
||||
@Field(() => String, { nullable: true })
|
||||
gmsApiKey: string | null
|
||||
|
||||
@Field(() => Point, { nullable: true })
|
||||
location: Point | null
|
||||
@Field(() => Location, { nullable: true })
|
||||
location: Location | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
creationDate: Date | null
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { Position, Point as geojsonPoint } from 'geojson'
|
||||
|
||||
export class Point implements geojsonPoint {
|
||||
constructor() {
|
||||
this.coordinates = []
|
||||
this.type = 'Point'
|
||||
}
|
||||
|
||||
type: 'Point'
|
||||
coordinates: Position
|
||||
}
|
||||
@ -18,6 +18,7 @@ import {
|
||||
getCommunityByUuid,
|
||||
getHomeCommunity,
|
||||
} from './util/communities'
|
||||
import { Location2Point } from './util/Location2Point'
|
||||
|
||||
@Resolver()
|
||||
export class CommunityResolver {
|
||||
@ -90,7 +91,9 @@ export class CommunityResolver {
|
||||
}
|
||||
if (homeCom.gmsApiKey !== gmsApiKey || homeCom.location !== location) {
|
||||
homeCom.gmsApiKey = gmsApiKey ?? null
|
||||
homeCom.location = location ?? null
|
||||
if (location) {
|
||||
homeCom.location = Location2Point(location)
|
||||
}
|
||||
await DbCommunity.save(homeCom)
|
||||
}
|
||||
return new Community(homeCom)
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { GraphQLScalarType, Kind } from 'graphql'
|
||||
|
||||
import { Point } from '@/graphql/model/Point'
|
||||
|
||||
export const PointScalar = new GraphQLScalarType({
|
||||
name: 'Point',
|
||||
description:
|
||||
'The `Point` scalar type to represent longitude and latitude values of a geo location',
|
||||
|
||||
serialize(value: Point) {
|
||||
// Check type of value
|
||||
if (value.type !== 'Point') {
|
||||
throw new Error(`PointScalar can only serialize Geometry type 'Point' values`)
|
||||
}
|
||||
return value
|
||||
},
|
||||
|
||||
parseValue(value): Point {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (value.type !== 'Point') {
|
||||
throw new Error(`PointScalar can only deserialize Geometry type 'Point' values`)
|
||||
}
|
||||
return value as Point
|
||||
},
|
||||
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind !== Kind.STRING) {
|
||||
throw new TypeError(`${String(ast)} is not a valid Geometry value.`)
|
||||
}
|
||||
|
||||
const point = JSON.parse(ast.value) as Point
|
||||
return point
|
||||
},
|
||||
})
|
||||
@ -7,10 +7,8 @@ import { buildSchema } from 'type-graphql'
|
||||
import { Location } from '@model/Location'
|
||||
|
||||
import { isAuthorized } from './directive/isAuthorized'
|
||||
import { Point } from './model/Point'
|
||||
import { DecimalScalar } from './scalar/Decimal'
|
||||
import { LocationScalar } from './scalar/Location'
|
||||
import { PointScalar } from './scalar/Point'
|
||||
|
||||
export const schema = async (): Promise<GraphQLSchema> => {
|
||||
return buildSchema({
|
||||
@ -19,7 +17,6 @@ export const schema = async (): Promise<GraphQLSchema> => {
|
||||
scalarsMap: [
|
||||
{ type: Decimal, scalar: DecimalScalar },
|
||||
{ type: Location, scalar: LocationScalar },
|
||||
{ type: Point, scalar: PointScalar },
|
||||
],
|
||||
validate: {
|
||||
validationError: { target: false },
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'
|
||||
|
||||
import { Point } from '@/graphql/model/Point'
|
||||
|
||||
export function isValidPoint(validationOptions?: ValidationOptions) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
return function (object: Object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isValidPoint',
|
||||
target: object.constructor,
|
||||
propertyName,
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: Point) {
|
||||
if (value.type === 'Point') {
|
||||
if (value.coordinates.length === 2) {
|
||||
return value.coordinates.every((coord) => typeof coord === 'number')
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `${propertyName} must be a valid Point in geoJSON Format, ${args.property}`
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -994,11 +994,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/geojson@^7946.0.13":
|
||||
version "7946.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613"
|
||||
integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==
|
||||
|
||||
"@types/glob@^7.1.3":
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672"
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
Point,
|
||||
Geometry,
|
||||
} from 'typeorm'
|
||||
import { FederatedCommunity } from '../FederatedCommunity'
|
||||
import { GeometryTransformer } from '../../src/typeorm/GeometryTransformer'
|
||||
@ -56,12 +56,12 @@ export class Community extends BaseEntity {
|
||||
|
||||
@Column({
|
||||
name: 'location',
|
||||
type: 'point',
|
||||
type: 'geometry',
|
||||
default: null,
|
||||
nullable: true,
|
||||
transformer: GeometryTransformer,
|
||||
})
|
||||
location: Point | null
|
||||
location: Geometry | null
|
||||
|
||||
@CreateDateColumn({
|
||||
name: 'created_at',
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(
|
||||
'ALTER TABLE `communities` ADD COLUMN IF NOT EXISTS `location` POINT DEFAULT NULL NULL AFTER `gms_api_key`;',
|
||||
'ALTER TABLE `communities` ADD COLUMN IF NOT EXISTS `location` geometry DEFAULT NULL NULL AFTER `gms_api_key`;',
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user