const graphql = require("graphql"); const sqlite3 = require('sqlite3').verbose(); const database = new sqlite3.Database("whiskies.db"); const createWhiskiesTable = () => { const query = ` CREATE TABLE IF NOT EXISTS Whiskies ( code text UNIQUE PRIMARY KEY, description text, lastSeen text, size integer, price real )`; return database.run(query); } const createLocationsTable = () => { const query = ` CREATE TABLE IF NOT EXISTS Locations ( code text UNIQUE PRIMARY KEY, city text, address text, latitude real, longitude real, phone text )`; return database.run(query); } const createInventoriesTable = () => { const query = ` CREATE TABLE IF NOT EXISTS Inventories ( id integer PRIMARY KEY, whisky integer, location integer, quantity integer, updated text )`; return database.run(query); } createWhiskiesTable(); createInventoriesTable(); createLocationsTable(); const WhiskyType = new graphql.GraphQLObjectType({ name: "Whisky", fields: () => ({ code: { type: graphql.GraphQLString }, description: { type: graphql.GraphQLString }, lastSeen: { type: graphql.GraphQLString }, size: { type: graphql.GraphQLInt }, price: { type: graphql.GraphQLFloat }, inventories: { type: graphql.GraphQLList(InventoryType) }, quantity: { type: graphql.GraphQLInt }, updated: { type: graphql.GraphQLString } }) }); const LocationType = new graphql.GraphQLObjectType({ name: "Location", fields: () => ({ code: { type: graphql.GraphQLString }, address: { type: graphql.GraphQLString }, city: { type: graphql.GraphQLString }, phone: { type: graphql.GraphQLString }, longitude: { type: graphql.GraphQLFloat }, latitude: { type: graphql.GraphQLFloat }, inventories: { type: graphql.GraphQLList(InventoryType) } }) }); const InventoryType = new graphql.GraphQLObjectType({ name: "Inventory", fields: { id: { type: graphql.GraphQLID }, location: { type: LocationType }, whisky: { type: WhiskyType }, quantity: { type: graphql.GraphQLInt }, updated: { type: graphql.GraphQLString } } }); const buildWhiskies = (rows) => { const whiskies = {}; rows.forEach(row => { let whisky = {}; if (!(row.code in whiskies)) { whisky = { description: row.description, price: row.price, size: row.size, code: row.code, inventories: [], quantity: 0, updated: 0 } whiskies[row.code] = whisky; } else { whisky = whiskies[row.code]; } if (row.quantity) { whisky.inventories.push({ location: { code: row.location, city: row.city, address: row.address, phone: row.phone, latitude: row.latitude, longitude: row.longitude }, quantity: row.quantity, updated: row.updated }); whisky.updated = (new Date(row.updated) > new Date(whisky.updated)) ? row.updated : whisky.updated; whisky.quantity += row.quantity; } }); const results = []; for (let key in whiskies) { results.push(whiskies[key]); } return results; }; const buildLocations = (rows) => { const locations = {}; rows.forEach(row => { let location = {}; if (!(row.code in locations)) { location = { code: row.code, address: row.address, city: row.city, phone: row.phone, latitude: row.latitude, longitude: row.longitude, inventories: [] }; locations[row.code] = location; } else { location = locations[row.code]; } if (row.quantity) { location.inventories.push({ whisky: { code: row.whisky, description: row.description, price: row.price, size: row.size, }, quantity: row.quantity, updated: row.updated }); } }); const results = []; for (let key in locations) { results.push(locations[key]); } return results; }; const buildInventories = (rows) => { const inventories = []; rows.forEach(row => { const inventory = { whisky: { code: row.whisky, description: row.description, price: row.price, size: row.size }, location: { code: row.location, address: row.address, city: row.city, phone: row.phone, latitude: row.latitude, longitude: row.longitude }, quantity: row.quantity, updated: row.updated }; inventories.push(inventory); }); return inventories; }; var queryType = new graphql.GraphQLObjectType({ name: 'Query', fields: { Whiskies: { type: graphql.GraphQLList(WhiskyType), args: { code: { type: graphql.GraphQLString }, }, resolve: (root, {code}, context, info) => { return new Promise((resolve, reject) => { if (code && code.trim() != "") { code = `${code.replace(/%/g, '')}%`; } database.all( "SELECT w.*,i.quantity,i.updated " + "FROM Whiskies AS w " + "LEFT JOIN Inventories AS i ON w.code=i.whisky " + (code ? "WHERE w.code LIKE (?) OR w.description LIKE (?)" : "") + ";", [code, code], function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildWhiskies(rows)); }); }); } }, Whisky: { type: WhiskyType, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {code}, context, info) => { return new Promise((resolve, reject) => { database.all( "SELECT w.*,l.code AS 'location',l.address,l.city,l.phone,l.latitude,l.longitude,i.quantity,i.updated " + "FROM Whiskies AS w " + "LEFT JOIN Inventories AS i ON w.code=i.whisky " + "LEFT JOIN Locations AS l ON l.code=i.location " + "WHERE w.code = (?);", [code], function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildWhiskies(rows)[0]); }); }); } }, Locations: { type: graphql.GraphQLList(LocationType), resolve: (root, args, context, info) => { console.log(`Locations resolve`); return new Promise((resolve, reject) => { database.all( "SELECT l.*,w.code AS 'whisky',w.description,w.price,w.size,i.quantity,i.updated " + "FROM Locations AS l " + "LEFT JOIN Inventories AS i ON l.code=i.location " + "LEFT JOIN Whiskies AS w ON w.code=i.whisky " + ";", function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildLocations(rows)); }); }); } }, Location: { type: LocationType, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {code}, context, info) => { console.log(`Location resolve`); return new Promise((resolve, reject) => { database.all( "SELECT l.*,w.code AS 'whisky',w.description,w.price,w.size,i.quantity,i.updated " + "FROM Locations AS l " + "INNER JOIN Inventories AS i ON l.code=i.location " + "INNER JOIN Whiskies AS w ON w.code=i.whisky " + "WHERE code = (?);", [code], function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildLocations(rows)[0]); }); }); } }, Inventories: { type: graphql.GraphQLList(InventoryType), resolve: (root, args, context, info) => { console.log(`Inventories resolve`); return new Promise((resolve, reject) => { database.all( "SELECT * FROM Inventories AS i " + "INNER JOIN Whiskies AS w ON w.code=i.whisky " + "INNER JOIN Locations AS l ON l.code=i.location;", function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildInventories(rows)); }); }); } }, Inventory: { type: InventoryType, args: { location: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, whisky: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, }, resolve: (root, {location, whisky}, context, info) => { console.log(`Inventory resolve`, root, id, context, info); return new Promise((resolve, reject) => { database.all( "SELECT * FROM Inventories AS i " + "INNER JOIN Whiskies AS w ON w.code=i.whisky " + "INNER JOIN Locations AS l ON l.code=i.location " + "WHERE i.whisky = (?) AND i.location = (?);", [whisky, location], function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildInventories(rows)[0]); }); }); } } } }); var mutationType = new graphql.GraphQLObjectType({ name: 'Mutation', fields: { createWhisky: { type: WhiskyType, args: { description: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, lastSeen: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, size: { type: new graphql.GraphQLNonNull(graphql.GraphQLInt) }, price: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) } }, resolve: (root, {description, code, lastSeen, size, price}) => { return new Promise((resolve, reject) => { database.run('INSERT INTO Whiskies (description, code, lastSeen, size, price) VALUES (?,?,?,?,?);', [description, code, lastSeen, size, price], (err) => { if (err) { console.error(err); return reject(null); } database.get("SELECT last_insert_rowid() AS id", (_err, row) => { if (_err) { console.error(_err); return reject(null); } resolve({ description, code, lastSeen, size, price }); }); }); }) } }, updateWhisky: { type: graphql.GraphQLString, args: { description: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, lastSeen: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, size: { type: new graphql.GraphQLNonNull(graphql.GraphQLInt) }, price: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) } }, resolve: (root, {code, description, lastSeen, size, price}) => { return new Promise((resolve, reject) => { database.run('UPDATE Whiskies SET description = (?), lastSeen = (?), size = (?), price = (?) WHERE code = (?);', [description, lastSeen, size, price, code], (err) => { if (err) { console.error(err); return reject(err); } resolve(`Whisky #${code} updated`); }); }) } }, deleteWhisky: { type: graphql.GraphQLString, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {code}) => { return new Promise((resolve, reject) => { database.run('DELETE from Whiskies WHERE code =(?);', [code], (err) => { if (err) { console.error(err); return reject(err); } resolve(`Whisky #${code} deleted`); }); }) } }, createLocation: { type: LocationType, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, address: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, city: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, phone: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, longitude: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) }, latitude: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) } }, resolve: (root, {code, address, city, phone, longitude, latitude}) => { return new Promise((resolve, reject) => { database.run('INSERT INTO Locations (code, address, city, phone, longitude, latitude) VALUES (?,?,?,?,?,?);', [code, address, city, phone, longitude, latitude], (err) => { if (err) { console.error(err); return reject(null); } resolve({ code, address, city, phone, longitude, latitude }); }); }) } }, updateLocation: { type: graphql.GraphQLString, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, address: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, city: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, phone: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, longitude: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) }, latitude: { type: new graphql.GraphQLNonNull(graphql.GraphQLFloat) } }, resolve: (root, {code, address, city, phone, longitude, latitude}) => { return new Promise((resolve, reject) => { database.run('UPDATE Locations SET address = (?), city = (?), phone = (?), longitude = (?), latitude = (?) WHERE code = (?);', [address, city, phone, longitude, latitude, code], (err) => { if (err) { console.error(err); return reject(null); } resolve(`Location #${code} updated`); }); }) } }, deleteLocation: { type: graphql.GraphQLString, args: { code: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {code}) => { return new Promise((resolve, reject) => { database.run('DELETE from Locations WHERE code = (?);', [code], (err) => { if (err) { console.error(err); return reject(null); } resolve(`Location #${code} deleted`); }); }) } }, createInventory: { type: InventoryType, args: { location: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, whisky: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, quantity: { type: new graphql.GraphQLNonNull(graphql.GraphQLInt) }, updated: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {location, whisky, quantity, updated}) => { console.log('createInventory'); return new Promise((resolve, reject) => { database.run('INSERT INTO Inventories (location, whisky, quantity, updated) VALUES (?,?,?,?);', [location, whisky, quantity, updated], (err) => { if (err) { console.error(err); return reject(null); } database.get("SELECT last_insert_rowid() as id", (_err, row) => { if (_err) { console.error(_err); return reject(null); } database.all( "SELECT * FROM Inventories AS i " + "INNER JOIN Whiskies AS w ON w.code=i.whisky " + "INNER JOIN Locations AS l ON l.code=i.location " + "WHERE i.whisky = (?) AND i.location = (?);", [whisky, location], function(err, rows) { if (err) { console.error(err); return reject(null); } resolve(buildInventories(rows)[0]); }); }); }); }) } }, updateInventory: { type: graphql.GraphQLString, args: { location: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, whisky: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, quantity: { type: new graphql.GraphQLNonNull(graphql.GraphQLInt) }, updated: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) } }, resolve: (root, {location, whisky, quantity, updated}) => { console.log('updateInventory'); return new Promise((resolve, reject) => { database.run('UPDATE Inventories SET quantity = (?), updated = (?) WHERE location = (?) AND whisky = (?);', [ quantity, updated, location, whisky], (err) => { if (err) { console.error(err); return reject(null); } resolve(`Inventory for ${location} of ${whisky} updated to ${quantity}:${updated}`); }); }) } }, deleteInventory: { type: graphql.GraphQLString, args: { location: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, whisky: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }, }, resolve: (root, {location, whisky}) => { console.log('deleteInventory'); return new Promise((resolve, reject) => { database.run('DELETE from Inventories WHERE location = (?) AND whisky = (?);', [location, whisky], (err) => { if (err) { console.error(err); return reject(err); } resolve(`Deleted inventory for ${location} of ${whisky}`); }); }) } } } }); const schema = new graphql.GraphQLSchema({ query: queryType, mutation: mutationType }); module.exports = { schema }