Skip to content

Commit

Permalink
feat(import): rework bulk import to use gql api
Browse files Browse the repository at this point in the history
  • Loading branch information
Silthus committed Feb 11, 2024
1 parent d09167a commit f7a472c
Show file tree
Hide file tree
Showing 18 changed files with 590 additions and 483 deletions.
81 changes: 54 additions & 27 deletions src/__tests__/import.test.ts → src/__tests__/bulkImport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@ import {muuidToString} from "../utils/helpers.js";
import MutableAreaDataSource from "../model/MutableAreaDataSource.js";
import exampleImportData from './import-example.json' assert {type: 'json'};
import {AreaType} from "../db/AreaTypes.js";
import {BulkImportResult} from "../db/import/json/import-json";
import {BulkImportResultType} from "../db/BulkImportTypes.js";
import MutableClimbDataSource from "../model/MutableClimbDataSource.js";

describe('bulkImportAreas', () => {
const query = `
mutation bulkImportAreas($input: BulkImportInput!) {
bulkImportAreas(input: $input) {
addedAreas {
uuid
}
updatedAreas {
uuid
}
addedClimbs {
id
}
updatedClimbs {
id
}
}
`

describe('/import', () => {
const endpoint = '/import'
let server: ApolloServer
let user: muuid.MUUID
let userUuid: string
Expand All @@ -19,18 +37,21 @@ describe('/import', () => {
let testArea: AreaType

let areas: MutableAreaDataSource
let climbs: MutableClimbDataSource

beforeAll(async () => {
({server, inMemoryDB, app} = await setUpServer())
// Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format
// "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==".
user = muuid.mode('relaxed').v4()
userUuid = muuidToString(user)

areas = MutableAreaDataSource.getInstance()
climbs = MutableClimbDataSource.getInstance()
})

beforeEach(async () => {
await inMemoryDB.clear()
areas = MutableAreaDataSource.getInstance()
await areas.addCountry('usa')
testArea = await areas.addArea(user, "Test Area", null, "us")
})
Expand All @@ -43,63 +64,69 @@ describe('/import', () => {
it('should return 403 if no user', async () => {
const res = await queryAPI({
app,
endpoint,
body: exampleImportData
query,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
})
expect(res.status).toBe(403)
expect(res.status).toBe(400)
expect(res.text).toBe('Forbidden')
})

it('should return 403 if user is not an editor', async () => {
const res = await queryAPI({
app,
endpoint,
userUuid,
body: exampleImportData
query,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
})
expect(res.status).toBe(403)
expect(res.status).toBe(400)
expect(res.text).toBe('Forbidden')
})

it('should return 200 if user is an editor', async () => {
const res = await queryAPI({
app,
endpoint,
userUuid,
roles: ['editor'],
body: exampleImportData
query,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
})
expect(res.status).toBe(200)
})

it('should import data', async () => {
const res = await queryAPI({
app,
endpoint,
userUuid,
roles: ['editor'],
body: {
areas: [
...exampleImportData.areas,
{
id: testArea.metadata.area_id.toUUID().toString(),
areaName: "Updated Test Area",
}
],
},
query,
operationName: 'bulkImportAreas',
variables: {
input: {
areas: [
...exampleImportData.areas,
{
id: testArea.metadata.area_id.toUUID().toString(),
areaName: "Updated Test Area",
}
]
}
}
});
expect(res.status).toBe(200)

const result = res.body as BulkImportResult
expect(result.addedAreaIds.length).toBe(4)
const result = res.body as BulkImportResultType
expect(result.addedAreas.length).toBe(4)

const committedAreas = await Promise.all(result.addedAreaIds.map((areaId: string) => areas.findOneAreaByUUID(muuid.from(areaId))));
const committedAreas = await Promise.all(result.addedAreas.map((area) => areas.findOneAreaByUUID(area.metadata.area_id)));
expect(committedAreas.length).toBe(4);

const committedClimbs = await Promise.all(result.climbIds.map((id: string) => areas.findOneClimbByUUID(muuid.from(id))));
const committedClimbs = await Promise.all(result.addedOrUpdatedClimbs.map((climb) => climbs.findOneClimbByMUUID(climb._id)));
expect(committedClimbs.length).toBe(2);

const updatedAreas = await Promise.all(result.updatedAreaIds.map((areaId: any) => areas.findOneAreaByUUID(muuid.from(areaId))));
const updatedAreas = await Promise.all(result.updatedAreas.map((area) => areas.findOneAreaByUUID(area.metadata.area_id)));
expect(updatedAreas.length).toBe(1);
expect(updatedAreas[0].area_name).toBe("Updated Test Area");
})
Expand Down
100 changes: 25 additions & 75 deletions src/__tests__/import-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,117 +9,67 @@
"children": [
{
"areaName": "Indian Creek",
"description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument).",
"lng": -109.5724044642857,
"lat": 38.069429035714286,
"children": [
{
"areaName": "Supercrack Buttress",
"gradeContext": "US",
"description": "",
"lng": -109.54552,
"lat": 38.03635,
"bbox": [
-109.54609091005857,
38.03590033981814,
-109.54494908994141,
38.03679966018186
],
"climbs": [
{
"name": "The Key Flake",
"grade": "5.10",
"fa": "unknown",
"type": {
"disciplines": {
"trad": true
},
"safety": "UNSPECIFIED",
"metadata": {
"lnglat": {
"type": "Point",
"coordinates": [
-109.54552,
38.03635
]
},
"left_right_index": 1
},
"content": {
"description": "Cool off-width that requires off-width and face skills.",
"protection": "Anchors hidden up top. Need 80m to make it all the way down.",
"location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder."
}
"lng": -109.54552,
"lat": 38.03635,
"leftRightIndex": 1,
"description": "Cool off-width that requires off-width and face skills.",
"protection": "Anchors hidden up top. Need 80m to make it all the way down.",
"location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder."
},
{
"name": "Incredible Hand Crack",
"grade": "5.10",
"fa": "Rich Perch, John Bragg, Doug Snively, and Anne Tarver, 1978",
"type": {
"disciplines": {
"trad": true
},
"safety": "UNSPECIFIED",
"metadata": {
"lnglat": {
"type": "Point",
"coordinates": [
-109.54552,
38.03635
]
},
"left_right_index": 2
},
"content": {
"description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.",
"protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)",
"location": ""
},
"left_right_index": 2,
"description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.",
"protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)",
"pitches": [
{
"pitchNumber": 1,
"grade": "5.10",
"disciplines": {
"trad": true
},
"length": 100,
"boltsCount": 0,
"description": "A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality."
},
{
"pitchNumber": 2,
"grade": "5.9",
"disciplines": {
"trad": true
},
"length": 30,
"boltsCount": 0,
"description": "Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable."
}
]
}
],
"gradeContext": "US",
"metadata": {
"isBoulder": false,
"isDestination": false,
"leaf": true,
"lnglat": {
"type": "Point",
"coordinates": [
-109.54552,
38.03635
]
},
"bbox": [
-109.54609091005857,
38.03590033981814,
-109.54494908994141,
38.03679966018186
]
},
"content": {
"description": ""
}
}
],
"metadata": {
"lnglat": {
"type": "Point",
"coordinates": [
-109.5724044642857,
38.069429035714286
]
}
},
"content": {
"description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument)."
}
]
}
]
}
Expand Down
21 changes: 5 additions & 16 deletions src/auth/local-dev/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,20 @@
* This file is a mod of src/auth/middleware.ts and is used when starting the server via `yarn serve-dev`
* It bypasses the authentication for local development
*/
import muuid, { MUUID } from 'uuid-mongodb'
import { AuthUserType } from '../../types.js'
import { logger } from '../../logger.js'
import muuid, {MUUID} from 'uuid-mongodb'
import {AuthUserType} from '../../types.js'
import {logger} from '../../logger.js'

export const localDevBypassAuthContext = (() => {
const testUUID: MUUID = muuid.v4()

return async ({ req }): Promise<any> => {
return async ({req}): Promise<any> => {
const user: AuthUserType = {
roles: ['user_admin', 'org_admin', 'editor'],
uuid: testUUID,
isBuilder: false
}
logger.info(`The user.roles for this session is: ${user.roles.toString()}`)
return { user }
return {user}
}
})()

export const localDevBypassAuthMiddleware = async (req, res, next): Promise<any> => {
req.user = {
roles: ['user_admin', 'org_admin', 'editor'],
uuid: muuid.v4(),
isBuilder: false
}
req.userId = req.user.uuid
req.token = 'local-dev-bypass'
next()
}
25 changes: 6 additions & 19 deletions src/auth/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import muid from 'uuid-mongodb'
import { AuthUserType } from '../types.js'
import { verifyJWT } from './util.js'
import { logger } from '../logger.js'
import {AuthUserType} from '../types.js'
import {verifyJWT} from './util.js'
import {logger} from '../logger.js'

/**
* Create a middleware context for Apollo server
*/
export const createContext = async ({ req }): Promise<any> => {
export const createContext = async ({req}): Promise<any> => {
try {
return await validateTokenAndExtractUser(req)
} catch (e) {
Expand All @@ -15,21 +15,8 @@ export const createContext = async ({ req }): Promise<any> => {
}
}

export const authMiddleware = async (req, res, next): Promise<void> => {
try {
const { user, token } = await validateTokenAndExtractUser(req)
req.user = user
req.userId = user.uuid
req.token = token
next()
} catch (e) {
logger.error(`Can't verify JWT token ${e.toString() as string}`)
res.status(401).send('Unauthorized')
}
}

async function validateTokenAndExtractUser (req: Request): Promise<{ user: AuthUserType, token: string }> {
const { headers } = req
async function validateTokenAndExtractUser(req: Request): Promise<{ user: AuthUserType, token: string }> {
const {headers} = req
// eslint-disable-next-line @typescript-eslint/dot-notation
const authHeader = String(headers?.['authorization'] ?? '')
if (!authHeader.startsWith('Bearer ')) {
Expand Down
Loading

0 comments on commit f7a472c

Please sign in to comment.