Repository & Pull Request
https://github.com/knacksteem/knacksteem-api
https://github.com/knacksteem/knacksteem-api/pull/3
What is KnackSteem?
"Do you have any talent? If yes! then KnackSteem is for you."
"Rewards people with talents, it can be any talent, anything you know how to do best is highly welcome on the platform. "
Source: Discord Channel :D
Changes Made
Setup SteemConnect in backend
I wrote the code to set up a SteemConnect instance in the backend to validate incoming access token from the client side.
Related code:
const sc2 = require('sc2-sdk');
const config = require('./vars');
const api = sc2.Initialize({
app: config.sc2_app, // SC2 App identifier here
callbackURL: config.sc2_callback, // not really needed
});
module.exports = api;
Basically, it creates an instance of SteemConnect and makes it available to use within the project.
SteemConnect middleware
Since some actions require the user to be logged in but we didn't want to store their token, I decide to create this middleware to check if the provided access token is correct. It will basically initialize an instance of SteemConnect with the provided token and will try to query its profile. If there is a response, it will move to the next middleware. Otherwise, will throw an error.
Related code:
const sc2 = require('../../config/steemconnect');
const httpStatus = require('http-status');
/**
* SC2 Validator. Validate if the provided access token is valid.
* @author Jayser Mendez.
* @public
*/
const validateSc2 = async (req, res, next) => {
try {
// Set the access token to the sc2 instance
sc2.setAccessToken(req.body.access_token);
// Call the sc2 api to validate the token.
const sc2Res = await sc2.me();
// Declare a local variable with the username.
res.locals.username = sc2Res.user;
// Declare a local variable with the user object.
res.locals.userObject = sc2Res;
// Move to the next middleware and pass username along.
return next();
// Catch the error if any.
} catch (err) {
// Send an error message in the response.
return next({
status: httpStatus.UNAUTHORIZED,
message: 'Unauthorized access',
});
}
};
module.exports = validateSc2;
As explained before, it will validate the token and also, will make available two global variables including the username and the user object for the next middleware.
User exist middleware
Before introducing a new post in the database, it should check if the provided username exists (taking in mind that it already pass the SteemConnect middleware). If the user does not exist, the backend will create a user before proceeding to insert the post at the database.
Related code:
const User = require('../models/user.model');
/**
* Username checker. Check if username exists in database, if not, create it.
* @author Jayser Mendez.
* @public
*/
const userExist = async (req, res, next) => {
try {
// Try to find the username in database.
const user = await User.findOne({ username: res.locals.username });
// If the username is the same as the one from locals, pass to next middleware.
if (user) {
return next();
}
// Otherwise, user does not exist, proceed to create it.
// Create a new user object with the required data.
const newUser = new User({
username: res.locals.username,
user: res.locals.userObject,
});
// Insert the new username in database.
User.create(newUser);
// Pass to the next middleware
return next();
// Catch any possible error.
} catch (err) {
// Catch errors here.
return next;
}
};
module.exports = userExist;
Created Post Schema (To be Edited)
I created the post schema (not a final version) in order to be able to test the endpoint. This schema has basic fields that are suitable for faster tests.
Code related:
const mongoose = require('mongoose');
/**
* Post Schema
* @private
*/
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: true,
},
tags: {
type: Array,
required: false,
},
author: {
type: String,
required: true,
trim: true,
lowercase: true,
},
}, {
timestamps: true,
});
// Declare index for createdAt property
postSchema.index({ createdAt: -1 });
/**
* @typedef Post
*/
module.exports = mongoose.model('Post', postSchema);
Endpoints to get/create posts
First, I created a function to avoid rewriting the same code over and over. This function receives 4 parameters which can be used to determine what field will be used to make the query, how will it be sorted, how much posts will pull, and how many posts will it skips (useful for pagination).
Related code:
/**
* Get posts from database based on criteria and sorting.
* @param {Object} criteria: Fields to project from database
* @param {Object} sort: Sorting strategy
* @param {Number} limit: how much posts will be pulled
* @param {Number} skip: how much posts will be skiped (useful for pagination)
* @author Jayser Mendez
* @private
* @returns an array with the posts from database response
*/
const getPosts = async (criteria, sort, limit, skip) => {
try {
limit = parseInt(limit, 10);
skip = parseInt(skip, 10);
const postsList = await Post.find(criteria).sort(sort).limit(limit || 25).skip(skip || 0);
return {
results: postsList,
count: postsList.length,
status: 200,
};
} catch (err) {
return err;
}
};
Then, I created a function to pull all the posts in descending order from the database:
/**
* Get all posts from database
* @author Jayser Mendez
* @public
*/
exports.getAllPosts = async (req, res, next) => {
try {
// Query the posts from database in a descending order.
const { limit, skip } = req.query;
const sort = { createdAt: -1 };
const postsList = await getPosts({}, sort, limit, skip);
// Send the posts to the client in a formatted JSON.
return res.send(postsList);
// Catch any possible error
} catch (err) {
return next(err);
}
};
And finally, a function to get the posts from a specific user:
/**
* Get all from a specific user from database
* @author Jayser Mendez
* @public
*/
exports.getPostsByAuthor = async (req, res, next) => {
try {
// Query the posts from database in a descending order.
const { limit, skip } = req.query;
const author = { author: req.query.username };
const sort = { createdAt: -1 };
const postsList = await getPosts(author, sort, limit, skip);
// Send the posts to the client in a formatted JSON.
return res.send(postsList);
// Catch any possible error
} catch (err) {
return next(err);
}
};
And to create a post, I made a function that will use the parameters from the middleware (if the access token is valid of course). When this function is reached, it will insert the post in the database with the provided fields.
/**
* Insert a new post into database
* @author Jayser Mendez
* @public
*/
exports.createPost = async (req, res, next) => {
try {
// Initialize a new object with post data
const newPost = new Post({
title: req.body.title,
description: req.body.description,
author: res.locals.username,
tags: req.body.tags || [],
});
// Insert the post into database.
Post.create(newPost);
res.send({
status: 200,
message: 'Post created correctly',
});
// Move to the next middleware
return next();
// If any error, catch it
} catch (error) {
return next(error);
}
};
What is next?
Next, I will create endpoints for the post master-details data and will create functions to parse post data from blockchain to propagate the data before sending it to the client side.