├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── README.md ├── github └── Screen Shot 2022-02-22 at 20.16.59.png ├── index.js ├── package.json ├── secret └── serviceAccountKey.json └── src ├── constant ├── message_constant.js └── project_constant.js ├── controller ├── home │ ├── card_controller.js │ ├── home_contoller.js │ ├── product_controller.js │ ├── search_controller.js │ └── validation │ │ ├── card_model.js │ │ ├── category_model.js │ │ └── product_model.js ├── login │ ├── login_model.js │ └── login_service.js └── profile │ └── profile_service.js ├── product └── parser_utility.js └── utility └── error_utility.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es2021: true, 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint', 9 | ecmaVersion: 2018, 10 | sourceType: 'module', 11 | }, 12 | rules: {}, 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "attach", 11 | "name": "Node: Nodemon", 12 | "processId": "${command:PickProcess}", 13 | "restart": true, 14 | "protocol": "inspector", 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontFamily": "JetBrains Mono", 3 | "editor.fontSize": 16 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ecommerce App Backend 2 | 3 | ![Firebase](https://github.com/VB10/ecommerce_backend/blob/master/github/Screen%20Shot%202022-02-22%20at%2020.16.59.png?raw=true) 4 | 5 | I made to be an example for the full app concept. 6 | 7 | ## Installation 8 | 9 | Postman collection link for about services. 10 | 11 | [Download this json then going to import your postman account](https://www.getpostman.com/collections/ac2a32c5576705d1d24b) 12 | 13 | ## Usage 14 | 15 | ```python 16 | (you must be have firebase credantial so you can getting to take your new project or existing) 17 | npm install 18 | npm run 19 | 20 | ``` 21 | 22 | ## Flutter 23 | [https://github.com/VB10/ecommerce_flutter_side](https://github.com/VB10/ecommerce_flutter_side) 24 | 25 | ## Contributing 26 | 27 | You wan to add something this project, always send me new pr after i'll check both a new features and review to your code. 28 | 29 | ## License 30 | 31 | [MIT](https://choosealicense.com/licenses/mit/) 32 | -------------------------------------------------------------------------------- /github/Screen Shot 2022-02-22 at 20.16.59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VB10/ecommerce_backend/bf56277eca76ccbe96f248714c6eff6d8bb11543/github/Screen Shot 2022-02-22 at 20.16.59.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const bodyParser = require('body-parser'); 4 | const port = 3000; 5 | const admin = require('firebase-admin'); 6 | const firebaseApp = require('firebase'); 7 | const { 8 | DATABASE_URL, 9 | FIREBASE_CONFIG_NORMAL, 10 | } = require('./src/constant/project_constant'); 11 | const { LoginService } = require('./src/controller/login/login_service'); 12 | const { ValidationError } = require('express-validation'); 13 | const { HomeService } = require('./src/controller/home/home_contoller'); 14 | const { ProductService } = require('./src/controller/home/product_controller'); 15 | const { SearchService } = require('./src/controller/home/search_controller'); 16 | 17 | const firebaseConfig = { 18 | credential: admin.credential.applicationDefault(), 19 | databaseURL: DATABASE_URL, 20 | }; 21 | 22 | admin.initializeApp(firebaseConfig); 23 | firebaseApp.initializeApp(FIREBASE_CONFIG_NORMAL); 24 | app.use(express.json()); 25 | app.use('', LoginService); 26 | app.use('', HomeService); 27 | app.use('', ProductService); 28 | app.use('', SearchService); 29 | 30 | app.use(function (err, req, res, next) { 31 | if (err instanceof ValidationError) { 32 | return res.status(err.statusCode).json(err); 33 | } 34 | console.log(err); 35 | next(); 36 | }); 37 | 38 | app.listen(port, () => console.log(`App is runnig`)); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecomm_backend_firebase", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon app/app.js", 9 | "debug": "nodemon --inspect index.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.21.4", 15 | "body-parser": "^1.19.0", 16 | "express": "^4.17.1", 17 | "express-validation": "^3.0.8", 18 | "firebase": "^8.7.1", 19 | "firebase-admin": "^9.10.0", 20 | "flatted": "^3.2.2", 21 | "http-status-codes": "^2.1.4", 22 | "https": "^1.0.0", 23 | "nodemon": "^2.0.12" 24 | }, 25 | "nodemonConfig": { 26 | "ignore": [ 27 | "test/*", 28 | "docs/*" 29 | ], 30 | "delay": "2500", 31 | "env": { 32 | "NODE_ENV": "development", 33 | "PORT": 4000 34 | } 35 | }, 36 | "devDependencies": { 37 | "eslint": "^7.32.0", 38 | "eslint-config-airbnb-base": "^14.2.1", 39 | "eslint-config-google": "^0.14.0", 40 | "eslint-plugin-import": "^2.24.2", 41 | "prettier": "2.3.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /secret/serviceAccountKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "", 3 | "project_id": "", 4 | "private_key_id": "", 5 | "private_key": "", 6 | "client_email": "", 7 | "client_id": "", 8 | "auth_uri": "", 9 | "token_uri": "", 10 | "auth_provider_x509_cert_url": "", 11 | "client_x509_cert_url": "" 12 | } -------------------------------------------------------------------------------- /src/constant/message_constant.js: -------------------------------------------------------------------------------- 1 | const SEARCH_ERROR_MESSAGE = 'Your key must be created for 3 length'; 2 | 3 | const PRODUCT_NOT_ENOUGH = 'We not have enough product'; 4 | exports = module.exports = { SEARCH_ERROR_MESSAGE, PRODUCT_NOT_ENOUGH }; 5 | -------------------------------------------------------------------------------- /src/constant/project_constant.js: -------------------------------------------------------------------------------- 1 | const DATABASE_URL = 'https://hwacommerce.firebaseio.com'; 2 | 3 | const FIREBASE_CONFIG_NORMAL = { 4 | apiKey: '', 5 | authDomain: '', 6 | projectId: 'hwacommerce', 7 | storageBucket: '', 8 | messagingSenderId: '', 9 | appId: '', 10 | measurementId: '', 11 | }; 12 | 13 | const SEARCH_KEY_MINUMUM = 3; 14 | 15 | const SECURE_TOKEN_URL = 'https://securetoken.googleapis.com/v1/token?key='; 16 | 17 | exports = module.exports = { 18 | SEARCH_KEY_MINUMUM, 19 | DATABASE_URL, 20 | FIREBASE_CONFIG_NORMAL, 21 | SECURE_TOKEN_URL, 22 | }; 23 | -------------------------------------------------------------------------------- /src/controller/home/card_controller.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const CardService = express.Router(); 3 | const admin = require('firebase-admin'); 4 | 5 | const { StatusCodes } = require('http-status-codes'); 6 | const { 7 | SEARCH_ERROR_MESSAGE, 8 | PRODUCT_NOT_ENOUGH, 9 | } = require('../../constant/message_constant'); 10 | const { SEARCH_KEY_MINUMUM } = require('../../constant/project_constant'); 11 | const { createError } = require('../../utility/error_utility'); 12 | const { cardValidation } = require('./validation/card_model'); 13 | const { validate } = require('express-validation'); 14 | 15 | const search = '/search'; 16 | const product = 'product'; 17 | 18 | CardService.get(search, validate(cardValidation, {}, {}), (req, res) => { 19 | admin 20 | .firestore() 21 | .collection(product) 22 | .path(req.body.id) 23 | .get() 24 | .then((snapshot) => { 25 | const isHaveProduct = snapshot.doc.data().count >= req.body.count; 26 | if (isHaveProduct) { 27 | return res.json({ 28 | isDone: 'true', 29 | }); 30 | } else { 31 | return res 32 | .status(StatusCodes.NOT_ACCEPTABLE) 33 | .json(createError(PRODUCT_NOT_ENOUGH)); 34 | } 35 | }) 36 | .catch((error) => { 37 | console.log(error); 38 | return res.status(StatusCodes.NOT_FOUND).json(error); 39 | }); 40 | }); 41 | 42 | exports = module.exports = { CardService }; 43 | -------------------------------------------------------------------------------- /src/controller/home/home_contoller.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const HomeService = express.Router(); 3 | const { StatusCodes } = require('http-status-codes'); 4 | const admin = require('firebase-admin'); 5 | const { validate } = require('express-validation'); 6 | const { 7 | categoryVaidation, 8 | latestVaidation, 9 | } = require('./validation/category_model'); 10 | 11 | const { parseSnapshotAndMerge } = require('./../../product/parser_utility'); 12 | 13 | const categories = 'categories'; 14 | const latest = 'latest'; 15 | HomeService.get('/' + categories, (req, res) => { 16 | admin 17 | .firestore() 18 | .collection(categories) 19 | .get() 20 | .then((snapshot) => { 21 | return res.json(parseSnapshotAndMerge(snapshot)); 22 | }) 23 | .catch((_) => {}); 24 | }); 25 | 26 | HomeService.get('/' + latest, (_, res) => { 27 | admin 28 | .firestore() 29 | .collection(latest) 30 | .get() 31 | .then((snapshot) => { 32 | return res.json(parseSnapshotAndMerge(snapshot)); 33 | }) 34 | .catch((err) => {}); 35 | }); 36 | 37 | HomeService.post( 38 | '/' + categories, 39 | validate(categoryVaidation, {}, {}), 40 | 41 | (req, res) => { 42 | admin 43 | .firestore() 44 | .collection(categories) 45 | .doc() 46 | .set(req.body) 47 | .then((snapshot) => { 48 | return res.json(snapshot.writeTime); 49 | }) 50 | .catch((err) => {}); 51 | } 52 | ); 53 | 54 | HomeService.post( 55 | '/' + latest, 56 | validate(latestVaidation, {}, {}), 57 | 58 | (req, res) => { 59 | admin 60 | .firestore() 61 | .collection(latest) 62 | .doc() 63 | .set(req.body) 64 | .then((snapshot) => { 65 | return res.json(snapshot.writeTime); 66 | }) 67 | .catch((err) => {}); 68 | } 69 | ); 70 | 71 | module.exports = { HomeService }; 72 | -------------------------------------------------------------------------------- /src/controller/home/product_controller.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const ProductService = express.Router(); 3 | const admin = require('firebase-admin'); 4 | const { validate } = require('express-validation'); 5 | const { productValidation } = require('./validation/product_model'); 6 | 7 | const product = 'product'; 8 | ProductService.get('/' + product, (req, res) => { 9 | admin 10 | .firestore() 11 | .collection(product) 12 | .get() 13 | .then((snapshot) => { 14 | const data = snapshot.docs.map((doc) => ({ 15 | id: doc.id, 16 | ...doc.data(), 17 | })); 18 | return res.json(data); 19 | }) 20 | .catch((_) => {}); 21 | }); 22 | 23 | ProductService.post( 24 | '/' + product, 25 | validate(productValidation, {}, {}), 26 | 27 | (req, res) => { 28 | admin 29 | .firestore() 30 | .collection(product) 31 | .doc() 32 | .set(req.body) 33 | .then((snapshot) => { 34 | return res.json(snapshot.writeTime); 35 | }) 36 | .catch((err) => {}); 37 | } 38 | ); 39 | 40 | module.exports = { ProductService }; 41 | -------------------------------------------------------------------------------- /src/controller/home/search_controller.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const SearchService = express.Router(); 3 | const admin = require('firebase-admin'); 4 | 5 | const { StatusCodes } = require('http-status-codes'); 6 | const { SEARCH_ERROR_MESSAGE } = require('../../constant/message_constant'); 7 | const { SEARCH_KEY_MINUMUM } = require('../../constant/project_constant'); 8 | const { createError } = require('../../utility/error_utility'); 9 | 10 | const search = '/search'; 11 | const product = 'product'; 12 | 13 | SearchService.get(search, (req, res) => { 14 | if (!req.query.key || req.query.key.length < SEARCH_KEY_MINUMUM) { 15 | return res 16 | .status(StatusCodes.NOT_ACCEPTABLE) 17 | .json(createError(SEARCH_ERROR_MESSAGE)); 18 | } 19 | const term = req.query.key.toLowerCase(); 20 | admin 21 | .firestore() 22 | .collection(product) 23 | .get() 24 | .then((snapshot) => { 25 | const items = snapshot.docs 26 | .filter((doc) => doc.data().title.toLowerCase().includes(term)) 27 | .map((doc) => ({ 28 | id: doc.id, 29 | ...doc.data(), 30 | })); 31 | 32 | return res.json(items); 33 | }) 34 | .catch((error) => { 35 | console.log(error); 36 | return res.status(StatusCodes.NOT_FOUND).json(error); 37 | }); 38 | }); 39 | 40 | exports = module.exports = { SearchService }; 41 | -------------------------------------------------------------------------------- /src/controller/home/validation/card_model.js: -------------------------------------------------------------------------------- 1 | const { Joi } = require('express-validation'); 2 | 3 | const cardValidation = { 4 | body: Joi.object({ 5 | id: Joi.string().required(), 6 | count: Joi.number().required(), 7 | }), 8 | }; 9 | 10 | module.exports = { cardValidation }; 11 | -------------------------------------------------------------------------------- /src/controller/home/validation/category_model.js: -------------------------------------------------------------------------------- 1 | const { Joi } = require('express-validation'); 2 | 3 | const categoryVaidation = { 4 | body: Joi.object({ 5 | name: Joi.string().required(), 6 | image: Joi.string().uri().required(), 7 | }), 8 | }; 9 | 10 | const latestVaidation = { 11 | body: Joi.object({ 12 | image: Joi.string().uri().required(), 13 | }), 14 | }; 15 | 16 | module.exports = { categoryVaidation, latestVaidation }; 17 | -------------------------------------------------------------------------------- /src/controller/home/validation/product_model.js: -------------------------------------------------------------------------------- 1 | const { Joi } = require('express-validation'); 2 | 3 | const productValidation = { 4 | body: Joi.object({ 5 | title: Joi.string().required(), 6 | image: Joi.string().uri().required(), 7 | money: Joi.number().required(), 8 | categoryId: Joi.string().required(), 9 | }), 10 | }; 11 | 12 | module.exports = { productValidation }; 13 | -------------------------------------------------------------------------------- /src/controller/login/login_model.js: -------------------------------------------------------------------------------- 1 | const { Joi } = require('express-validation'); 2 | 3 | const loginValidation = { 4 | body: Joi.object({ 5 | email: Joi.string().email().required(), 6 | password: Joi.string() 7 | .regex(/[a-zA-Z0-9]{3,30}/) 8 | .required(), 9 | }), 10 | }; 11 | 12 | const forgotValidation = { 13 | body: Joi.object({ 14 | email: Joi.string().email().required(), 15 | }), 16 | }; 17 | 18 | const registerValidation = { 19 | body: Joi.object({ 20 | displayName: Joi.string().required(), 21 | email: Joi.string().email().required(), 22 | password: Joi.string() 23 | .regex(/[a-zA-Z0-9]{3,30}/) 24 | .required(), 25 | }), 26 | }; 27 | 28 | module.exports = { loginValidation, forgotValidation, registerValidation }; 29 | -------------------------------------------------------------------------------- /src/controller/login/login_service.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const LoginService = express.Router(); 4 | 5 | const loginPath = '/login'; 6 | const registerPath = '/register'; 7 | const forgotPath = '/forgot'; 8 | const refreshToken = '/refreshToken'; 9 | const LoginModel = require('./login_model'); 10 | 11 | const { StatusCodes } = require('http-status-codes'); 12 | 13 | const firebaseApp = require('firebase'); 14 | const { validate } = require('express-validation'); 15 | const admin = require('firebase-admin'); 16 | const axios = require('axios').default; 17 | const { 18 | FIREBASE_CONFIG_NORMAL, 19 | SECURE_TOKEN_URL, 20 | } = require('../../constant/project_constant'); 21 | 22 | LoginService.post( 23 | loginPath, 24 | validate(LoginModel.loginValidation, {}, {}), 25 | (req, res) => { 26 | firebaseApp 27 | .auth() 28 | .signInWithEmailAndPassword(req.body.email, req.body.password) 29 | .then((result) => { 30 | return res.send(result.user); 31 | }) 32 | .catch((err) => { 33 | console.log(err); 34 | return res.status(StatusCodes.NOT_FOUND).send(err); 35 | }); 36 | } 37 | ); 38 | 39 | LoginService.post( 40 | forgotPath, 41 | validate(LoginModel.forgotValidation, {}, {}), 42 | (req, res) => { 43 | firebaseApp.default 44 | .auth() 45 | .sendPasswordResetEmail(req.body.email) 46 | .then(() => { 47 | return res.send({ message: 'Password forgot mail sended' }); 48 | }) 49 | .catch((err) => { 50 | return res.status(StatusCodes.NOT_FOUND).send(err); 51 | }); 52 | } 53 | ); 54 | 55 | LoginService.post( 56 | registerPath, 57 | validate(LoginModel.registerValidation, {}, {}), 58 | async (req, res) => { 59 | try { 60 | const user = await admin.auth().createUser({ 61 | displayName: req.body.displayName, 62 | email: req.body.email, 63 | password: req.body.password, 64 | }); 65 | 66 | console.log(user); 67 | const token = await admin.auth().createCustomToken(user.uid); 68 | const data = await firebaseApp.default 69 | .auth() 70 | .signInWithCustomToken(token); 71 | 72 | return res.json({ token: data.user }); 73 | } catch (error) { 74 | return res.status(StatusCodes.UNAUTHORIZED).send(error); 75 | } 76 | } 77 | ); 78 | 79 | LoginService.get(refreshToken, async (req, res) => { 80 | const data = { 81 | refresh_token: req.headers.authorization, 82 | grant_type: 'refresh_token', 83 | }; 84 | 85 | const url = SECURE_TOKEN_URL + FIREBASE_CONFIG_NORMAL.apiKey; 86 | axios 87 | .post(url, JSON.stringify(data), { 88 | headers: { 89 | 'Content-Type': 'application/json; charset=UTF-8', 90 | }, 91 | }) 92 | .then((response) => { 93 | return res.json(response.data); 94 | }) 95 | .catch((err) => { 96 | return res.status(StatusCodes.NOT_FOUND).json(err); 97 | }); 98 | }); 99 | 100 | module.exports = { LoginService }; 101 | -------------------------------------------------------------------------------- /src/controller/profile/profile_service.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const ProfileService = express.Router(); 4 | 5 | const loginPath = '/login'; 6 | const registerPath = '/register'; 7 | const forgotPath = '/forgot'; 8 | const refreshToken = '/refreshToken'; 9 | const LoginModel = require('./login_model'); 10 | 11 | const { StatusCodes } = require('http-status-codes'); 12 | 13 | const firebaseApp = require('firebase'); 14 | const { validate } = require('express-validation'); 15 | const admin = require('firebase-admin'); 16 | const axios = require('axios').default; 17 | const { 18 | FIREBASE_CONFIG_NORMAL, 19 | SECURE_TOKEN_URL, 20 | } = require('../../constant/project_constant'); 21 | const { createError } = require('../../utility/error_utility'); 22 | 23 | const profile = '/profile'; 24 | ProfileService.get(profile, async (req, res) => { 25 | try { 26 | const result = await admin.auth().verifyIdToken(req.headers.token); 27 | const user = await admin.auth().getUser(result.uid); 28 | return res.json(user); 29 | } catch (error) { 30 | return res.status(StatusCodes.UNAUTHORIZED).json(createError(error)); 31 | } 32 | }); 33 | 34 | module.exports = { ProfileService }; 35 | -------------------------------------------------------------------------------- /src/product/parser_utility.js: -------------------------------------------------------------------------------- 1 | function parseSnapshotAndMerge(snapshot) { 2 | const data = snapshot.docs.map((doc) => ({ 3 | id: doc.id, 4 | ...doc.data(), 5 | })); 6 | 7 | return data; 8 | } 9 | 10 | module.exports = { parseSnapshotAndMerge }; 11 | -------------------------------------------------------------------------------- /src/utility/error_utility.js: -------------------------------------------------------------------------------- 1 | function createError(val) { 2 | return { 3 | error: val, 4 | }; 5 | } 6 | 7 | exports = module.exports = { createError }; 8 | --------------------------------------------------------------------------------