I’m fairly new to Airtable, and I’m unsure how to accomplish the query(ies) I’m performing in a simpler manner using Airtable commands.
The Base
I have an API with two tables: Classes and Students. The Classes table consists of classes and all the students enrolled in each class. The Students table consists of students and all the classes each student is enrolled in.
The Task
A user searches for a specific student on the front end. Using Airtable formulae, I need to query the Students table for the classes that student is taking. (NOTE: I need to return the class names, not the record IDs/codes for those classes, which are unintelligible for the user.) Using those class names, I then need to ask the Classes table for the students associated with each class, again returning the students’ actual names, not their record IDs/codes. Finally, I need to return the original student’s classes to the front end, along with the other student names associated to those classes. I would think I need to run a window function or something, but I’m unsure how to do that with Airtable.
For clarity, here is the code I’m using so far. I realize it’s very clunky, but I haven’t been able to find much documentation online.
router.get('/:name', async (req, res) => {
try {
// Retrieve submitted student
res.setHeader('Access-Control-Allow-Origin', '*');
const URL = 'http://api.airtable.com/v0/app8ZbcPx7dkpOnP0/students';
const query = '?filterByFormula=';
const filterBy = `SEARCH("${req.params.name}", {Name} )`;
const link = `${URL}${query}${filterBy}`;
const KEY = process.env.AIRTABLE_API_KEY;
const headers = {
headers: {
Authorization: `Bearer ${KEY}`
}
}
const { data } = await axios.get(`${link}`, headers);
const records = data.records;
console.log("Object by record:", records);
const classes = records[0].fields['Classes'];
console.log("Classes by record:", classes);
let classNames = [];
let studentIds = [];
let otherStudents = [];
let zippedData = [];
let resultData;
// Swap class codes for class names
for (let i = 0; i < classes.length; i++) {
base('Classes').find(classes[i], function (err, record) {
if (err) { console.error(err); return; }
classNames.push(record.fields.Name);
});
}
// Retrieve other student codes per class associated to chosen student
for (let i = 0; i < classes.length; i++) {
base('Classes').find(classes[i], function (err, record) {
if (err) { console.error(err); return; }
studentIds.push(record.fields['Students']);
});
}
// Associate other student codes to associated student names. Zip data to send to Reducer.
setTimeout(async function () {
for (let i = 0; i < studentIds.length; i++) {
let newTempArr = [];
for (let j = 0; j < studentIds[i].length; j++) {
base('Students').find(studentIds[i][j], function (err, record) {
if (err) { console.error(err); return; }
newTempArr.push(record.fields.Name);
});
}
otherStudents.push(newTempArr);
}
}, 500);
setTimeout(function () {
for (let i = 0; i < classNames.length; i++) {
let newTempArr = [];
newTempArr.push(classNames[i]);
for (let j = 0; j < otherStudents[i].length; j++) {
newTempArr.push(otherStudents[i][j] + ", ");
}
zippedData.push(newTempArr);
}
}, 750);
setTimeout(function () {
console.log("Class names: ", classNames);
console.log("Records of other students by class:", studentIds);
console.log("Records of other student names:", otherStudents);
console.log("Zipped array:", zippedData);
resultData = { zippedData: zippedData, name: req.params.name.toString(), loading: false };
console.log(resultData);
res.send(resultData);
}, 1000);
} catch (err) {
return res.send({ zippedData: [["Name/class do not exist in Airtable database", "Associated students do not exist in Airtable database"]], name: "Error", loading: false });
}})
I’m also having issues figuring out how to properly implement asynchronicity in Airtable, but I’ve posted my questions for that topic in another thread.
Even incomplete solutions will be very helpful, so that I can better understand which direction to take in order to cut down on the verbosity of this beast.
Thank you in advance for any insights!
-Justin
Man, do not try to learn or even just brush up on the intricacies of HTTPS and JavaScript from Airtable's docs. Their documentation is arguably the best in business but it wasn't written to be accessible to anyone not already dealing with a bajillion APIs on the regular. That's definitely a silly mistake for a no-code platform to make, but it is what it is.
Regarding your question, you didn't say where you got stuck. That request isn't working? Where is it throwing?
Either way, I'd suggest saving yourself the trouble by registering for a free account with either Autocode, Syncic, or both. Either can do what you want at a moments notice, hundreds of time per day, without approaching the limits of their free plans. That's obviously just a bandaid solution but it sounds like you're under plenty duress already so this might be the least nerve-wrecking route for you to take.
Related
I'm learning JavaScrit/node.JS and I made the code below, which checks for how many vehicles a player has stored in the database once he joins the server and spawns all them. The problem is that it only works for one vehicle right now (personalVehicles gets overwritten, I belive)
mp.events.add("playerReady", (player) => {
pool.getConnection((err, con) => {
if (err) console.log(err)
console.log()
con.query("SELECT vehicleID, vehicleModel, vehicleSpawnX, vehicleSpawnY, vehicleSpawnZ FROM vehicles WHERE vehicleType = ? AND vehicleOwner = ?", ["players", player.playerID], function (err, result) {
if (err) console.log(err)
for (i = 0; i < result.length; i++) {
vehicleModel = result[i].vehicleModel
player.personalVehicles = mp.vehicles.new(parseInt(vehicleModel), new mp.Vector3(result[i].vehicleSpawnX, result[i].vehicleSpawnY, result[i].vehicleSpawnZ))
}
});
con.release()
});
});
I've tried switching player.personalVehicles withplayer.personalVehicles.vehicle[i], player.personalVehicles.vehicle and player.personalVehicles.[i], but none of them worked.
I pretend to do something like this automatically:
player = {
personalVehicles = {
vehicle1 = xxxxxxx
vehicle2 = xxxxxxx
vehicle3 = xxxxxxx
and so on
}
}
I know it only works for one vehicle because when I try to destroy the player's vehicles once he leaves only one vehicle gets destroy (the last one in the database).
Thanks.
const obj = {};
you cannot do this obj['hits']['hits'] ='whyDosntWork'; due to obj['hist'] does not exists.
You need to do:
obj['hits'] = {}
and then obj['hits']['hits'] ='whyDosntWork';
And the same for the rest...
I cannot understand what do you want to do here:
obj['hits']['hits'][i]['_source.ActiveChannelReleases'][0]['ImageExports'][0]['Resolutions'][0]['Url']
But follow what I said before, you need to create each step the value you want. I can assume that you want an array in ´hits`
I'm writing a cloud function that uses request-promise and cheerio to scrape a website and then check that information against a user document.
I am not entirely familiar with Javascript and Cloud Functions.
I've come so far that I managed to extract the information I need and navigate to the user's document and compare the data. Now the last piece of this function is to give the user points for each matching data point, so I need to update a map inside the user document.
This function has to loop through all users and change their document if the data point matches. I'm not sure the way I've written the code is the most optimal in terms of performance and billing if the userbase gets huge... Any pointers to how I could minimize the impact on the task would be of great help, as im new with JS.
So this is the code:
exports.getV75Results = functions.pubsub.schedule('every 2 minutes').onRun(async (context) => {
let linkMap = new Map();
const url = `https://www.example.com`
const options = {
uri: url,
headers: { 'User-Agent': 'test' },
transform: (body) => cheerio.load(body)
}
await rp(options)
.then(($) => {
for(let i = 1; i <= 7; i++)
{
//Find player from game
const lopp1 = $(`#mainContentHolder > div > div.mainContentStyleTrot > div > div.panel-body > table:nth-child(1) > tbody > tr:nth-child(${i}) > td:nth-child(2) > span`).text()
const lopp1StrR1 = lopp1.replace("(", "");
const lopp1StrR2 = lopp1StrR1.replace(")", "");
const lopp1StrR3 = lopp1StrR2.replace(" ", "");
linkMap.set(i, lopp1StrR3.toUpperCase());
}
console.log(linkMap);
return linkMap;
}).then(async () => {
//Start lookup users
let usersRef = db.collection('fantasyfotball').doc('users');
usersRef.listCollections().then(collections => {
collections.forEach( collection => {
var user = collection.doc(collection.id);
let batch = new admin.firestore().batch();
user.get().then(function(doc) {
let json = doc.data();
//Look in users collection if players document exist
Object.keys(json).forEach((name) => {
if(name != null) {
//Document with users active fotball players
if(name == 'players') {
let i = 0;
Object.values(json[name]).forEach((value) => {
i++;
if(value.localeCompare(linkMap.get(i)) == 0) {
//Loop through user keys and find owned players if user has the correct player
Object.keys(json).forEach((map) => {
if(map != null)
{
//Document with a map of player owned fotball players, each respective player has a key = 'fotball player' and value = '[price, points]'
if(map == 'ownedplayers')
{
Object.entries(json[map]).forEach((players) => {
if(players[0].localeCompare(value) == 0) {
console.log(players[1][1]);
//Add points to respective player field
//PROBABLY NOT HOW TO CHANGE A DOCUMENT FILED, THIS DOESNT WORK..
players[1][1]++;
}
});
//EACH TIME THIS RUNS IT SAYS: "Cannot modify a WriteBatch that has been committed"
batch.update(user, {'ownedplayers': json[map]});
}
}
});
}
});
}
} else {
console.log('user does not have a playermode document.');
}
});
});
return batch.commit().then(function () {
console.log("Succesfully commited changes.");
return null;
});
});
});
}).catch((err) => {
return err;
});
});
The issues i get in the console are "Cannot modify a WriteBatch that has been committed." and I fail to modify and add points to the player field inside the users document.
This is the console:
This is the firestore document structure:
I'm completely stuck on this.. Feels like I've tried all different approaches, but I think i dont fully understand cloud functions and javascript, so i would gladly recive feedback and help on how to make this work.
Cheers,
Finally.... i managed to update the document successfully. I put the commit outside another ".then()". Thought I tried that, but yay I guess :P
}).then(() => {
return batch.commit().then(function () {
console.log("Succesfully commited changes.");
return null;
});
The problem now is that it commits every loop. I think the most optimal here would be to batch update ALL users before committing?
And again, is there a more optimal way to do this, in terms of minimizing the operation and impact? I'm afraid I go too deep with for loops instead of directly navigating to the document, but haven't found an easier way to do that.
Any thoughts?
So here's the problem. I have a REST API that handles a booking creation, however, before saving the booking inside mongo it validates if there is a clash with another booking.
exports.create = function(req, res) {
var new_type = new Model(req.body);
var newBooking = new_type._doc;
//check if the new booking clashes with existing bookings
validateBooking.bookingClash(newBooking, function(clash){
if(clash == null) // no clashes, therefore save new booking
{
new_type.save(function(err, type) {
if (err)
{
res.send(err); //error saving
}
else{
res.json(type); //return saved new booking
}
});
}
else //clash with booking
{
//respond with "clashDate"
}
});
};
Here you have the validation function to check if there is a clash with bookings on the same day:
exports.bookingClash = function (booking, clash) {
//find the bookings for the same court on the same day
var courtId = (booking.courtId).toString();
Model.find({courtId: courtId, date: booking.date}, function(err, bookings) {
if(err == null && bookings == null)
{
//no bookings found so no clashes
clash(null);
}
else //bookings found
{
//for each booking found, check if the booking start hour falls between other booking hours
for(var i = 0; i<bookings.length ; i++)
{
//here is where I check if the new booking clashes with bookings that are already in the DB
{
//the new booking clashes
//return booking date of the clash
clash(clashDate); //return the clashDate in order to tell the front-end
return;
}
}
//if no clashes with bookings, return null
clash(null);
}
});
};
So, ALL of this works with one single new booking. However, now I want to be able to handle a recursive booking (booking that is made weekly). I have recreated the "create" function and call the validateBooking.bookingClash function inside a for loop.
Unfortunately, when I run this, it calls the bookingClash function perfectly, but when it reaches the line making the search in the database:
Model.find({courtId: courtId, date: booking.date}, function(err, bookings)
It does not wait for the callback and before handling the response "clash", makes i++ and continues.
How can I make it work and wait for the callback?
var array = req.body;
var clashes = [];
for(var i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}
Seems like a basic async call problem, for loops do not wait for callbacks to be called.
You could use async 'series' function for exmaple instead of the for loop. This way each find will get called after the previous one.
Mongoose also has a promise based syntax which can help you : http://mongoosejs.com/docs/promises.html
You Can use async eachSeries
async.eachSeries(users, function iterator(user, callback) {
if(something) {
//thing you want to do
callback();
} else {
callback();
}
}
Since you are using callback functions there are two ways you could try to solve this:
1) use some external library that allows you to perform an asynchronous map operation and run all the checks for each clash. Once they are done check the combined results for a clash and proceed accordingly
I would suggest using the async library
your code would look something like:
async.map(array,(entry,callback) => validateBooking.bookingClash(entry,callback),(error,mappingResults)=>{...})
2) you could try to change this function to a recursive one
`function recursiveValidation(arrayToCheck,mainCallback){
if(arrayToCheck.length === 0) {
return cb(null} // end of array without errors
}
validateBooking.bookingClash(_.head(arrayToCheck), function(clash)
{
if(clash)
{
return mainCallback(clash);
}
return recursiveValidation(_.tail(arrayToCheck),mainCallback);
}
}`
The above code is just a mockup but it should show the point.
The _ is lodash
No need to changing anything in your code except the declaration use let instead of var and your loop should work.
var array = req.body;
var clashes = [];
`
for(**let** i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}`
You have to understand the difference between let and var. Also why var cannot be used for running async code inside a loop.
Learn about let: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
I found the way to get this done after trying all of your answers.
What I had to do was this:
validateBooking.singleBooking(new_type._doc, newBookingClubId, function (clash) {
if (clash == null) // no clash
{
validatorArray.push(0);
if(validatorArray.length == array.length) //has received everything from mongo
{
console.log("Clashes: " + clashes.toString());
if(validatorArray.indexOf(1) > -1) //contains a clash
{
var error = {
code: 409,
message: "409 Conflict",
clashes: clashes
};
errorsHandler.handleError(error, res);
}
This way, I created an array called "validatorArray" that was called every time I received something back from Mongo.
This way I could easily compare the length of the array of bookings and the validatorArray length. When they were equal, it meant that it had received everything back from mongo and could send back the response.
Thanks for the help!
So I am using adonis to build a very basic back-end for an image hosting site such as reddit or imgur. I am having an issue with one of my controllers while trying to give users the ability to vote on posts, but limiting each user to vote only once per post.
this is my controller class file:
'use strict'
const Vote = use('App/Model/Vote')
const Post = use('App/Model/Post')
class VoteController {
* create (request, response) {
let currentUser = request.authUser
let postId = request.param('post_id')
let data = {}
data.user_id = currentUser.id
data.post_id = postId
let voteCheck = yield Vote.query()
.where({'post_id': postId,
'user_id': currentUser.id })
console.log(voteCheck)
if (voteCheck) {
response.status(401).json({error: "User can only cast 1 vote per post."})
} else {
let addVote = yield Vote.create(data)
yield addVote.save();
let post = yield Post.findBy('id', data.post_id)
let votes = post.vote_count;
if (votes === null ) {
votes = 1;
} else {
votes++;
}
post.vote_count = votes;
yield post.save()
response.status(202).json([post, addVote]);
}
}
}
module.exports = VoteController
I have been playing around with different combinations of conditionals. My main issue is with the variable 'voteCheck'. Essentially if a user votes, it creates a new column in my 'votes' table that stores the authUser's id and the post's id. I have a query before my conditional that searches for a pre-existing vote matching that criteria, and if it exists, throws an error. If not (else) it creates a new vote and adds 1 to the posts 'vote_count'. The latter is working just fine but I believe there is something wrong with my conditional logic as I am currently getting an error even for users who have not yet voted. Any suggestions? Thanks a bunch for reading!
You forget to execute the query chain. Put .fetch() at the end of your query.
let voteCheck = yield Vote.query()
.where({'post_id': postId,
'user_id': currentUser.id })
.fetch()
I am looking for a simple strategy to store user data, as well as messages. I was thinking of using different key values like some random token (Ynjk_nkjSNKJN) for users and some real ids (1,2,3) for messages.
Has anyone ever had that problem?
The reason is that I would like to keep localStorage always up to date with new messages from the server, but users should not be deleted during an update.
Thanks
You can handle "tables" in localStorage this way:
//columns should be an array of column literals
function createTable(tableName, columns) {
db[tableName] = {rows: {}, columns: columns};
}
function insertInto(tableName, row, id) {
var newRow = {};
for (var columnName in row) {
if (db[tableName].columns.indexOf(columnName) === -1) {
//invalid column
return false;
}
newRow[columnName] = row[columnName];
}
db[tableName].rows[id] = newRow;
return true;
}
function getIDs(tableName, where) {
var IDs = [];
for (var id in db[tableName].rows) {
if (where(db[tableName].rows[id])) {
IDs[IDs.length]=id;
}
}
return IDs;
}
function update(tableName, where, what) {
what(tableName, getIDs(tableName, where));
}
function deleteRecord(tableName, where) {
var removeIDs = getIDs(tableName, where);
for (var id in removeIDs) {
//Could be done by regexes, but I am not fluent with them and I am lazy to check them out
delete db[tableName].rows[removeIDs[id]];
}
}
function select(tableName, where) {
var IDs = getIDs(tableName, where);
var result = {};
for (var id in db[tableName].rows) {
result[id] = db[tableName].rows[id];
}
return result;
}
function dropTable(tableName) {
delete db[tableName];
}
You probably see that this is only a minimalistic implementation, but with the same approach you can implement altering, joins, grouping and so on. My focus here was just to illustrate how you can create a database. Let's go to the next step, storing the database into localStorage:
localStorage.setItem("db", JSON.stringify(db));
You will need to be able to convert back the local storage item to object, especially because you want to reuse your database even after reload. Let's see how you should initialize db:
var db = !!localStorage.getItem("db") ? angular.fromJson(localStorage.getItem("db")) : {};
Localstorage is a key-value store, which stores everything in string format. So, the messages will be identified by one key (e.g. "messages") and the users another key (e.g. "users").
Then you need to create 2 (angular) services one for the messages and one for the users. Both will interface with localstorage (using the respective keys) and will perform the operations that you want.
If you provide us with more information then we could help you a bit more.