Skip to content

Commit 71df6ee

Browse files
committedMay 27, 2022
initial comment
0 parents  commit 71df6ee

15 files changed

+2035
-0
lines changed
 

‎.env

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SERVER_PORT="8000"
2+
SERVER_HOSTNAME=""

‎.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SERVER_PORT="8000"
2+
SERVER_HOSTNAME=""

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/build
2+
/node_modules

‎.prettierrc

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"singleQuote": true,
3+
"printWidth": 200,
4+
"proseWrap": "always",
5+
"tabWidth": 4,
6+
"useTabs": false,
7+
"trailingComma": "none",
8+
"bracketSpacing": true,
9+
"jsxBracketSameLine": false,
10+
"semi": true
11+
}

‎package-lock.json

+1,612
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "server",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "src/index.ts",
6+
"scripts": {
7+
"start": "nodemon src/index.ts",
8+
"build": "rm -rf build/ && prettier --write src/ && tsc"
9+
},
10+
"author": "zaman",
11+
"license": "ISC",
12+
"dependencies": {
13+
"body-parser": "^1.20.0",
14+
"cors": "^2.8.5",
15+
"dotenv": "^16.0.1",
16+
"express": "^4.18.1",
17+
"nodemon": "^2.0.16",
18+
"prettier": "^2.6.2",
19+
"ts-node": "^10.8.0",
20+
"typescript": "^4.7.2",
21+
"websocket": "^1.0.34"
22+
},
23+
"devDependencies": {
24+
"@types/body-parser": "^1.19.2",
25+
"@types/cors": "^2.8.12",
26+
"@types/express": "^4.17.13",
27+
"@types/node": "^17.0.35",
28+
"@types/websocket": "^1.0.5"
29+
}
30+
}

‎src/config/config.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import dotenv from 'dotenv';
2+
3+
dotenv.config();
4+
5+
const SERVER_HOSTNAME = process.env.SERVER_HOSTNAME || 'localhost';
6+
const SERVER_PORT = process.env.SERVER_PORT || 8000;
7+
8+
const SERVER = {
9+
hostname: SERVER_HOSTNAME,
10+
port: SERVER_PORT
11+
};
12+
13+
const config = {
14+
server: SERVER
15+
};
16+
17+
export default config;

‎src/config/logging.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const info = (namespace: string, message: string, object?: any) => {
2+
if (object) {
3+
console.info(`[${getTimeStamp()}] [INFO] [${namespace}] ${message}`, object);
4+
} else {
5+
console.info(`[${getTimeStamp()}] [INFO] [${namespace}] ${message}`);
6+
}
7+
};
8+
9+
const warn = (namespace: string, message: string, object?: any) => {
10+
if (object) {
11+
console.warn(`[${getTimeStamp()}] [WARN] [${namespace}] ${message}`, object);
12+
} else {
13+
console.warn(`[${getTimeStamp()}] [WARN] [${namespace}] ${message}`);
14+
}
15+
};
16+
17+
const error = (namespace: string, message: string, object?: any) => {
18+
if (object) {
19+
console.error(`[${getTimeStamp()}] [ERROR] [${namespace}] ${message}`, object);
20+
} else {
21+
console.error(`[${getTimeStamp()}] [ERROR] [${namespace}] ${message}`);
22+
}
23+
};
24+
25+
const debug = (namespace: string, message: string, object?: any) => {
26+
if (object) {
27+
console.debug(`[${getTimeStamp()}] [DEBUG] [${namespace}] ${message}`, object);
28+
} else {
29+
console.debug(`[${getTimeStamp()}] [DEBUG] [${namespace}] ${message}`);
30+
}
31+
};
32+
33+
const getTimeStamp = (): string => {
34+
return new Date().toISOString();
35+
};
36+
37+
export default {
38+
info,
39+
warn,
40+
error,
41+
debug
42+
};

‎src/controllers/authController.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextFunction, Request, Response } from 'express';
2+
import { randomUUID } from 'crypto';
3+
import User from '../db/users';
4+
import logging from '../config/logging';
5+
6+
const loginUser = (req: Request, res: Response, next: NextFunction) => {
7+
const { name } = req?.body;
8+
console.log('username', req?.body);
9+
if (name) {
10+
const user = User.getUser(name);
11+
if (user) {
12+
logging.info('User', `User ${name} logged in`);
13+
return res.status(200).json(user);
14+
} else {
15+
const user = User.addUser({ name: name, id: randomUUID() });
16+
logging.info('User', `User ${name} logged in`);
17+
return res.status(200).json(user);
18+
}
19+
}
20+
};
21+
22+
export default { loginUser };

‎src/db/notes.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
interface Note {
2+
id: string;
3+
userId: string;
4+
author: string;
5+
bgColor: string;
6+
text: string;
7+
placement: {
8+
x: number;
9+
y: number;
10+
};
11+
}
12+
13+
const notes: Note[] = [];
14+
15+
const getNotes = () => {
16+
return notes;
17+
};
18+
19+
const getNote = (id: string) => {
20+
return notes.find((note) => note.id === id);
21+
};
22+
const addNote = (note: Note) => {
23+
notes.push(note);
24+
return note;
25+
};
26+
27+
const updateNote = (note: Note) => {
28+
const index = notes.findIndex((n) => n.id === note.id);
29+
if (index === -1) return;
30+
return (notes[index] = note);
31+
};
32+
33+
export default {
34+
getNotes,
35+
getNote,
36+
addNote,
37+
updateNote
38+
};

‎src/db/users.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
interface User {
2+
id: string;
3+
name: string;
4+
}
5+
const users: User[] = [];
6+
7+
const getUsers = (): User[] => {
8+
return users;
9+
};
10+
11+
const getUser = (name: string): User | undefined => {
12+
return users.find((user) => user.name === name);
13+
};
14+
15+
const addUser = (user: User): User | undefined => {
16+
if (getUser(user.name)) return;
17+
users.push(user);
18+
return user;
19+
};
20+
21+
export default {
22+
getUsers,
23+
getUser,
24+
addUser
25+
};

‎src/index.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import http from 'http';
2+
import bodyParser from 'body-parser';
3+
import express from 'express';
4+
import cors from 'cors';
5+
import authRoutes from './routes/authRoute';
6+
import logging from './config/logging';
7+
import connectWebSocketServer from './services/websocket';
8+
import config from './config/config';
9+
const NAMESPACE = 'Server';
10+
const router = express();
11+
router.use(cors());
12+
13+
/** Log the request */
14+
router.use((req, res, next) => {
15+
/** Log the req */
16+
logging.info(NAMESPACE, `METHOD: [${req.method}] - URL: [${req.url}] - IP: [${req.socket.remoteAddress}]`);
17+
18+
res.on('finish', () => {
19+
/** Log the res */
20+
logging.info(NAMESPACE, `METHOD: [${req.method}] - URL: [${req.url}] - STATUS: [${res.statusCode}] - IP: [${req.socket.remoteAddress}]`);
21+
});
22+
23+
next();
24+
});
25+
26+
/** Parse the body of the request */
27+
router.use(bodyParser.urlencoded({ extended: true }));
28+
router.use(bodyParser.json());
29+
30+
/** Rules of our API */
31+
router.use((req, res, next) => {
32+
res.header('Access-Control-Allow-Origin', '*');
33+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
34+
35+
if (req.method == 'OPTIONS') {
36+
res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
37+
return res.status(200).json({});
38+
}
39+
40+
next();
41+
});
42+
43+
/** Routes go here */
44+
router.use('/api/auth', authRoutes);
45+
46+
/** Error handling */
47+
router.use((req, res, next) => {
48+
const error = new Error('Not found');
49+
50+
res.status(404).json({
51+
message: error.message
52+
});
53+
});
54+
55+
/** Create the server */
56+
57+
const httpServer = http.createServer(router);
58+
59+
httpServer.listen(config.server.port, () => logging.info(NAMESPACE, `Server is running ${config.server.hostname}:${config.server.port}`));
60+
connectWebSocketServer(httpServer);

‎src/routes/authRoute.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import express from 'express';
2+
import controller from '../controllers/authController';
3+
4+
const router = express.Router();
5+
6+
router.post('/login', controller.loginUser);
7+
8+
export default router;

‎src/services/websocket.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import websocket from 'websocket';
2+
import http from 'http';
3+
import { randomUUID } from 'crypto';
4+
import logging from '../config/logging';
5+
import Notes from '../db/notes';
6+
7+
let clients: {
8+
userID: string;
9+
connection: websocket.connection;
10+
}[] = [];
11+
12+
const connectWebSocketServer = async (expressServer: http.Server) => {
13+
const websocketServer = new websocket.server({
14+
httpServer: expressServer
15+
});
16+
websocketServer.on('request', function (request) {
17+
const userID = randomUUID();
18+
logging.info('WebSocket', `${new Date()} Received a new connection from origin: ${request.origin}.`);
19+
const connection = request.accept(null, request.origin);
20+
clients?.push({ userID, connection: connection });
21+
logging.info('WebSocket', `connected: ${userID} in ${clients?.find((client) => client.userID === userID)?.userID}`);
22+
const notes = Notes?.getNotes();
23+
const notesToSend = {
24+
type: 'notes',
25+
data: notes
26+
};
27+
connection.send(JSON.stringify(notesToSend));
28+
connection.on('close', function () {
29+
clients = clients.filter((client) => client.userID === userID);
30+
console.log('No', clients?.length);
31+
logging.info('WebSocket', `${new Date()} Connection closed. Connection ID: ${userID}`);
32+
});
33+
connection.on('message', function (message) {
34+
if (message.type === 'utf8') {
35+
console.log("Received: '" + message.utf8Data + "'");
36+
const { type, data } = JSON.parse(message.utf8Data);
37+
if (type === 'note') {
38+
const { id, userId, author, bgColor, text, placement } = data;
39+
let note = id ? Notes.getNote(id) : null;
40+
if (note) {
41+
note = Notes.updateNote(data);
42+
} else {
43+
note = Notes.addNote({ id: randomUUID(), userId: userId, author: author, bgColor: bgColor, text: text, placement: placement });
44+
}
45+
console.log('MEME', clients?.length);
46+
//broadcast the message to all the clients
47+
const sendNote = {
48+
type: 'note',
49+
data: note
50+
};
51+
clients?.forEach((client) => {
52+
client?.connection?.send(JSON.stringify(sendNote));
53+
console.log('Sent: ' + JSON.stringify(sendNote));
54+
});
55+
}
56+
}
57+
});
58+
});
59+
};
60+
61+
export default connectWebSocketServer;

‎tsconfig.json

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"compilerOptions": {
3+
/* Visit https://aka.ms/tsconfig to read more about this file */
4+
5+
/* Projects */
6+
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7+
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8+
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9+
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10+
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11+
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12+
13+
/* Language and Environment */
14+
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15+
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16+
// "jsx": "preserve", /* Specify what JSX code is generated. */
17+
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18+
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19+
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20+
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21+
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22+
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23+
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24+
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25+
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26+
27+
/* Modules */
28+
"module": "commonjs" /* Specify what module code is generated. */,
29+
// "rootDir": "./", /* Specify the root folder within your source files. */
30+
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31+
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32+
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33+
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34+
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35+
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
36+
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37+
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38+
// "resolveJsonModule": true, /* Enable importing .json files. */
39+
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
40+
41+
/* JavaScript Support */
42+
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43+
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44+
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45+
46+
/* Emit */
47+
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48+
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
49+
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50+
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51+
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52+
"outDir": "./build" /* Specify an output folder for all emitted files. */,
53+
// "removeComments": true, /* Disable emitting comments. */
54+
// "noEmit": true, /* Disable emitting files from a compilation. */
55+
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56+
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57+
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58+
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59+
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60+
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61+
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62+
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63+
// "newLine": "crlf", /* Set the newline character for emitting files. */
64+
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65+
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66+
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67+
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68+
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69+
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70+
71+
/* Interop Constraints */
72+
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73+
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
75+
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
77+
78+
/* Type Checking */
79+
"strict": true /* Enable all strict type-checking options. */,
80+
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81+
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82+
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83+
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84+
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85+
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86+
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88+
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89+
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91+
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92+
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93+
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94+
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95+
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96+
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97+
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98+
99+
/* Completeness */
100+
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
102+
}
103+
}

0 commit comments

Comments
 (0)
Please sign in to comment.