move distanceToMe onto Location (#8464)

This commit is contained in:
Ulf Gebhardt 2025-05-01 10:33:53 +02:00 committed by GitHub
parent 21f343b8cf
commit f8864a779c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 240 additions and 228 deletions

View File

@ -14,6 +14,7 @@ type Location {
lat: Float
lng: Float
parent: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
distanceToMe: Int
}
# This is not smart - we need one location for everything - use the same type everywhere!

View File

@ -50,7 +50,6 @@ type User {
locationName: String
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
distanceToMe: Int
about: String
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")

View File

@ -483,6 +483,9 @@ export default shield(
email: or(isMyOwn, isAdmin),
emailNotificationSettings: isMyOwn,
},
Location: {
distanceToMe: isAuthenticated,
},
Report: isModerator,
},
{

View File

@ -8,7 +8,7 @@ import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
let mutate, authenticatedUser
let query, mutate, authenticatedUser
const driver = getDriver()
const neode = getNeode()
@ -25,6 +25,7 @@ beforeAll(async () => {
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
@ -93,3 +94,209 @@ describe('resolvers', () => {
})
})
})
const distanceToMeQuery = gql`
query ($id: ID!) {
User(id: $id) {
location {
distanceToMe
}
}
}
`
let user, myPlaceUser, otherPlaceUser, noCordsPlaceUser, noPlaceUser
describe('distanceToMe', () => {
beforeEach(async () => {
const Hamburg = await Factory.build('location', {
id: 'region.5127278006398860',
name: 'Hamburg',
type: 'region',
lng: 10.0,
lat: 53.55,
nameES: 'Hamburgo',
nameFR: 'Hambourg',
nameIT: 'Amburgo',
nameEN: 'Hamburg',
namePT: 'Hamburgo',
nameDE: 'Hamburg',
nameNL: 'Hamburg',
namePL: 'Hamburg',
nameRU: 'Гамбург',
})
const Germany = await Factory.build('location', {
id: 'country.10743216036480410',
name: 'Germany',
type: 'country',
namePT: 'Alemanha',
nameDE: 'Deutschland',
nameES: 'Alemania',
nameNL: 'Duitsland',
namePL: 'Niemcy',
nameFR: 'Allemagne',
nameIT: 'Germania',
nameEN: 'Germany',
nameRU: 'Германия',
})
const Paris = await Factory.build('location', {
id: 'region.9397217726497330',
name: 'Paris',
type: 'region',
lng: 2.35183,
lat: 48.85658,
nameES: 'París',
nameFR: 'Paris',
nameIT: 'Parigi',
nameEN: 'Paris',
namePT: 'Paris',
nameDE: 'Paris',
nameNL: 'Parijs',
namePL: 'Paryż',
nameRU: 'Париж',
})
user = await Factory.build('user', {
id: 'user',
role: 'user',
})
await user.relateTo(Hamburg, 'isIn')
myPlaceUser = await Factory.build('user', {
id: 'myPlaceUser',
role: 'user',
})
await myPlaceUser.relateTo(Hamburg, 'isIn')
otherPlaceUser = await Factory.build('user', {
id: 'otherPlaceUser',
role: 'user',
})
await otherPlaceUser.relateTo(Paris, 'isIn')
noCordsPlaceUser = await Factory.build('user', {
id: 'noCordsPlaceUser',
role: 'user',
})
await noCordsPlaceUser.relateTo(Germany, 'isIn')
noPlaceUser = await Factory.build('user', {
id: 'noPlaceUser',
role: 'user',
})
})
describe('query the field', () => {
describe('for self user', () => {
it('returns 0', async () => {
authenticatedUser = await user.toJson()
const targetUser = await user.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
location: {
distanceToMe: 0,
},
},
],
},
errors: undefined,
}),
)
})
})
describe('for myPlaceUser', () => {
it('returns 0', async () => {
authenticatedUser = await user.toJson()
const targetUser = await myPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
location: {
distanceToMe: 0,
},
},
],
},
errors: undefined,
}),
)
})
})
describe('for otherPlaceUser', () => {
it('returns a number', async () => {
authenticatedUser = await user.toJson()
const targetUser = await otherPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
location: {
distanceToMe: 746,
},
},
],
},
errors: undefined,
}),
)
})
})
describe('for noCordsPlaceUser', () => {
it('returns null', async () => {
authenticatedUser = await user.toJson()
const targetUser = await noCordsPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
location: {
distanceToMe: null,
},
},
],
},
errors: undefined,
}),
)
})
})
describe('for noPlaceUser', () => {
it('returns null location', async () => {
authenticatedUser = await user.toJson()
const targetUser = await noPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
location: null,
},
],
},
errors: undefined,
}),
)
})
})
})
})

View File

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { UserInputError } from 'apollo-server'
@ -20,6 +23,31 @@ export default {
'nameRU',
],
}),
distanceToMe: async (parent, _params, context, _resolveInfo) => {
const session = context.driver.session()
const query = session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (loc:Location {id: $parent.id})
MATCH (me:User {id: $user.id})-[:IS_IN]->(meLoc:Location)
WITH
point({latitude: loc.lat, longitude: loc.lng}) as locPoint,
point({latitude: meLoc.lat, longitude: meLoc.lng}) as mePoint
RETURN round(point.distance(locPoint, mePoint) / 1000) as distance
`,
{ parent, user: context.user },
)
return result.records.map((record) => record.get('distance'))[0]
})
try {
return await query
} finally {
await session.close()
}
},
},
Query: {
queryLocations: async (_object, args, _context, _resolveInfo) => {

View File

@ -660,202 +660,6 @@ const emailNotificationSettingsMutation = gql`
}
`
const distanceToMeQuery = gql`
query ($id: ID!) {
User(id: $id) {
distanceToMe
}
}
`
let myPlaceUser, otherPlaceUser, noCordsPlaceUser, noPlaceUser
describe('distanceToMe', () => {
beforeEach(async () => {
const Hamburg = await Factory.build('location', {
id: 'region.5127278006398860',
name: 'Hamburg',
type: 'region',
lng: 10.0,
lat: 53.55,
nameES: 'Hamburgo',
nameFR: 'Hambourg',
nameIT: 'Amburgo',
nameEN: 'Hamburg',
namePT: 'Hamburgo',
nameDE: 'Hamburg',
nameNL: 'Hamburg',
namePL: 'Hamburg',
nameRU: 'Гамбург',
})
const Germany = await Factory.build('location', {
id: 'country.10743216036480410',
name: 'Germany',
type: 'country',
namePT: 'Alemanha',
nameDE: 'Deutschland',
nameES: 'Alemania',
nameNL: 'Duitsland',
namePL: 'Niemcy',
nameFR: 'Allemagne',
nameIT: 'Germania',
nameEN: 'Germany',
nameRU: 'Германия',
})
const Paris = await Factory.build('location', {
id: 'region.9397217726497330',
name: 'Paris',
type: 'region',
lng: 2.35183,
lat: 48.85658,
nameES: 'París',
nameFR: 'Paris',
nameIT: 'Parigi',
nameEN: 'Paris',
namePT: 'Paris',
nameDE: 'Paris',
nameNL: 'Parijs',
namePL: 'Paryż',
nameRU: 'Париж',
})
user = await Factory.build('user', {
id: 'user',
role: 'user',
})
await user.relateTo(Hamburg, 'isIn')
myPlaceUser = await Factory.build('user', {
id: 'myPlaceUser',
role: 'user',
})
await myPlaceUser.relateTo(Hamburg, 'isIn')
otherPlaceUser = await Factory.build('user', {
id: 'otherPlaceUser',
role: 'user',
})
await otherPlaceUser.relateTo(Paris, 'isIn')
noCordsPlaceUser = await Factory.build('user', {
id: 'noCordsPlaceUser',
role: 'user',
})
await noCordsPlaceUser.relateTo(Germany, 'isIn')
noPlaceUser = await Factory.build('user', {
id: 'noPlaceUser',
role: 'user',
})
})
describe('query the field', () => {
describe('for self user', () => {
it('returns null', async () => {
authenticatedUser = await user.toJson()
const targetUser = await user.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
distanceToMe: null,
},
],
},
errors: undefined,
}),
)
})
})
describe('for myPlaceUser', () => {
it('returns 0', async () => {
authenticatedUser = await user.toJson()
const targetUser = await myPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
distanceToMe: 0,
},
],
},
errors: undefined,
}),
)
})
})
describe('for otherPlaceUser', () => {
it('returns a number', async () => {
authenticatedUser = await user.toJson()
const targetUser = await otherPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
distanceToMe: 746,
},
],
},
errors: undefined,
}),
)
})
})
describe('for noCordsPlaceUser', () => {
it('returns null', async () => {
authenticatedUser = await user.toJson()
const targetUser = await noCordsPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
distanceToMe: null,
},
],
},
errors: undefined,
}),
)
})
})
describe('for noPlaceUser', () => {
it('returns null', async () => {
authenticatedUser = await user.toJson()
const targetUser = await noPlaceUser.toJson()
await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
distanceToMe: null,
},
],
},
errors: undefined,
}),
)
})
})
})
})
describe('emailNotificationSettings', () => {
beforeEach(async () => {
user = await Factory.build('user', {

View File

@ -467,36 +467,6 @@ export default {
},
},
User: {
distanceToMe: async (parent, _params, context, _resolveInfo) => {
// is it myself?
if (parent.id === context.user.id) {
return null
}
const session = context.driver.session()
const query = session.readTransaction(async (transaction) => {
const result = await transaction.run(
`
MATCH (user:User {id: $parent.id})-[:IS_IN]->(userLoc:Location)
MATCH (me:User {id: $user.id})-[:IS_IN]->(meLoc:Location)
WITH
point({latitude: userLoc.lat, longitude: userLoc.lng}) as userPoint,
point({latitude: meLoc.lat, longitude: meLoc.lng}) as mePoint
RETURN round(point.distance(userPoint, mePoint) / 1000) as distance
`,
{ parent, user: context.user },
)
return result.records.map((record) => record.get('distance'))[0]
})
try {
return await query
} finally {
session.close()
}
},
emailNotificationSettings: async (parent, _params, _context, _resolveInfo) => {
return [
{