Skip to content

Commit

Permalink
chore: create MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
hardikmodi1 committed Apr 24, 2021
1 parent 45d2eb1 commit 7274a95
Show file tree
Hide file tree
Showing 66 changed files with 9,232 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.env.production
.env
dist
.DS_Store
node_modules
out
yarn.lock
4 changes: 4 additions & 0 deletions api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
npm-debug.log
.env
4 changes: 4 additions & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
ACCESS_TOKEN_SECRET=
MONGO_URL=mongodb://localhost:27017/vstodo
23 changes: 23 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM node:14

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package.json ./
COPY yarn.lock ./

RUN yarn

COPY . .
COPY .env.production .env

RUN yarn build

ENV NODE_ENV production

EXPOSE 3002
CMD [ "node", "dist/index.js" ]
USER node
41 changes: 41 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch": "tsc -w",
"build": "tsc",
"dev": "nodemon dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"browserslist": "^4.16.3",
"browserslist-useragent": "^3.0.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.11.17",
"passport": "^0.4.1",
"passport-github": "^1.1.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/cors": "^2.8.10",
"@types/express": "^4.17.11",
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose": "^5.10.3",
"@types/node": "^14.14.27",
"@types/passport": "^1.0.6",
"@types/passport-github": "^1.1.5",
"@types/uuid": "^8.3.0",
"nodemon": "^2.0.7",
"typescript": "^4.1.5"
},
"browserslist": [
"ie <= 11"
]
}
134 changes: 134 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
require("dotenv").config();
// lib
import express from "express";
import mongoose from "mongoose";
import passport from "passport";
import { Strategy as GithubStrategy } from "passport-github";
import { sign, verify } from "jsonwebtoken";
import cors from "cors";

// DB Models
import User from "./models/User";

// routes
import boardRoutes from "./routes/board";
import todoListRoutes from "./routes/todoList";

const main = async () => {
console.log(process.env, "check env variables here");
const app = express();
passport.serializeUser((user: any, done) => {
done(null, user.accessToken);
});
app.use(cors({ origin: "*" }));
app.use(passport.initialize());
app.use(express.json());

passport.use(
new GithubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
callbackURL:
process.env.NODE_ENV === "production"
? process.env.CALLBACK_URL
: "http://localhost:3002/auth/github/callback",
},
async (_, __, profile, cb) => {
let user = await User.findOne({ githubId: profile.id });
if (user) {
user.name = profile.displayName;
user.email = profile.emails?.[0]?.value;
await user.save();
} else {
user = await (
await User.create({
githubId: profile.id,
name: profile.displayName,
email: profile.emails?.[0]?.value,
})
).save();
}

cb(null, {
accessToken: sign(
{ userId: user.id },
process.env.ACCESS_TOKEN_SECRET!,
{
expiresIn: "1y",
}
),
});
}
)
);

app.get("/auth/github", passport.authenticate("github", { session: false }));

app.get(
"/auth/github/callback",
passport.authenticate("github", { session: false }),
(req: any, res) => {
res.redirect(`http://localhost:54321/auth/${req.user.accessToken}`);
}
);

app.get("/me", async (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.send({ user: null });
return;
}
const token = authHeader.split(" ")[1];
if (!token) {
res.send({ user: null });
return;
}

let userId = "";

try {
const payload: any = verify(token, process.env.ACCESS_TOKEN_SECRET!);
userId = payload.userId;
} catch (err) {
res.send({ user: null });
return;
}

if (!userId) {
res.send({ user: null });
return;
}
const user = await User.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(userId) } },
{
$lookup: {
from: "boards",
localField: "_id",
foreignField: "creatorId",
as: "boards",
},
},
]);
res.send({ user: user[0] });
});

app.use("/board", boardRoutes);
app.use("/todo-list", todoListRoutes);

// connect to database
mongoose.connect(process.env.MONGO_URL!, {
useNewUrlParser: true,
useUnifiedTopology: true,
});

mongoose.connection.once("open", () =>
console.log("connected To MongoDB", process.env)
);

// test url
app.get("/", (_, res) => res.send("Hello Here"));
app.listen(3002, () => console.log(`listening on port 3002`));
};

main();
22 changes: 22 additions & 0 deletions api/src/middlewares/isAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RequestHandler } from "express";
import { verify } from "jsonwebtoken";

export const isAuth: RequestHandler<{}, any, any, {}> = (req, _, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
throw new Error("Not Authenticated");
}
const token = authHeader.split(" ")[1];
if (!token) {
throw new Error("Not Authenticated");
}

try {
const payload: any = verify(token, process.env.ACCESS_TOKEN_SECRET!);
(req as any).userId = payload.userId;
next();
return;
} catch {}

throw new Error("Not Authenticated");
};
19 changes: 19 additions & 0 deletions api/src/models/Board.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import mongoose, { Schema } from "mongoose";

export interface IBoard extends mongoose.Document {
name: string;
creatorId: string;
todoLists?: Array<string>;
}

const BoardSchema = new Schema({
creatorId: { type: Schema.Types.ObjectId },
name: { type: String },
todoLists: [
{
type: Schema.Types.ObjectId,
},
],
});

export default mongoose.model<IBoard>("Board", BoardSchema);
22 changes: 22 additions & 0 deletions api/src/models/TodoList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import mongoose, { Schema } from "mongoose";

export interface ITodoList extends mongoose.Document {
boardId: string;
creatorId: string;
title: string;
todos: Array<{ _id: string; todo: string }>;
}

export const Todo = new Schema({
_id: { type: String, require: true },
todo: { type: String, require: true },
});

const TodoListSchema = new Schema({
boardId: { type: Schema.Types.ObjectId },
creatorId: { type: Schema.Types.ObjectId },
title: { type: String, require: true },
todos: [Todo],
});

export default mongoose.model<ITodoList>("TodoList", TodoListSchema);
15 changes: 15 additions & 0 deletions api/src/models/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import mongoose from "mongoose";

export interface IUser extends mongoose.Document {
name?: string;
email?: string;
githubId?: string;
}

const UserSchema = new mongoose.Schema({
githubId: { type: String, unique: true },
name: { type: String },
email: { type: String },
});

export default mongoose.model<IUser>("User", UserSchema);
46 changes: 46 additions & 0 deletions api/src/routes/board.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import express from "express";

import Board from "../models/Board";
import TodoList from "../models/TodoList";

import { isAuth } from "../middlewares/isAuth";

const routes = express.Router();

routes.post("/", isAuth, async (req: any, res) => {
const board = await Board.create({
creatorId: req.userId,
name: req.body.name,
});
await board.save();
res.send({ board });
});

routes.delete("/", isAuth, async (req: any, res) => {
const board = await Board.findById(req.body.id);
if (board && board.creatorId.toString() === req.userId) {
const promises = [];
promises.push(Board.findByIdAndDelete(req.body.id));
promises.push(TodoList.deleteMany({ boardId: board._id }));
await Promise.all(promises);
return res.send({ success: true });
}
return res.send({ success: false });
});

routes.put("/", isAuth, async (req: any, res) => {
const board = await Board.findById(req.body._id);
if (!board) {
return res.send({ success: false });
}
if (board.creatorId.toString() === req.userId) {
if (req.body.name?.trim?.()) {
board.name = req.body.name.trim();
}
await board.save();
return res.send({ success: true, board });
}
return res.send({ success: false });
});

export default routes;
Loading

0 comments on commit 7274a95

Please sign in to comment.