🚀 Initial commit for rzmk/smart-brain-api!

This commit is contained in:
rzmk 2022-01-01 09:45:39 -05:00
commit 16e22cda96
10 changed files with 4048 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

28
README.md Normal file
View file

@ -0,0 +1,28 @@
<div align="center">
<a href="https://github.com/rzmk/smart-brain-api">
<img src="server.svg" alt="Server" width="80" height="80">
</a>
<h3 align="center">smart-brain-api</h3>
Backend server for <a href="https://github.com/rzmk/smart-brain"> rzmk/smart-brain</a>.
<br />
</div>
## Endpoints
- `/signin`: Sign in verification with email and password.
- `/register`: Register with name, email, and password.
- `/profile/:id`: Get user profile (once logged in).
- `/image`: Side effects of image detection (increment entries count).
- `/imageurl`: Image URL upload handler ([Clarifai API](https://www.clarifai.com/) call).
## Stack
- [Node.js](https://nodejs.org/) (Javascript runtime)
- [Express](https://expressjs.com/) (web app framework)
- [PostgreSQL](https://www.postgresql.org/) (database)
- [Heroku](https://www.heroku.com/) (cloud hosting)
- [Knex.js](https://knexjs.org/) (SQL query builder)
## Acknowledgements
- [Zero to Mastery Academy](https://academy.zerotomastery.io/p/complete-web-developer-zero-to-mastery)

29
controllers/image.js Normal file
View file

@ -0,0 +1,29 @@
const Clarifai = require("clarifai"); // Face detection model API
const app = new Clarifai.App({
apiKey: process.env.API_CLARIFAI,
});
const handleApiCall = (req, res) => {
app.models
.predict(Clarifai.FACE_DETECT_MODEL, req.body.input)
.then((data) => {
res.json(data);
})
.catch((err) => res.status(400).json("Unable to work with API."));
};
const handleImage = (req, res, db) => {
const { id } = req.body;
db("users")
.where("id", "=", id)
.increment("entries", 1) // "entries" is the total number of image uploads the user has made
.returning("entries")
.then((entries) => res.json(entries[0]))
.catch((err) => res.status(400).json("Unable to get entries."));
};
module.exports = {
handleImage: handleImage,
handleApiCall: handleApiCall,
};

20
controllers/profile.js Normal file
View file

@ -0,0 +1,20 @@
const handleProfileGet = (req, res, db) => {
const { id } = req.params;
db.select("*")
.from("users")
.where({
id: id,
})
.then((user) => {
if (user.length) {
res.json(user[0]);
} else {
res.status(400).json("Not found.");
}
})
.catch((err) => res.status(400).json("Error getting user."));
};
module.exports = {
handleProfileGet: handleProfileGet,
};

43
controllers/register.js Normal file
View file

@ -0,0 +1,43 @@
const handleRegister = (req, res, db, bcrypt) => {
const { name, email, password } = req.body;
// Validate the input to not be empty
if (!email || !name || !password) {
return res.status(400).json("Incorrect form submission.");
}
// Hash (encrypt) the password
const hash = bcrypt.hashSync(password);
/*
* Database transaction to make sure all the queries are executed together
* First query is to insert the user into the login table
* Second query is to insert the user into the users table
* Then we return the user
*/
db.transaction((trx) => {
trx.insert({
hash: hash,
email: email,
})
.into("login")
.returning("email")
.then((loginEmail) => {
return trx("users")
.returning("*")
.insert({
email: loginEmail[0],
name: name,
joined: new Date(),
})
.then((user) => {
res.json(user[0]);
})
.then(trx.commit);
})
.catch(trx.rollback);
}).catch((err) => res.status(400).json("Unable to register."));
};
module.exports = {
handleRegister: handleRegister,
};

34
controllers/signin.js Normal file
View file

@ -0,0 +1,34 @@
const handleSignIn = (req, res, db, bcrypt) => {
const { email, password } = req.body;
// Validate the input to not be empty
if (!email || !password) {
return res.status(400).json("Incorrect form submission.");
}
// Check if the user exists in the database
db.select("email", "hash")
.from("login")
.where("email", "=", email)
.then((data) => {
const isValid = bcrypt.compareSync(password, data[0].hash);
// If the user exists and the password is correct, return the user
if (isValid) {
return db
.select("*")
.from("users")
.where("email", "=", email)
.then((user) => {
res.json(user[0]);
})
.catch((err) =>
res.status(400).json("Unable to get user.")
);
} else {
res.status(400).json("Invalid credentials.");
}
})
.catch((err) => res.status(400).json("Invalid credentials."));
};
module.exports = {
handleSignIn: handleSignIn,
};

3816
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "smart-brain-api",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"start:dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt-nodejs": "^0.0.3",
"clarifai": "^2.9.1",
"cors": "^2.8.5",
"express": "^4.17.2",
"knex": "^0.95.15",
"pg": "^8.7.1"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}

52
server.js Normal file
View file

@ -0,0 +1,52 @@
const express = require("express");
const bcrypt = require("bcrypt-nodejs"); // bcrypt-nodejs is a module that we can use to hash passwords
const cors = require("cors");
const knex = require("knex"); // knex is a database library
// Import endpoint controllers
const register = require("./controllers/register");
const signin = require("./controllers/signin");
const profile = require("./controllers/profile");
const image = require("./controllers/image");
// Set up database connection
const db = knex({
client: "pg",
connection: {
connectionString: process.env.DATABASE_URL,
},
});
const app = express();
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.get("/", (req, res) => {
res.send("Success!");
});
app.post("/signin", (req, res) => {
signin.handleSignIn(req, res, db, bcrypt);
});
app.post("/register", (req, res) => {
register.handleRegister(req, res, db, bcrypt);
});
app.get("/profile/:id", (req, res) => {
profile.handleProfileGet(req, res, db);
});
app.put("/image", (req, res) => {
image.handleImage(req, res, db);
});
app.post("/imageurl", (req, res) => {
image.handleApiCall(req, res);
});
app.listen(process.env.PORT || 3000, () => {
console.log(`app is running on port ${process.env.PORT}!`);
});

1
server.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 154.58 141.53"><defs><style>.cls-1{fill:#ecebeb;}.cls-2{fill:#d8d2d2;}.cls-3{fill:#637582;}.cls-4{fill:#94a5af;}.cls-5{fill:#455760;}.cls-6{fill:#ffc754;}.cls-7{fill:#a2fce2;}.cls-10,.cls-11,.cls-8,.cls-9{fill:none;stroke:#3e3127;stroke-miterlimit:10;}.cls-11,.cls-8{stroke-width:3px;}.cls-9{stroke-width:5px;}.cls-10{stroke-width:4px;}.cls-11{stroke-linecap:round;}.cls-12{fill:#fff;}</style></defs><title>Asset 29</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><rect class="cls-1" x="16.84" y="68.67" width="120.91" height="26.11"/><rect class="cls-2" x="17.78" y="78.06" width="118.56" height="5.04"/><path class="cls-1" d="M22,2H24.2a4.37,4.37,0,0,1,4.37,4.37V26.91a0,0,0,0,1,0,0h-11a0,0,0,0,1,0,0V6.37A4.37,4.37,0,0,1,22,2Z"/><rect class="cls-3" x="3.46" y="26.9" width="149.58" height="51.13" rx="5"/><path class="cls-4" d="M13.51,78H7.24c-2.46,0-4.47-2.3-4.47-5.14V32c0-2.83,2-5.13,4.47-5.13h6.27C11,26.9,9,29.2,9,32V72.9C9,75.74,11,78,13.51,78Z"/><path class="cls-5" d="M151.32,30V72.26a4.41,4.41,0,0,1-4.41,4.41H73.54c31.92-9.53,44.71-37,49.29-51.13h24.08A4.41,4.41,0,0,1,151.32,30Z"/><circle class="cls-6" cx="23.45" cy="49.59" r="5.78"/><circle class="cls-7" cx="58.43" cy="49.59" r="5.78"/><circle class="cls-6" cx="40.94" cy="49.59" r="5.78"/><circle class="cls-8" cx="40.94" cy="49.59" r="5.78"/><rect class="cls-9" x="2.5" y="26.91" width="149.58" height="51.13" rx="5"/><rect class="cls-10" x="16.84" y="78.06" width="120.91" height="24.77"/><circle class="cls-8" cx="23.4" cy="49.59" r="5.78"/><rect class="cls-3" x="3.46" y="87.89" width="149.58" height="51.13" rx="5"/><path class="cls-5" d="M152.08,92.31v42.3a4.4,4.4,0,0,1-4.41,4.41H74.3c31.92-9.52,44.71-37,49.29-51.12h24.08A4.4,4.4,0,0,1,152.08,92.31Z"/><line class="cls-11" x1="21.02" y1="103.74" x2="36.19" y2="103.74"/><line class="cls-11" x1="21.02" y1="113.46" x2="36.19" y2="113.46"/><line class="cls-11" x1="21.02" y1="123.18" x2="36.19" y2="123.18"/><line class="cls-11" x1="53.48" y1="103.74" x2="68.65" y2="103.74"/><line class="cls-11" x1="53.48" y1="113.46" x2="68.65" y2="113.46"/><line class="cls-11" x1="53.48" y1="123.18" x2="68.65" y2="123.18"/><line class="cls-11" x1="85.94" y1="103.74" x2="101.1" y2="103.74"/><line class="cls-11" x1="85.94" y1="113.46" x2="101.1" y2="113.46"/><line class="cls-11" x1="85.94" y1="123.18" x2="101.1" y2="123.18"/><line class="cls-11" x1="118.39" y1="103.74" x2="133.56" y2="103.74"/><line class="cls-11" x1="118.39" y1="113.46" x2="133.56" y2="113.46"/><line class="cls-11" x1="118.39" y1="123.18" x2="133.56" y2="123.18"/><polygon class="cls-12" points="27.86 9.43 17.67 18.25 17.67 14.92 27.86 6.1 27.86 9.43"/><polygon class="cls-12" points="27.86 15.23 17.67 24.05 17.67 20.72 27.86 11.9 27.86 15.23"/><path class="cls-10" d="M23.1,2h0a5.48,5.48,0,0,1,5.48,5.48V26.91a0,0,0,0,1,0,0h-11a0,0,0,0,1,0,0V7.48A5.48,5.48,0,0,1,23.1,2Z"/><circle class="cls-8" cx="58.43" cy="49.59" r="5.78"/><circle class="cls-5" cx="104.77" cy="54.05" r="6.56"/><circle class="cls-5" cx="95.18" cy="44.31" r="2.98"/><path class="cls-4" d="M13.51,139H7.24c-2.46,0-4.47-2.3-4.47-5.13V93c0-2.83,2-5.13,4.47-5.13h6.27C11,87.89,9,90.19,9,93V133.9C9,136.73,11,139,13.51,139Z"/><rect class="cls-9" x="2.5" y="87.89" width="149.58" height="51.13" rx="5"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB