From 8db8267fb040d94cee4472e4d95f7a1317236c21 Mon Sep 17 00:00:00 2001 From: 0xheartcode <0xheartcode@gmail.com> Date: Wed, 18 Sep 2024 02:48:11 +0200 Subject: [PATCH] =?UTF-8?q?mongo=20implementation=20=F0=9F=90=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local | 1 + .github/workflows/prod-workflow.yml | 11 +-- .github/workflows/staging-workflow.yml | 12 +-- .github/workflows/testnet-workflow.yml | 109 +++++++++++++++++++++++++ package.json | 1 + src/app.ts | 5 +- src/dbcode/dbSetup.ts | 29 ++++--- src/dbcode/mongoDbClient.ts | 51 ++++++++++++ src/handlers/basicHandler.ts | 63 ++++++++------ src/middleware/auth.ts | 16 ++-- src/types/dbInterfaces.ts | 8 +- yarn.lock | 93 +++++++++++++++++++++ 12 files changed, 335 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/testnet-workflow.yml create mode 100644 src/dbcode/mongoDbClient.ts diff --git a/.env.local b/.env.local index da1ceae..be19bd3 100644 --- a/.env.local +++ b/.env.local @@ -1,2 +1,3 @@ MAKEFILE_OVERWRITE_DOCKERNAME= INITIAL_BEARER_TOKEN= +MONGO_URI= diff --git a/.github/workflows/prod-workflow.yml b/.github/workflows/prod-workflow.yml index 0eea0c0..8219ed2 100644 --- a/.github/workflows/prod-workflow.yml +++ b/.github/workflows/prod-workflow.yml @@ -1,9 +1,9 @@ -name: Prod - TALENTPLUG-MAIL-APP +name: Prod on: - # push: - # branches: - # - prod + push: + branches: + - prod jobs: cloud_build-local_docker: @@ -17,10 +17,7 @@ jobs: uses: SpicyPizza/create-envfile@v2.0 with: envkey_PORT: ${{ vars.ENVKEY_PORT }} - envkey_API_URL: ${{ vars.API_URL_PROD }} #Full api-core email endpoint here - envkey_INITIAL_REFRESH_TOKEN: ${{ vars.INITIAL_REFRESH_TOKEN }} envkey_INITIAL_BEARER_TOKEN: ${{ vars.INITIAL_BEARER_TOKEN }} - envkey_INITIAL_GOOGLE_CREDENTIALS: ${{ vars.INITIAL_GOOGLE_CREDENTIALS }} envkey_MAKEFILE_OVERWRITE_DOCKERNAME: ${{ vars.MAKEFILE_OVERWRITE_DOCKERNAME }} sort_keys: false file_name: .env.local diff --git a/.github/workflows/staging-workflow.yml b/.github/workflows/staging-workflow.yml index 14b8376..fe21a9d 100644 --- a/.github/workflows/staging-workflow.yml +++ b/.github/workflows/staging-workflow.yml @@ -1,9 +1,9 @@ -name: Staging - TALENTPLUG-MAIL-APP +name: Staging on: - # push: - # branches: - # - staging + push: + branches: + - staging workflow_dispatch: inputs: reason: @@ -24,10 +24,7 @@ jobs: uses: SpicyPizza/create-envfile@v2.0 with: envkey_PORT: ${{ vars.ENVKEY_PORT }} - envkey_API_URL: ${{ vars.API_URL_STAGING }} #Full api-core email endpoint here - envkey_INITIAL_REFRESH_TOKEN: ${{ vars.INITIAL_REFRESH_TOKEN }} envkey_INITIAL_BEARER_TOKEN: ${{ vars.INITIAL_BEARER_TOKEN }} - envkey_INITIAL_GOOGLE_CREDENTIALS: ${{ vars.INITIAL_GOOGLE_CREDENTIALS }} envkey_MAKEFILE_OVERWRITE_DOCKERNAME: ${{ vars.MAKEFILE_OVERWRITE_DOCKERNAME }} sort_keys: false file_name: .env.local @@ -105,7 +102,6 @@ jobs: rm ${{env.DOCKER_IMAGE_NAME}}.tar rm Makefile cd .. - # We're keeping the folder, because of the shared files rmdir ${{env.FOLDER_NAME}} - name: Print end of script message diff --git a/.github/workflows/testnet-workflow.yml b/.github/workflows/testnet-workflow.yml new file mode 100644 index 0000000..68d1241 --- /dev/null +++ b/.github/workflows/testnet-workflow.yml @@ -0,0 +1,109 @@ +name: Testnet + +on: + push: + branches: + - testnet + + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual trigger' + required: false + default: 'Manual build and deploy' + +jobs: + cloud_build-local_docker: + runs-on: ubuntu-latest + if: github.ref != 'refs/heads/dev' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Make production envfile + uses: SpicyPizza/create-envfile@v2.0 + with: + envkey_PORT: ${{ vars.ENVKEY_PORT }} + envkey_INITIAL_BEARER_TOKEN: ${{ vars.INITIAL_BEARER_TOKEN }} + envkey_MAKEFILE_OVERWRITE_DOCKERNAME: ${{ vars.MAKEFILE_OVERWRITE_DOCKERNAME }} + sort_keys: false + file_name: .env.local + + - name: Set environment variable + run: echo "CI=false" >> $GITHUB_ENV + + - name: Check version + run: make version + + - name: Build the docker image + run: make composebuild-prod + + - name: Display image_id variable + run: make print_image_id + + - name: Compress the image.tar + run: make save_image_as_tar + + - name: Set Docker image name & PORT number + run: | + export FOLDER_NAME=$(make echo_foldername) + echo "FOLDER_NAME=$FOLDER_NAME" >> $GITHUB_ENV + export DOCKER_IMAGE_NAME=$(make echo_docker_image_name) + echo "DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME" >> $GITHUB_ENV + export APPLICATION_PORT=$(grep '^PORT=' .env.local | cut -d= -f2 | tr -d '\n') + echo "APPLICATION_PORT=$APPLICATION_PORT" >> $GITHUB_ENV + - name: Check files + run: ls -a + + - name: SCP file to server + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{ secrets.REMOTE_HOST_TESTNET }} + username: ${{ secrets.REMOTE_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: ${{ secrets.REMOTE_PORT }} + source: "${{ env.DOCKER_IMAGE_NAME }}.tar.gz,Makefile" + target: ./${{ env.FOLDER_NAME }}/ # Target is based on the host:username login path. Will create non-existant folders. + + - name: Setup SSH and deploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{secrets.REMOTE_HOST_TESTNET}} + username: ${{secrets.REMOTE_USERNAME}} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: ${{ secrets.REMOTE_PORT }} + script: | + cd ${{ env.FOLDER_NAME }} + ls + # + # Prepare stop & delete + # Docker + make stop_matching_containers + make delete_matching_images + # + # Dockerfile + if command -v pigz &> /dev/null + then + unpigz -f "${{ env.DOCKER_IMAGE_NAME }}.tar.gz" + else + gunzip -f "${{ env.DOCKER_IMAGE_NAME }}.tar.gz" + fi + # + # Load Docker + # + docker load -i ${{env.DOCKER_IMAGE_NAME}}.tar + make print_image_id + make run_container PORT=${{ env.APPLICATION_PORT }} + # + # Cleanup + # + #ls + rm ${{env.DOCKER_IMAGE_NAME}}.tar + rm Makefile + cd .. + rmdir ${{env.FOLDER_NAME}} + + - name: Print end of script message + run: echo "Good Morning... You have arrived at the end of the script 0.0/" + diff --git a/package.json b/package.json index d0200d6..77600c2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "form-data": "^4.0.0", "google-auth-library": "^9.14.0", "googleapis": "^140.0.1", + "mongodb": "^6.9.0", "open": "^10.1.0", "whatwg-url": "^14.0.0" }, diff --git a/src/app.ts b/src/app.ts index 31d1464..bc94a82 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,10 +1,13 @@ import express from 'express'; import routes from './routes'; -import { checkRequiredEnvVariables } from './dbcode/dbSetup'; +import { checkRequiredEnvVariables, initializeDatabase } from './dbcode/dbSetup'; // Check required environment variables checkRequiredEnvVariables(); +// Initialize database connection +await initializeDatabase(); + const app = express(); const PORT = 8080; diff --git a/src/dbcode/dbSetup.ts b/src/dbcode/dbSetup.ts index 5ced91f..42ee6ca 100644 --- a/src/dbcode/dbSetup.ts +++ b/src/dbcode/dbSetup.ts @@ -1,27 +1,23 @@ -// src/dbCode/dbSetup.ts +// src/dbcode/dbSetup.ts + import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; -import dbClient from '../dbcode/dbClient'; +import MongoDbClient from './mongoDbClient'; const envPath = path.resolve(process.cwd(), '.env'); const envLocalPath = path.resolve(process.cwd(), '.env.local'); if (fs.existsSync(envPath)) { dotenv.config({ path: envPath }); - //console.log('.env file found and loaded'); } else if (fs.existsSync(envLocalPath)) { dotenv.config({ path: envLocalPath }); - //console.log('.env.local file found and loaded'); } else { console.log('Neither .env nor .env.local file found.'); } export function checkRequiredEnvVariables() { - const requiredVariables: string[] = [ - // Add your required variables here - // For example: 'DATABASE_URL', 'API_KEY', etc. - ]; + const requiredVariables = ['MONGO_URI' ]; for (const variable of requiredVariables) { if (!process.env[variable]) { @@ -33,12 +29,19 @@ export function checkRequiredEnvVariables() { console.log('All required environment variables are set.'); } -export function initializeTokens() { - const bearerToken = process.env.INITIAL_BEARER_TOKEN; +let dbClient: MongoDbClient; + +export async function initializeDatabase() { + const mongoUri = process.env.MONGO_URI as string; + dbClient = new MongoDbClient(mongoUri); + await dbClient.connect(); + + const bearerToken = process.env.INITIAL_BEARER_TOKEN; if (bearerToken) { - if (dbClient.getCurrentBearerTokenDB() === null) { - dbClient.setBearerTokenDB(bearerToken); + const currentToken = await dbClient.getCurrentBearerTokenDB(); + if (currentToken === null) { + await dbClient.setBearerTokenDB(bearerToken); console.log('Initial bearer token set from environment variable'); } } else { @@ -46,4 +49,6 @@ export function initializeTokens() { } } +export { dbClient }; + console.log('Database setup module loaded'); diff --git a/src/dbcode/mongoDbClient.ts b/src/dbcode/mongoDbClient.ts new file mode 100644 index 0000000..b4dc1d8 --- /dev/null +++ b/src/dbcode/mongoDbClient.ts @@ -0,0 +1,51 @@ +// src/dbcode/mongoDbClient.ts + +import { MongoClient, Db } from 'mongodb'; +import { IDbClient } from '../types/dbInterfaces'; + +class MongoDbClient implements IDbClient { + private client: MongoClient; + private db: Db | null = null; + + constructor(uri: string) { + this.client = new MongoClient(uri); + } + + async connect() { + await this.client.connect(); + this.db = this.client.db('yourDatabaseName'); + console.log('Connected to MongoDB'); + } + + async setBearerTokenDB(token: string): Promise { + if (!this.db) throw new Error('Database not connected'); + const collection = this.db.collection('bearer_tokens'); + const existingToken = await collection.findOne({}); + if (existingToken) return false; + await collection.insertOne({ token }); + return true; + } + + async isValidBearerTokenDB(token: string): Promise { + if (!this.db) throw new Error('Database not connected'); + const collection = this.db.collection('bearer_tokens'); + const result = await collection.findOne({ token }); + return !!result; + } + + async getCurrentBearerTokenDB(): Promise { + if (!this.db) throw new Error('Database not connected'); + const collection = this.db.collection('bearer_tokens'); + const result = await collection.findOne({}); + return result ? result.token : null; + } + + async changeBearerTokenDB(currentToken: string, newToken: string): Promise { + if (!this.db) throw new Error('Database not connected'); + const collection = this.db.collection('bearer_tokens'); + const result = await collection.updateOne({ token: currentToken }, { $set: { token: newToken } }); + return result.modifiedCount > 0; + } +} + +export default MongoDbClient; diff --git a/src/handlers/basicHandler.ts b/src/handlers/basicHandler.ts index ca6da9b..7c9b3bb 100644 --- a/src/handlers/basicHandler.ts +++ b/src/handlers/basicHandler.ts @@ -1,40 +1,47 @@ import { Request, Response } from 'express'; -import dbClient from '../dbcode/dbClient'; +import { dbClient } from '../dbcode/dbSetup'; -export function ping(req: Request, res: Response) { +export async function ping(req: Request, res: Response) { res.send('Pong!'); } -export function safePing(req: Request, res: Response) { +export async function safePing(req: Request, res: Response) { res.send('Safe Pong!'); } -export function getCurrentToken(req: Request, res: Response) { - const token = dbClient.getCurrentBearerTokenDB(); - if (token) { - res.json({ token }); - } else { - res.status(404).json({ error: 'No Bearer Token set' }); +export async function getCurrentToken(req: Request, res: Response) { + try { + const token = await dbClient.getCurrentBearerTokenDB(); + if (token) { + res.json({ token }); + } else { + res.status(404).json({ error: 'No Bearer Token set' }); + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); } } -export function setNewBearerToken(req: Request, res: Response) { +export async function setNewBearerToken(req: Request, res: Response) { const { token } = req.body; if (!token) { return res.status(400).json({ error: 'Token is required' }); } - const result = dbClient.setBearerTokenDB(token); - - if (result) { - res.status(201).json({ message: 'Bearer Token set successfully' }); - } else { - res.status(409).json({ error: 'Bearer Token has already been set' }); + try { + const result = await dbClient.setBearerTokenDB(token); + if (result) { + res.status(201).json({ message: 'Bearer Token set successfully' }); + } else { + res.status(409).json({ error: 'Bearer Token has already been set' }); + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); } } -export function changeBearerToken(req: Request, res: Response) { +export async function changeBearerToken(req: Request, res: Response) { const authHeader = req.headers.authorization; const { newToken } = req.body; @@ -48,15 +55,19 @@ export function changeBearerToken(req: Request, res: Response) { const currentToken = authHeader.split(' ')[1]; - if (!dbClient.isValidBearerTokenDB(currentToken)) { - return res.status(401).json({ error: 'Invalid Bearer Token' }); - } - - const result = dbClient.changeBearerTokenDB(currentToken, newToken); + try { + const isValid = await dbClient.isValidBearerTokenDB(currentToken); + if (!isValid) { + return res.status(401).json({ error: 'Invalid Bearer Token' }); + } - if (result) { - res.json({ message: 'Bearer Token changed successfully' }); - } else { - res.status(500).json({ error: 'Failed to change Bearer Token' }); + const result = await dbClient.changeBearerTokenDB(currentToken, newToken); + if (result) { + res.json({ message: 'Bearer Token changed successfully' }); + } else { + res.status(500).json({ error: 'Failed to change Bearer Token' }); + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); } } diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 5e32576..297bffa 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,16 +1,20 @@ import { Request, Response, NextFunction } from 'express'; -import dbClient from '../dbcode/dbClient'; +import { dbClient } from '../dbcode/dbSetup'; -export function authenticateToken(req: Request, res: Response, next: NextFunction) { +export async function authenticateToken(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized: No bearer token provided' }); } const bearerToken = authHeader.split(' ')[1]; - if (!dbClient.isValidBearerTokenDB(bearerToken)) { - return res.status(401).json({ error: 'Unauthorized: Invalid bearer token' }); + try { + const isValid = await dbClient.isValidBearerTokenDB(bearerToken); + if (!isValid) { + return res.status(401).json({ error: 'Unauthorized: Invalid bearer token' }); + } + next(); + } catch (error) { + return res.status(500).json({ error: 'Internal Server Error' }); } - - next(); } diff --git a/src/types/dbInterfaces.ts b/src/types/dbInterfaces.ts index e5f5ca6..b70372b 100644 --- a/src/types/dbInterfaces.ts +++ b/src/types/dbInterfaces.ts @@ -5,8 +5,8 @@ export interface ITokenResult { } export interface IDbClient { - setBearerTokenDB(token: string): boolean; - isValidBearerTokenDB(token: string): boolean; - getCurrentBearerTokenDB(): string | null; - changeBearerTokenDB(currentToken: string, newToken: string): boolean; + setBearerTokenDB(token: string): Promise; + isValidBearerTokenDB(token: string): Promise; + getCurrentBearerTokenDB(): Promise; + changeBearerTokenDB(currentToken: string, newToken: string): Promise; } diff --git a/yarn.lock b/yarn.lock index 4a1b9f6..34a4b23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -180,6 +180,15 @@ __metadata: languageName: node linkType: hard +"@mongodb-js/saslprep@npm:^1.1.5": + version: 1.1.9 + resolution: "@mongodb-js/saslprep@npm:1.1.9" + dependencies: + sparse-bitfield: "npm:^3.0.3" + checksum: 10c0/2f391db96db7a69e6849cc906e4a0bcd14127ad0493f97fc4134790fa2f0d4af5abcd110382c9d97a996ca31766e0c358c2e08d154f6ddfa06144e2c8a036895 + languageName: node + linkType: hard + "@npmcli/agent@npm:^2.0.0": version: 2.2.2 resolution: "@npmcli/agent@npm:2.2.2" @@ -344,6 +353,22 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.3 + resolution: "@types/webidl-conversions@npm:7.0.3" + checksum: 10c0/ac2ccff93b95ac7c8ca73dc6064403181691bba7ea144296f462dc9108a07be16cbad7b9c704b3df706dcc5a117e1f7bf7fb27aeb75b09c0f3148de8aee11aff + languageName: node + linkType: hard + +"@types/whatwg-url@npm:^11.0.2": + version: 11.0.5 + resolution: "@types/whatwg-url@npm:11.0.5" + dependencies: + "@types/webidl-conversions": "npm:*" + checksum: 10c0/7a9b9252dee98df6db1ad62337daca7f59ae50d7a3406d14ac6b57168d406004359994f3371155e24f3cf12002c4cb8bbb0883bd4cefb9d7ee8e2b510bdd7f5e + languageName: node + linkType: hard + "abbrev@npm:^2.0.0": version: 2.0.0 resolution: "abbrev@npm:2.0.0" @@ -523,6 +548,7 @@ __metadata: form-data: "npm:^4.0.0" google-auth-library: "npm:^9.14.0" googleapis: "npm:^140.0.1" + mongodb: "npm:^6.9.0" open: "npm:^10.1.0" tsx: "npm:^4.16.5" typescript: "npm:^5.5.4" @@ -539,6 +565,13 @@ __metadata: languageName: node linkType: hard +"bson@npm:^6.7.0": + version: 6.8.0 + resolution: "bson@npm:6.8.0" + checksum: 10c0/0503bb2a4ce2e183bd06151bdb983a623cfde05c76fbb5a34e941c594f3a681b52333d61b37c7933b8733b0ba14607b3599d94b46635d90bb96b1e7cec51aa8f + languageName: node + linkType: hard + "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -1599,6 +1632,13 @@ __metadata: languageName: node linkType: hard +"memory-pager@npm:^1.0.2": + version: 1.5.0 + resolution: "memory-pager@npm:1.5.0" + checksum: 10c0/2596e80c99fee24f05bd8a20cde2ee899012c996f4ec361ac76ed6f009f34149d733ac6f76880106ccd6a66d062ad439357578d383d429df66ba1278f68806e9 + languageName: node + linkType: hard + "merge-descriptors@npm:1.0.1": version: 1.0.1 resolution: "merge-descriptors@npm:1.0.1" @@ -1761,6 +1801,50 @@ __metadata: languageName: node linkType: hard +"mongodb-connection-string-url@npm:^3.0.0": + version: 3.0.1 + resolution: "mongodb-connection-string-url@npm:3.0.1" + dependencies: + "@types/whatwg-url": "npm:^11.0.2" + whatwg-url: "npm:^13.0.0" + checksum: 10c0/758f6fddde603cbe584d77f71216fc333bff18b53a8524291bb9924754de39a36af812905f3295b85ae1b20ed0a0155e8176207d34fe229f9d10c672393b7f4f + languageName: node + linkType: hard + +"mongodb@npm:^6.9.0": + version: 6.9.0 + resolution: "mongodb@npm:6.9.0" + dependencies: + "@mongodb-js/saslprep": "npm:^1.1.5" + bson: "npm:^6.7.0" + mongodb-connection-string-url: "npm:^3.0.0" + peerDependencies: + "@aws-sdk/credential-providers": ^3.188.0 + "@mongodb-js/zstd": ^1.1.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: ">=6.0.0 <7" + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + "@aws-sdk/credential-providers": + optional: true + "@mongodb-js/zstd": + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + checksum: 10c0/2b6c241a3361d014464a2c446fc9a71bae7270a9d8f777ffb8d69f0decfc6047f7dd6315c76caaa301fd61476948c3570b4b54a0866af5968e89b3cf5afa218e + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -2248,6 +2332,15 @@ __metadata: languageName: node linkType: hard +"sparse-bitfield@npm:^3.0.3": + version: 3.0.3 + resolution: "sparse-bitfield@npm:3.0.3" + dependencies: + memory-pager: "npm:^1.0.2" + checksum: 10c0/248c6ff7b5e354735e1daac4059222a29c9d291dfcf265daf675d13515eeaac454cfcccd687c8d134f86698b39abd7ad4d7434f7272dd6f8e41a00f21aae4194 + languageName: node + linkType: hard + "sprintf-js@npm:^1.1.3": version: 1.1.3 resolution: "sprintf-js@npm:1.1.3"