mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #4185 from Ocelot-Social-Community/fix_locations
Fix locations
This commit is contained in:
commit
49f7897689
@ -121,6 +121,7 @@ export default shield(
|
||||
userData: isAuthenticated,
|
||||
MyInviteCodes: isAuthenticated,
|
||||
isValidInviteCode: allow,
|
||||
queryLocations: isAuthenticated,
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { queryLocations } from './users/location'
|
||||
|
||||
export default {
|
||||
Location: {
|
||||
@ -16,4 +18,13 @@ export default {
|
||||
],
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
queryLocations: async (object, args, context, resolveInfo) => {
|
||||
try {
|
||||
return queryLocations(args)
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -137,4 +137,15 @@ const createOrUpdateLocations = async (userId, locationName, session) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const queryLocations = async ({ place, lang }) => {
|
||||
const res = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`,
|
||||
)
|
||||
// Return empty array if no location found or error occurred
|
||||
if (!res || !res.features) {
|
||||
return []
|
||||
}
|
||||
return res.features
|
||||
}
|
||||
|
||||
export default createOrUpdateLocations
|
||||
|
||||
@ -6,7 +6,7 @@ import createServer from '../../../server'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
let authenticatedUser, mutate, variables
|
||||
let authenticatedUser, mutate, query, variables
|
||||
|
||||
const updateUserMutation = gql`
|
||||
mutation($id: ID!, $name: String!, $locationName: String) {
|
||||
@ -16,6 +16,15 @@ const updateUserMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const queryLocations = gql`
|
||||
query($place: String!, $lang: String!) {
|
||||
queryLocations(place: $place, lang: $lang) {
|
||||
place_name
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const newlyCreatedNodesWithLocales = [
|
||||
{
|
||||
city: {
|
||||
@ -76,6 +85,7 @@ beforeAll(() => {
|
||||
},
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
query = createTestClient(server).query
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@ -85,6 +95,66 @@ beforeEach(() => {
|
||||
|
||||
afterEach(cleanDatabase)
|
||||
|
||||
describe('Location Service', () => {
|
||||
// Authentication
|
||||
// TODO: unify, externalize, simplify, wtf?
|
||||
let user
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build('user', {
|
||||
id: 'location-user',
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
it('query Location existing', async () => {
|
||||
variables = {
|
||||
place: 'Berlin',
|
||||
lang: 'en',
|
||||
}
|
||||
const result = await query({ query: queryLocations, variables })
|
||||
expect(result.data.queryLocations).toEqual([
|
||||
{ id: 'place.14094307404564380', place_name: 'Berlin, Germany' },
|
||||
{ id: 'place.15095411613564380', place_name: 'Berlin, Maryland, United States' },
|
||||
{ id: 'place.5225018734564380', place_name: 'Berlin, Connecticut, United States' },
|
||||
{ id: 'place.16922023226564380', place_name: 'Berlin, New Jersey, United States' },
|
||||
{ id: 'place.4035845612564380', place_name: 'Berlin Township, New Jersey, United States' },
|
||||
])
|
||||
})
|
||||
|
||||
it('query Location existing in different language', async () => {
|
||||
variables = {
|
||||
place: 'Berlin',
|
||||
lang: 'de',
|
||||
}
|
||||
const result = await query({ query: queryLocations, variables })
|
||||
expect(result.data.queryLocations).toEqual([
|
||||
{ id: 'place.14094307404564380', place_name: 'Berlin, Deutschland' },
|
||||
{ id: 'place.15095411613564380', place_name: 'Berlin, Maryland, Vereinigte Staaten' },
|
||||
{ id: 'place.16922023226564380', place_name: 'Berlin, New Jersey, Vereinigte Staaten' },
|
||||
{ id: 'place.10735893248465990', place_name: 'Berlin Heights, Ohio, Vereinigte Staaten' },
|
||||
{ id: 'place.1165756679564380', place_name: 'Berlin, Massachusetts, Vereinigte Staaten' },
|
||||
])
|
||||
})
|
||||
|
||||
it('query Location not existing', async () => {
|
||||
variables = {
|
||||
place: 'GbHtsd4sdHa',
|
||||
lang: 'en',
|
||||
}
|
||||
const result = await query({ query: queryLocations, variables })
|
||||
expect(result.data.queryLocations).toEqual([])
|
||||
})
|
||||
|
||||
it('query Location without a place name given', async () => {
|
||||
variables = {
|
||||
place: '',
|
||||
lang: 'en',
|
||||
}
|
||||
const result = await query({ query: queryLocations, variables })
|
||||
expect(result.data.queryLocations).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('userMiddleware', () => {
|
||||
describe('UpdateUser', () => {
|
||||
let user
|
||||
@ -95,7 +165,7 @@ describe('userMiddleware', () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
it('creates a Location node with localised city/state/country names', async () => {
|
||||
it('creates a Location node with localized city/state/country names', async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'updating-user',
|
||||
|
||||
@ -16,3 +16,13 @@ type Location {
|
||||
parent: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
}
|
||||
|
||||
# This is not smart - we need one location for everything - use the same type everywhere!
|
||||
type LocationMapBox {
|
||||
id: ID!
|
||||
place_name: String!
|
||||
}
|
||||
|
||||
type Query {
|
||||
queryLocations(place: String!, lang: String!): [LocationMapBox]!
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
|
||||
SENTRY_DSN_WEBAPP=
|
||||
COMMIT=
|
||||
PUBLIC_REGISTRATION=false
|
||||
|
||||
10
webapp/graphql/location.js
Normal file
10
webapp/graphql/location.js
Normal file
@ -0,0 +1,10 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const queryLocations = () => gql`
|
||||
query($place: String!, $lang: String!) {
|
||||
queryLocations(place: $place, lang: $lang) {
|
||||
place_name
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -11,6 +11,7 @@ describe('index.vue', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$i18n: { locale: () => 'en' },
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
@ -27,6 +28,40 @@ describe('index.vue', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
query: jest
|
||||
.fn()
|
||||
.mockRejectedValue({ message: 'Ouch!' })
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
queryLocations: [
|
||||
{
|
||||
place_name: 'Brazil',
|
||||
id: 'country.9531777110682710',
|
||||
__typename: 'LocationMapBox',
|
||||
},
|
||||
{
|
||||
place_name: 'United Kingdom',
|
||||
id: 'country.12405201072814600',
|
||||
__typename: 'LocationMapBox',
|
||||
},
|
||||
{
|
||||
place_name: 'Buenos Aires, Argentina',
|
||||
id: 'place.7159025980072860',
|
||||
__typename: 'LocationMapBox',
|
||||
},
|
||||
{
|
||||
place_name: 'Bandung, West Java, Indonesia',
|
||||
id: 'place.8224726664248590',
|
||||
__typename: 'LocationMapBox',
|
||||
},
|
||||
{
|
||||
place_name: 'Banten, Indonesia',
|
||||
id: 'region.11849645724544000',
|
||||
__typename: 'LocaLocationMapBoxtion2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
@ -93,9 +128,182 @@ describe('index.vue', () => {
|
||||
wrapper.find('#name').setValue('Peter')
|
||||
wrapper.find('.ds-form').trigger('submit')
|
||||
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
name: 'Peter',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a new slug and hitting submit', () => {
|
||||
it('calls updateUser mutation', () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.find('#slug').setValue('peter-der-lustige')
|
||||
wrapper.find('.ds-form').trigger('submit')
|
||||
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
slug: 'peter-der-lustige',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a new location and hitting submit', () => {
|
||||
it('calls updateUser mutation', async () => {
|
||||
const wrapper = Wrapper()
|
||||
wrapper.setData({
|
||||
cities: [
|
||||
{
|
||||
label: 'Berlin, Germany',
|
||||
value: 'Berlin, Germany',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
wrapper.find('.ds-select-option').trigger('click')
|
||||
wrapper.find('.ds-form').trigger('submit')
|
||||
|
||||
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
locationName: 'Berlin, Germany',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a new about and hitting submit', () => {
|
||||
it('calls updateUser mutation', () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.find('#about').setValue('I am Peter!111elf')
|
||||
wrapper.find('.ds-form').trigger('submit')
|
||||
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
about: 'I am Peter!111elf',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given new username, slug, location and about then hitting submit', () => {
|
||||
it('calls updateUser mutation', async () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.setData({
|
||||
cities: [
|
||||
{
|
||||
label: 'Berlin, Germany',
|
||||
value: 'Berlin, Germany',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
label: 'Hamburg, Germany',
|
||||
value: 'Hamburg, Germany',
|
||||
id: '2',
|
||||
},
|
||||
],
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
wrapper.find('#name').setValue('Peter')
|
||||
wrapper.find('#slug').setValue('peter-der-lustige')
|
||||
wrapper.findAll('.ds-select-option').at(1).trigger('click')
|
||||
wrapper.find('#about').setValue('I am Peter!111elf')
|
||||
wrapper.find('.ds-form').trigger('submit')
|
||||
|
||||
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
name: 'Peter',
|
||||
slug: 'peter-der-lustige',
|
||||
locationName: 'Hamburg, Germany',
|
||||
about: 'I am Peter!111elf',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given user input on location field', () => {
|
||||
it('calls queryLocations query', async () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
wrapper.find('#city').trigger('input')
|
||||
wrapper.find('#city').setValue('B')
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(mocks.$apollo.query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
place: 'B',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the dropdown', () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.find('#city').trigger('input')
|
||||
wrapper.find('#city').setValue('B')
|
||||
|
||||
expect(wrapper.find('.ds-select-dropdown').isVisible()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given no user input on location field', () => {
|
||||
it('cannot call queryLocations query', async () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
wrapper.find('#city').setValue('')
|
||||
wrapper.find('#city').trigger('input')
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(mocks.$apollo.query).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not show the dropdown', () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.find('#city').setValue('')
|
||||
wrapper.find('#city').trigger('input')
|
||||
|
||||
expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given user presses escape on location field', () => {
|
||||
it('closes the dropdown', () => {
|
||||
const wrapper = Wrapper()
|
||||
|
||||
wrapper.find('#city').setValue('B')
|
||||
wrapper.find('#city').trigger('input')
|
||||
|
||||
expect(wrapper.find('.ds-select-dropdown').isVisible()).toBe(true)
|
||||
|
||||
wrapper.find('#city').trigger('keyup.esc')
|
||||
|
||||
expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
/>
|
||||
<!-- eslint-enable vue/use-v-on-exact -->
|
||||
<ds-input
|
||||
id="bio"
|
||||
id="about"
|
||||
model="about"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
@ -41,17 +41,15 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { CancelToken } from 'axios'
|
||||
import UniqueSlugForm from '~/components/utils/UniqueSlugForm'
|
||||
import { updateUserMutation } from '~/graphql/User'
|
||||
import { queryLocations } from '~/graphql/location'
|
||||
|
||||
let timeout
|
||||
const mapboxToken = process.env.MAPBOX_TOKEN
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
axiosSource: null,
|
||||
cities: [],
|
||||
loadingData: false,
|
||||
loadingGeo: false,
|
||||
@ -123,51 +121,38 @@ export default {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => this.requestGeoData(value), 500)
|
||||
},
|
||||
processCityResults(res) {
|
||||
if (!res || !res.data || !res.data.features || !res.data.features.length) {
|
||||
processLocationsResult(places) {
|
||||
if (!places.length) {
|
||||
return []
|
||||
}
|
||||
const output = []
|
||||
res.data.features.forEach((item) => {
|
||||
output.push({
|
||||
label: item.place_name,
|
||||
value: item.place_name,
|
||||
id: item.id,
|
||||
const result = []
|
||||
places.forEach((place) => {
|
||||
result.push({
|
||||
label: place.place_name,
|
||||
value: place.place_name,
|
||||
id: place.id,
|
||||
})
|
||||
})
|
||||
|
||||
return output
|
||||
return result
|
||||
},
|
||||
requestGeoData(e) {
|
||||
if (this.axiosSource) {
|
||||
// cancel last request
|
||||
this.axiosSource.cancel()
|
||||
}
|
||||
|
||||
async requestGeoData(e) {
|
||||
const value = e.target ? e.target.value.trim() : ''
|
||||
if (value === '' || value.length < 3) {
|
||||
if (value === '') {
|
||||
this.cities = []
|
||||
return
|
||||
}
|
||||
this.loadingGeo = true
|
||||
this.axiosSource = CancelToken.source()
|
||||
|
||||
const place = encodeURIComponent(value)
|
||||
const lang = this.$i18n.locale()
|
||||
|
||||
this.$axios
|
||||
.get(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxToken}&types=region,place,country&language=${lang}`,
|
||||
{
|
||||
cancelToken: this.axiosSource.token,
|
||||
},
|
||||
)
|
||||
.then((res) => {
|
||||
this.cities = this.processCityResults(res)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadingGeo = false
|
||||
})
|
||||
const {
|
||||
data: { queryLocations: res },
|
||||
} = await this.$apollo.query({ query: queryLocations(), variables: { place, lang } })
|
||||
|
||||
this.cities = this.processLocationsResult(res)
|
||||
this.loadingGeo = false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user