Skip to content

Commit

Permalink
feat: add user api
Browse files Browse the repository at this point in the history
  • Loading branch information
Dun-sin committed Nov 5, 2023
1 parent 9698a81 commit adb81fe
Show file tree
Hide file tree
Showing 13 changed files with 1,062 additions and 125 deletions.
680 changes: 589 additions & 91 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
"@types/react-scroll-to-bottom": "^4.2.3",
"@types/socket.io": "^3.0.2",
"@types/uuid": "^9.0.6",
"@types/validator": "^13.11.5",
"axios": "^1.6.0",
"bad-words-next": "^2.2.1",
"crypto-js": "^4.2.0",
"emoji-picker-react": "^4.5.7",
"express-validator": "^7.0.1",
"lodash": "^4.17.21",
"markdown-it": "^13.0.2",
"mongoose": "^8.0.0",
Expand Down
66 changes: 66 additions & 0 deletions src/httpStatusCodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const statusCodes = {
CONTINUE: 100,
SWITCHING_PROTOCOLS: 101,
PROCESSING: 102,
EARLY_HINTS: 103,
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NON_AUTHORITATIVE_INFORMATION: 203,
NO_CONTENT: 204,
RESET_CONTENT: 205,
PARTIAL_CONTENT: 206,
MULTI_STATUS: 207,
ALREADY_REPORTED: 208,
IM_USED: 226,
MULTIPLE_CHOICES: 300,
MOVED_PERMANENTLY: 301,
FOUND: 302,
SEE_OTHER: 303,
NOT_MODIFIED: 304,
USE_PROXY: 305,
TEMPORARY_REDIRECT: 307,
PERMANENT_REDIRECT: 308,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
PAYMENT_REQUIRED: 402,
FORBIDDEN: 403,
NOT_FOUND: 404,
METHOD_NOT_ALLOWED: 405,
NOT_ACCEPTABLE: 406,
PROXY_AUTHENTICATION_REQUIRED: 407,
REQUEST_TIMEOUT: 408,
CONFLICT: 409,
GONE: 410,
LENGTH_REQUIRED: 411,
PRECONDITION_FAILED: 412,
PAYLOAD_TOO_LARGE: 413,
URI_TOO_LONG: 414,
UNSUPPORTED_MEDIA_TYPE: 415,
RANGE_NOT_SATISFIABLE: 416,
EXPECTATION_FAILED: 417,
I_M_A_TEAPOT: 418,
MISDIRECTED_REQUEST: 421,
UNPROCESSABLE_ENTITY: 422,
LOCKED: 423,
FAILED_DEPENDENCY: 424,
TOO_EARLY: 425,
UPGRADE_REQUIRED: 426,
PRECONDITION_REQUIRED: 428,
TOO_MANY_REQUESTS: 429,
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504,
HTTP_VERSION_NOT_SUPPORTED: 505,
VARIANT_ALSO_NEGOTIATES: 506,
INSUFFICIENT_STORAGE: 507,
LOOP_DETECTED: 508,
NOT_EXTENDED: 510,
NETWORK_AUTHENTICATION_REQUIRED: 511,
};

export default statusCodes;
31 changes: 1 addition & 30 deletions src/lib/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,6 @@ async function init() {
}
}

/**
*
* @param {ActiveUser[]} users
*
* @return {Promise<Chat>}
*/
async function createChat(users: ActiveUserType[]) {
const _chat: {
_id: mongoose.Types.ObjectId;
Expand Down Expand Up @@ -223,19 +217,10 @@ async function closeChat(chatId: string) {
return inactiveList;
}

/**
*
* @param {string} chatId
*/
function chatExists(chatId: string) {
return Boolean(getChat(chatId));
}

/**
*
* @param {string} chatId
* @param {Message} message
*/
async function addMessage(
chatId: string,
{
Expand Down Expand Up @@ -395,28 +380,17 @@ function getRandomPairFromWaitingList() {
return pairedUsers;
}

/**
* @param {string} emailOrLoginId
*/
function isUserActive(id: string) {
return Boolean(activeUsers[id]);
}

/**
*
* @param {{
* loginId: string,
* email?: null | string,
* socket: Socket
* }} param0
*/
function addToWaitingList({
loginId,
email,
socket,
}: {
loginId: string;
email: string;
email?: string;
socket: Socket;
}) {
waitingUsers[loginId] = new Proxy(
Expand Down Expand Up @@ -453,14 +427,11 @@ export {
addMessage,
removeMessage,
editMessage,
getChatsCount,
addActiveUser,
getWaitingUserLen,
delWaitingUser,
getRandomPairFromWaitingList,
isUserActive,
getActiveUser,
addToWaitingList,
delActiveUser,
seenMessage,
};
150 changes: 150 additions & 0 deletions src/lib/userAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { NextApiRequest, NextApiResponse } from 'next';

import validator from 'validator';
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';

import { UserModel } from '@/models/UserModel';
import statusCodes from '@/httpStatusCodes';
import connectMongo from '@/mongo';

let accessToken = process.env.ACCESS_TOKEN;
const clientId = process.env.clientId;
const clientSecret = process.env.clientSecret;
const domain = process.env.DOMAIN;

export const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
};

export const createUserWithAutoId = async (email: string) => {
// Logic to create a new user with an autogenerated ID
return UserModel.create({ _id: uuidv4(), email });
};

// Define separate email validation middleware
export const emailValidator = async (
req: NextApiRequest,
res: NextApiResponse,
next: Function
) => {
const { email: emailBody } = req.body;
const { email: emailQuery } = req.query;

const isItInTheBody =
typeof emailBody !== 'string' || !validator.isEmail(emailBody);
const isItInQuery =
typeof emailQuery !== 'string' || !validator.isEmail(emailQuery);

if (isItInTheBody || isItInQuery) {
return res.status(statusCodes.NOT_ACCEPTABLE).json({
message: 'Email is invalid',
});
} else {
await connectMongo();
next();
}
};

export const getAccessToken = async () => {
try {
const response = await axios.post(`${domain}/oauth2/token`, null, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
audience: `${domain}/api`,
},
});

if (response.status !== 200) {
throw new Error(`Couldn't get access token`);
}

return response.data.access_token;
} catch (error) {
console.error('An error occurred:', error);
}
};

export const getKindeUser = async (email: string) => {
const response = await axios.get(`${domain}/api/v1/users?email=${email}`, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
let data;
if (response.status !== 200) {
const errorText = response.data;
if (errorText.errors[1].code === 'TOKEN_INVALID') {
const newAccessToken = await getAccessToken();
accessToken = newAccessToken;

const response = await axios.get(
`${domain}/api/v1/users?email=${email}`,
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${newAccessToken}`,
},
}
);

data = response.data;
} else {
console.log(errorText);
throw new Error(`Couldn't get user from kinde`);
}
} else {
data = response.data;
}
return data;
};

export const createUserWithId = async (email: string, id: string) => {
// Logic to create a new user with a provided ID
const getUser = await getKindeUser(email);
const doesUserExist = getUser.users ? true : false;

if (doesUserExist) {
return UserModel.create({ _id: id, email });
}

const inputBody = {
identities: [
{
type: 'email',
details: {
email: email,
},
},
],
};
const response = await axios.post(`${domain}/api/v1/user`, inputBody, {
headers: headers,
});

if (response.status !== 200) {
const errorText = response.data;
if (errorText.errors[1].code === 'TOKEN_INVALID') {
const newAccessToken = await getAccessToken();
accessToken = newAccessToken;

await axios.post(`${domain}/api/v1/user`, inputBody, {
headers: headers,
});
} else {
throw new Error(`Couldn't add user to kinde`);
}
}

return UserModel.create({ _id: id, email });
};
61 changes: 61 additions & 0 deletions src/mongo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import mongoose, { Connection, Error, Mongoose } from 'mongoose';
import { init } from './lib/lib';

// Initialize variables to store the MongoDB connection and Mongoose instance.
let connection: Connection | null = null;
let mongooseInstance: Mongoose | null = null;

// Retrieve the MongoDB connection string from environment variables.
const connectionString = process.env.MongoDB_URL;

// Async function to establish and return a MongoDB connection.
const connectMongo = async (): Promise<Connection> => {
// Check if the connection string is defined in the environment.
if (!connectionString) {
throw new Error('Connection String is missing, add it to the env file');
}

// If a connection already exists, return it.
if (connection) {
return connection;
}

try {
// Define connection options for MongoDB.
const options = {
autoIndex: true,
family: 4,
maxPoolSize: 10,
useNewUrlParser: true,
useUnifiedTopology: true,
};

mongoose.set('strictQuery', false);

// Connect to MongoDB and store the Mongoose instance.
mongooseInstance = await mongoose.connect(connectionString, options);

// Get the MongoDB connection from the Mongoose instance.
connection = mongooseInstance.connection;

init()
.then(done => console.log(`sorted the search`))
.catch(error => console.log(error));

// Log a successful database connection.
console.log('DB connection successful:', connection.name);

// Return the MongoDB connection.
return connection;
} catch (error) {
// Handle any errors that occur during the connection process.
const errorMessage = error as Error;
console.error('DB connection failed:', errorMessage.message);

// Re-throw the error to signal the failure.
throw error;
}
};

// Export the connectMongo function for use in other parts of your application.
export default connectMongo;
9 changes: 7 additions & 2 deletions src/pages/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import LogOutHandler from '@/sockets/logout';
import CloseChatHandler from '@/sockets/close';
import StopSearchHandler from '@/sockets/stopSearch';
import OnlineStatusHandler from '@/sockets/onlineStatus';
import connectMongo from '@/mongo';

interface SocketServer extends HTTPServer {
io?: IOServer | undefined;
Expand All @@ -36,14 +37,18 @@ const handler = createRouter<NextApiRequest, NextApiResponseWithSocket>();
// Enable CORS
handler.use(cors());

handler.all((_: NextApiRequest, res: NextApiResponseWithSocket) => {
handler.all(async (_: NextApiRequest, res: NextApiResponseWithSocket) => {
if (res.socket.server.io) {
console.log('Already set up');
res.end();
return;
}

const io = new Server(res.socket.server);
const io = new Server(res.socket.server, {
cors: { origin: '*' },
});

await connectMongo();

// Event handler for client connections
io.on('connection', (socket: Socket) => {
Expand Down
Loading

0 comments on commit adb81fe

Please sign in to comment.