This commit is contained in:
Kfir Dayan 2023-08-13 11:51:54 +03:00
commit 64cb27eb45
28 changed files with 1965 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
dist
.env
data
node_modules
tmp

3
api/.env.example Normal file
View file

@ -0,0 +1,3 @@
REDIS_CONNECTION_URL="redis://localhost:6379"
SERVER_PORT=3000
MONGO_CONNECTION_URL="mongodb://{example_user}:{example_password}@localhost:27017/{DB}"

82
api/README.md Normal file
View file

@ -0,0 +1,82 @@
# Dario API
The Dario API is a RESTful API built using Express.js, MongoDB, and Redis, designed to provide endpoints for user eligibility verification and registration.
## Table of Contents
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Starting the Server](#starting-the-server)
- [Endpoints](#endpoints)
- [Contributing](#contributing)
- [License](#license)
## Getting Started
### Prerequisites
Before running the Dario API, ensure you have the following installed on your system:
- Node.js (v14 or higher)
- MongoDB
- Redis Server
### Installation
1. Clone this repository to your local machine:
```sh
git clone https://github.com/yourusername/dario-api.git
```
2. Install the required npm packages:
```sh
cd api
npm install
```
### Configuration
Create a .env file in the root directory of the project with the variables listed in .env.example.
### Usage
```sh
npm run dev
```
### Endpoints
# POST /verify-eligibility
Verify user eligibility based on a provided key.
Example Request:
```sh
POST http://localhost:3000/verify-eligibility
Content-Type: application/json
{
"key": "your_user_key"
}
```
# POST /register
Register a user after verifying eligibility.
Example Request:
```sh
POST http://localhost:3000/register
Content-Type: application/json
{
"email": "johndoe@example.com",
"key": "your_user_key",
"name": "John Doe",
"phone": "123-456-7890"
}
```

1007
api/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

21
api/package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "dario",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongoose": "^7.4.2",
"redis": "^3.1.2"
},
"devDependencies": {
"@types/express": "^4.17.17"
}
}

12
api/request.http Normal file
View file

@ -0,0 +1,12 @@
### Get
GET http://localhost:3000/
### Post
POST http://localhost:3000/verify-eligibility
Content-Type: application/json
{
"email": "kfda89@gmail.com"
}

View file

@ -0,0 +1,6 @@
export interface User {
Name: string;
Email: string;
Phone: string;
EmployeeID: string;
}

View file

@ -0,0 +1,63 @@
import { NextFunction, Request, Response } from "express";
import { Redis } from "../db/redis/Redis";
import { RegisterModel } from "../models/DarioRegisterModel";
import { User } from "../common/types/User";
export class DarioRegisterController {
redis: Redis;
registerModel: RegisterModel;
constructor() {
this.redis = new Redis();
this.registerModel = new RegisterModel();
}
async connectToRedis() {
await this.redis.connect();
}
public async verifyEligibility(
req: Request,
res: Response
) {
const { key } = req.body;
try {
const user: any = await this.redis.findUserByKey(key);
res.status(200).json(user);
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
}
public async register(req: Request, res: Response, next: NextFunction) {
const { email, key, name, phone } = req.body;
try {
const user: any = await this.redis.findUserByKey(key);
if (!user) return res.status(404).send("User not found");
const isUserVerified: boolean = this.verifyUser(user, email, name, phone);
if (isUserVerified) {
await this.registerModel.create(user);
await this.redis.deleteUserByKey(key);
res.status(200).send("User registered");
} else {
res.status(404).send("User not found");
}
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
}
private verifyUser(
user: User,
email: string,
name: string,
phone: string
): boolean {
console.log(user.Phone !== phone);
return user.Email === email && user.Name.toLocaleLowerCase() === name.toLocaleLowerCase() && user.Phone === phone;
}
}

View file

@ -0,0 +1,46 @@
import mongoose, { Document, Schema } from 'mongoose';
interface IUser extends Document {
Name: string;
Email: string;
Phone: string;
EmployeeID: string;
created_at: Date;
active: boolean;
updated_at: Date;
}
const userSchema = new Schema<IUser>({
Name: {
type: String,
required: true,
},
Email: {
type: String,
required: true,
},
Phone: {
type: String,
required: true,
},
EmployeeID: {
type: String,
required: true,
},
created_at: {
type: Date,
default: Date.now,
},
updated_at: {
type: Date,
default: Date.now,
},
active: {
type: Boolean,
default: true,
},
});
const UserModel = mongoose.model<IUser>('User', userSchema);
export default UserModel;

78
api/src/db/redis/Redis.ts Normal file
View file

@ -0,0 +1,78 @@
import * as redis from "redis";
import { User } from "../../common/types/User";
import dotenv from "dotenv";
const envVars = dotenv.config().parsed;
class Redis {
client: any;
async connect(): Promise<void> {
this.client = redis.createClient({ url: envVars.REDIS_CONNECTION_URL });
this.client.on("connect", () => {
console.log("Connected to Redis");
});
this.client.on("error", (err) => {
console.log("Error connecting to Redis:", err);
process.exit(1);
});
}
public async findUserByKey(key: string) {
try {
this.connect();
return new Promise((resolve, reject) => {
this.client.hget("users", key, (err: Error | null, userDataJson: string | null) => {
if (err) {
console.error("Error finding user in Redis:", err);
reject(err);
} else {
if (userDataJson) {
const userData: User = JSON.parse(userDataJson);
resolve(userData);
} else {
resolve(null);
}
}
});
});
} catch (error) {
console.error("Error connecting to Redis:", error);
throw error;
} finally {
this.close();
}
}
public async deleteUserByKey(key: string) {
try {
this.connect();
return new Promise((resolve, reject) => {
this.client.hdel("users", key, (err: Error | null, userDataJson: string | null) => {
if (err) {
console.error("Error deleting user in Redis:", err);
reject(err);
} else {
console.log("User deleted")
resolve(null);
}
});
});
} catch (error) {
console.error("Error connecting to Redis:", error);
throw error;
} finally {
this.close();
}
}
async close(): Promise<void> {
await new Promise((resolve) => this.client.quit(resolve));
console.log("Disconnected from Redis");
}
}
export { Redis };

37
api/src/index.ts Normal file
View file

@ -0,0 +1,37 @@
import express, { Express } from "express";
import { router } from "./routers/DarioRegisterRouter";
import dotenv from "dotenv";
class App {
app: Express;
envVars: any;
constructor() {
this.envVars = dotenv.config().parsed;
this.app = express();
}
public start() {
this.setMiddlewares();
this.setRouters();
this.startServer();
}
private setMiddlewares() {
this.app.use(express.json());
}
private startServer() {
this.app.listen(this.envVars.SERVER_PORT, () => {
console.log(`Server is running on port ${this.envVars.SERVER_PORT}`);
});
}
private setRouters() {
this.app.use(router);
}
}
const app = new App();
app.start();

View file

@ -0,0 +1,12 @@
export const registerMiddleware = async (req, res, next) => {
const { key, name, email, phone } = req.body;
try {
if (key && name && email && phone) {
next();
} else {
next("key, name, email, and phone are required");
}
} catch (error) {
next(error);
}
}

View file

@ -0,0 +1,12 @@
export const verifyEligibility = async (req, res, next) => {
const { key } = req.body;
try {
if (key) {
next();
} else {
next("key is required");
}
} catch (error) {
next(error);
}
};

View file

@ -0,0 +1,72 @@
import UserModel from '../db/mongodb/schema'; // Import the user schema/model
import mongoose from 'mongoose';
import { User } from '../common/types/User';
import dotenv from 'dotenv';
const envVars = dotenv.config().parsed;
export class RegisterModel {
async connect() {
try {
await mongoose.connect(envVars.MONGO_CONNECTION_URL);
console.log('Connected to MongoDB');
} catch (error) {
console.error('Error connecting to MongoDB:', error);
}
}
async disconnect() {
try {
await mongoose.disconnect();
console.log('Disconnected from MongoDB');
} catch (error) {
console.error('Error disconnecting from MongoDB:', error);
}
}
async create(userInput: User) {
try {
this.connect();
const user = new UserModel(userInput);
console.log(user)
return await user.save();
} catch (error) {
throw new Error('Error creating user: ' + error);
} finally {
this.disconnect();
}
}
async getByEmail(email: string) {
try {
return await UserModel.findOne({ Email: email });
} catch (error) {
throw new Error('Error getting user by email: ' + error);
}
}
async delete(email: string) {
try {
const user = await UserModel.findOne({ Email: email });
if (user) {
user.active = false;
return await user.save();
}
} catch (error) {
throw new Error('Error deleting user: ' + error);
}
}
async updateUpdatedAt(email: string) {
try {
const user = await UserModel.findOne({ Email: email });
if (user) {
user.updated_at = new Date();
return await user.save();
}
} catch (error) {
throw new Error('Error updating updated_at: ' + error);
}
}
}

View file

@ -0,0 +1,25 @@
import express, { Response, Request, NextFunction } from "express";
import { DarioRegisterController } from "../controllers/DarioRegisterController";
import { verifyEligibility } from "../middleware/verify-eligibility-middleware";
import { registerMiddleware } from "../middleware/register-middleware";
const router = express.Router();
const controller = new DarioRegisterController();
router.post(
"/verify-eligibility",
verifyEligibility,
async (req: Request, res: Response) => {
await controller.verifyEligibility(req, res);
}
);
router.post(
"/register",
registerMiddleware,
async (req: Request, res: Response, next: NextFunction) => {
await controller.register(req, res, next);
}
);
export { router };

14
api/tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "CommonJS",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ES2019",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
]
}

View file

@ -0,0 +1,32 @@
version: "3"
services:
redis:
image: redis:latest
container_name: my-redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
mongodb:
image: arm64v8/mongo:4.0
container_name: my-mongo
restart: always
ports:
- "27017:27017"
volumes:
- mongodb_data_vol:/data/db
- ./mongo-init-scripts/init.js:/docker-entrypoint-initdb.d/mongo-init.js
platform: linux/arm64/v8
environment:
- MONGO_INITDB_DATABASE=admin
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
expose:
- 27017
volumes:
redis_data:
mongodb_data_vol:

View file

@ -0,0 +1,12 @@
const username = 'example_user';
const password = 'example_password';
const dbName = 'example_db';
const userRole = 'readWrite';
db = db.getSiblingDB(dbName);
db.createUser({
user: username,
pwd: password,
roles: [{ role: userRole, db: dbName }],
});

5
inserter/.env.example Normal file
View file

@ -0,0 +1,5 @@
## Redis
REDIS_CONNECTION_URL="redis://localhost:6379"
## SQS
SQS_URL=""

47
inserter/README.md Normal file
View file

@ -0,0 +1,47 @@
### Inserter
This is a program that inserts a data.json file into a redis database.
## Usage
To use this program, you must have a redis database running on your machine. You can download redis [here](https://redis.io/download).
To run the program, you must have a data.json file in the same directory as the inserter.py file. The data.json file must be in the following format:
```
{
"user1@example.com": {
"Name": "John Doe",
"Email": "user1@example.com",
"Phone": "555-1234",
"EmployeeID": "EMP001"
},
"user2@example.com": {
"Name": "Jane Smith",
"Email": "user2@example.com",
"Phone": "444-5678",
"EmployeeID": "EMP002"
},
}
```
## Install
```
npm install
```
## ENV
```
export REDIS_CONNECTION_URL=redis://localhost:6379
```
Or use the .env.example file
## Run
```
npm run dev
```
## Output
This program will remove all in redis and insert new data by the data.json file.
the users collection will be inserted in a transaction mode with the redis database, with expiration of 4 days.

122
inserter/data.json Normal file
View file

@ -0,0 +1,122 @@
{
"user1@example.com": {
"Name": "John Doe",
"Email": "user1@example.com",
"Phone": "555-1234",
"EmployeeID": "EMP001"
},
"user2@example.com": {
"Name": "Jane Smith",
"Email": "user2@example.com",
"Phone": "444-5678",
"EmployeeID": "EMP002"
},
"user3@example.com": {
"Name": "Michael Johnson",
"Email": "user3@example.com",
"Phone": "777-9876",
"EmployeeID": "EMP003"
},
"user4@example.com": {
"Name": "Emily Wilson",
"Email": "user4@example.com",
"Phone": "999-5432",
"EmployeeID": "EMP004"
},
"user5@example.com": {
"Name": "David Lee",
"Email": "user5@example.com",
"Phone": "111-2222",
"EmployeeID": "EMP005"
},
"user6@example.com": {
"Name": "Olivia Martinez",
"Email": "user6@example.com",
"Phone": "888-7654",
"EmployeeID": "EMP006"
},
"user7@example.com": {
"Name": "William Taylor",
"Email": "user7@example.com",
"Phone": "777-8888",
"EmployeeID": "EMP007"
},
"user8@example.com": {
"Name": "Sophia Anderson",
"Email": "user8@example.com",
"Phone": "333-4444",
"EmployeeID": "EMP008"
},
"user9@example.com": {
"Name": "James Brown",
"Email": "user9@example.com",
"Phone": "222-1111",
"EmployeeID": "EMP009"
},
"user10@example.com": {
"Name": "Isabella Garcia",
"Email": "user10@example.com",
"Phone": "111-9999",
"EmployeeID": "EMP010"
},
"user11@example.com": {
"Name": "Ethan Clark",
"Email": "user11@example.com",
"Phone": "666-7777",
"EmployeeID": "EMP011"
},
"user12@example.com": {
"Name": "Ava Hernandez",
"Email": "user12@example.com",
"Phone": "777-4444",
"EmployeeID": "EMP012"
},
"user13@example.com": {
"Name": "Alexander Allen",
"Email": "user13@example.com",
"Phone": "999-2222",
"EmployeeID": "EMP013"
},
"user14@example.com": {
"Name": "Mia Turner",
"Email": "user14@example.com",
"Phone": "333-5555",
"EmployeeID": "EMP014"
},
"user15@example.com": {
"Name": "Benjamin Scott",
"Email": "user15@example.com",
"Phone": "222-7777",
"EmployeeID": "EMP015"
},
"user16@example.com": {
"Name": "Amelia Adams",
"Email": "user16@example.com",
"Phone": "777-3333",
"EmployeeID": "EMP016"
},
"user17@example.com": {
"Name": "Daniel Evans",
"Email": "user17@example.com",
"Phone": "444-1111",
"EmployeeID": "EMP017"
},
"user18@example.com": {
"Name": "Charlotte Stewart",
"Email": "user18@example.com",
"Phone": "888-6666",
"EmployeeID": "EMP018"
},
"user19@example.com": {
"Name": "Logan Hernandez",
"Email": "user19@example.com",
"Phone": "666-4444",
"EmployeeID": "EMP019"
},
"user20@example.com": {
"Name": "Scarlett Parker",
"Email": "user20@example.com",
"Phone": "222-8888",
"EmployeeID": "EMP020"
}
}

97
inserter/package-lock.json generated Normal file
View file

@ -0,0 +1,97 @@
{
"name": "inserter",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "inserter",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/redis": "^4.0.11",
"dotenv": "^16.3.1",
"redis": "^3.1.2"
},
"devDependencies": {
"@types/node": "^20.4.8"
}
},
"node_modules/@types/node": {
"version": "20.4.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz",
"integrity": "sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==",
"dev": true
},
"node_modules/@types/redis": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz",
"integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==",
"deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.",
"dependencies": {
"redis": "*"
}
},
"node_modules/denque": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/redis": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"dependencies": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-redis"
}
},
"node_modules/redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
}
}
}

19
inserter/package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "inserter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon dist/index.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.4.8"
},
"dependencies": {
"@types/redis": "^4.0.11",
"dotenv": "^16.3.1",
"redis": "^3.1.2"
}
}

View file

@ -0,0 +1,7 @@
export interface User {
Name: string;
Email: string;
Phone: string;
EmployeeID: string;
SecureKey?: string;
}

37
inserter/src/index.ts Normal file
View file

@ -0,0 +1,37 @@
import { Redis } from "./redis/Redis";
import fs from "fs";
import { User } from "./common/types/User";
class Inserter {
redis: Redis;
constructor() {
console.log("Inserter class is created");
this.redis = new Redis();
}
async connectToRedis() {
await this.redis.connect();
}
async start() {
try {
await this.connectToRedis();
const data = await this.readFile("./data.json");
await this.redis.transactionToRedis(data);
} catch (error) {
console.error("Error:", error);
} finally {
this.redis.close();
}
}
async readFile(filePath: string): Promise<User[]> {
const data = fs.readFileSync(filePath, "utf8");
const json = JSON.parse(data);
return json;
}
}
const inserter = new Inserter();
inserter.start();

View file

@ -0,0 +1,78 @@
import * as redis from "redis";
import { User } from "../common/types/User";
import { randomUUID } from "crypto";
import dotenv from "dotenv";
class Redis {
client: any;
envVars: any;
constructor() {
this.envVars = dotenv.config().parsed;
console.log("Redis class is created");
}
async connect(): Promise<void> {
this.client = redis.createClient({ url: this.envVars.REDIS_CONNECTION_URL });
this.client.on("connect", () => {
console.log("Connected to Redis");
});
this.client.on("error", (err) => {
console.log("Error connecting to Redis:", err);
process.exit(1);
});
}
async transactionToRedis(data: User[]): Promise<void> {
const EXPIRATION_TIME = 60 * 60 * 24 * 4;
const multi = this.client.multi();
multi.flushall();
for (const user in data) {
if (!data[user].EmployeeID || !data[user].Email || !data[user].Name || !data[user].Phone) {
this.rollback("Invalid user data", user);
return;
}
data[user].SecureKey = this.randomUUID()
data[user].Email = this.encrypt(data[user].Email)
multi.expire("users", EXPIRATION_TIME);
multi.hset("users", data[user].SecureKey, JSON.stringify(data[user]));
};
multi.exec(async (err: any, replies: any) => {
if (err) {
console.log("Error inserting data in Redis:", err.message);
} else {
console.log("Data inserted in Redis:", replies);
await this.pushToSqs(data);
}
});
}
rollback(reason: string, session: any, data = null): void {
console.error(reason, data);
session.discard();
}
randomUUID(): string {
return randomUUID();
}
private encrypt(email: string): string {
return email;
}
async close(): Promise<void> {
await new Promise((resolve) => this.client.quit(resolve));
console.log("Disconnected from Redis");
}
private async pushToSqs(data: User[]): Promise<void> {
console.log("Pushing to SQS");
}
}
export { Redis };

14
inserter/tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "CommonJS",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ES2019",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
]
}