Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zoobot/feature/data sources #126

Merged
merged 21 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,982 changes: 863 additions & 6,119 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const whitelist = [
'https://dev.waterthetrees.com',
'https://waterthetrees.com',
'https://www.waterthetrees.com',
'https://6d0e-2600-1700-ab10-b55f-5c38-8242-c3c0-5804.ngrok.io',
zoobot marked this conversation as resolved.
Show resolved Hide resolved
];

const options = {
Expand Down
8 changes: 7 additions & 1 deletion server/routes/csv/csv-queries.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { db } from '../../db/index.js';

export default async function getAllTreeDataByCity(cityName) {
export async function getAllTreeDataByCity(cityName) {
const query = 'SELECT * FROM treedata WHERE city = $1';
const values = [cityName];
const cityTreeData = await db.manyOrNone(query, values);
return cityTreeData;
}

export async function getAllTreeDataCities() {
const query = `SELECT * FROM treedata WHERE city NOT IN ('Alameda', 'San Francisco', 'Oakland')`;
const cityTreeData = await db.manyOrNone(query);
return cityTreeData;
}
19 changes: 12 additions & 7 deletions server/routes/csv/csv-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,32 @@ import fastcsv from 'fast-csv';
import fs from 'fs';
import express from 'express';
import AppError from '../../errors/AppError.js';

import getAllTreeDataByCity from './csv-queries.js';
import { getAllTreeDataByCity, getAllTreeDataCities } from './csv-queries.js';

const csvRouter = express.Router();

csvRouter.get('/', async (req, res) => {
const { city } = req.query;
const data = await getAllTreeDataByCity(city);
const data = city
? await getAllTreeDataByCity(city)
: await getAllTreeDataCities();

if (!data) {
if (!data || data === undefined || data.length === 0) {
throw new AppError(404, 'Failed to find any data.');
}

const jsonData = JSON.parse(JSON.stringify(data));
const cityPath = `server/csv-downloads/${city.toLowerCase()}.csv`;
const ws = fs.createWriteStream(cityPath);
const cityName = city
? city.toLowerCase().replaceAll(' ', '_')
zoobot marked this conversation as resolved.
Show resolved Hide resolved
: 'all-cities';
const csvPath = `server/csv-downloads/${cityName}.csv`;
const ws = fs.createWriteStream(csvPath);

fastcsv
.write(jsonData, { headers: true })
.on('finish', () => {
res.download(cityPath);
res.statusCode = 200;
res.download(csvPath);
})
.pipe(ws);
});
Expand Down
108 changes: 108 additions & 0 deletions server/routes/csv/csv.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import axios from 'axios';
import faker from 'faker';
import nock from 'nock';
// import csvSync from 'csv/lib/sync';

let axiosAPIClient;

beforeAll(() => {
const axiosConfig = {
baseURL: 'http://127.0.0.1:3002/api',
validateStatus: () => true,
};

axiosAPIClient = axios.create(axiosConfig);

// Only allow requests to localhost
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');
});

afterEach(() => {
// Remove all interceptors
nock.cleanAll();
});

afterAll(() => {
// Re-enable requests to all hosts
nock.enableNetConnect();
});

describe('/api/csv', () => {
describe('GET', () => {
describe('When the city exists', () => {
test('Then returns a 200 status code and a csv for specific city', async () => {
/** Arrange */
const body = {
idSourceName: faker.address.cityName(),
common: faker.animal.dog(),
scientific: faker.animal.cat(),
species: faker.animal.cat(),
city: faker.address.cityName(),
datePlanted: new Date(),
lat: Number(faker.address.latitude()),
lng: Number(faker.address.longitude()),
};

const {
data: { city },
} = await axiosAPIClient.post('/trees', body);

const cityName = city.toLowerCase().replaceAll(' ', '_');
/** Act */
const params = {
city,
};

const response = await axiosAPIClient.get('/csv', {
params,
});

/** Assert */
expect(response.status).toBe(200);
expect(response.headers['content-disposition']).toBe(
`attachment; filename="${cityName}.csv"`,
);
expect(response.headers['content-type']).toBe(
'text/csv; charset=UTF-8',
);
expect(response.headers['content-length']).toBe('0');
});
});

describe('When the city does not exist', () => {
test('Then returns a 404 status code', async () => {
/** Arrange */
const city = faker.address.cityName();
/** Act */
const params = {
city,
};

const response = await axiosAPIClient.get('/csv', {
params,
});
/** Assert */
expect(response.status).toBe(404);
});
});

describe('When no city is sent in query', () => {
test('Then returns a 200 and rest of cities', async () => {
/** Arrange */

/** Act */
const response = await axiosAPIClient.get('/csv');

/** Assert */
expect(response.status).toBe(200);
expect(response.headers['content-disposition']).toBe(
`attachment; filename="all-cities.csv"`,
);
expect(response.headers['content-type']).toBe(
'text/csv; charset=UTF-8',
);
});
});
});
});
32 changes: 29 additions & 3 deletions server/routes/shared-routes-utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
// function camelToSnakeCase(camelIn) {
// return camelIn.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
// }

function snakeToCamelCase(snakeIn) {
return snakeIn.replace(/([-_][a-z])/g, (group) =>
group.toUpperCase().replace('-', '').replace('_', ''),
);
}

export const convertObjectKeysToCamelCase = (obj) => {
const newObj = {};
for (const key in obj) {
newObj[snakeToCamelCase(key)] = obj[key];
}
return newObj;
};

function camelToSnakeCase(camelIn) {
return camelIn.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
return camelIn.replace(/[A-Z0-9]/g, (letter) => {
if (/[A-Z]/.test(letter)) {
return `_${letter.toLowerCase()}`;
} else if (/[0-9]/.test(letter)) {
return `_${letter}`;
} else {
return letter;
}
});
}

export default function convertObjectKeysToSnakeCase(obj) {
export const convertObjectKeysToSnakeCase = (obj) => {
const newObj = {};

// eslint-disable-next-line no-restricted-syntax
Expand All @@ -14,4 +40,4 @@ export default function convertObjectKeysToSnakeCase(obj) {
}
}
return newObj;
}
};
Loading