Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions server/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.space
index.js
34 changes: 34 additions & 0 deletions server/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"root": true,
"env": {
"node": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "prettier"],
"rules": {
"no-unused-vars": ["error"],
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"no-console": ["error"],
"prettier/prettier": ["error"]
},
"settings": {
"import/resolver": {
"typescript": {}
}
}
}
26 changes: 26 additions & 0 deletions server/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"prettier/prettier": ["error", {}],
"quotes": ["error", "double"],
"semi": ["error", "always"]
}
}
15 changes: 15 additions & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Deta files
.deta/
.space


# Compiled javascript files
dist/

# Node
node_modules/
yarn.lock
package-lock.json

# env variables
.env
8 changes: 8 additions & 0 deletions server/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": false,
"trailingComma": "none",
"bracketSpacing": true
}
5 changes: 5 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Import the compiled dist js
const app = require("./dist/index.js");

// Export it to Deta
module.exports = app;
35 changes: 35 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "deta-typescript-express-starter",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "npx nodemon --watch src src/index.ts",
"build": "npx tsc",
"deploy": "npx tsc && deta deploy",
"lint-fix": "eslint --fix . --ext .ts"
},
"dependencies": {
"argon2": "^0.31.2",
"cors": "^2.8.5",
"deta": "^1.1.0",
"dotenv": "^16.0.3",
"express": "^4.18.1",
"nodemailer": "^6.9.7",
"outlook-nodemailer-transport": "^1.4.1"
},
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.13",
"@types/node": "14",
"@types/nodemailer": "^6.4.14",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-prettier": "^4.2.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.3"
}
}
Empty file removed server/placeholder.txt
Empty file.
28 changes: 28 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import express from "express";
import cors from "cors";
import auth from "./routes/auth";
import task from "./routes/task";
import board from "./routes/board";

const app = express();
app.use(express.json());
app.use(cors());
app.disable("etag");

app.get("/", (req, res) =>
res.status(200).json({
msg: "This is the API of the following repository on GitHub: https://github.com/janlehner/taskhub"
})
);

// routes
app.use("/auth", auth);
app.use("/task", task);
app.use("/board", board);

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
console.log(`listening on port ${port}`);
});

module.exports = app;
26 changes: 26 additions & 0 deletions server/src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface ILoginForm {
username: string;
password: string;
}

export interface IRegisterForm {
username: string;
password: string;
}

export interface IJWTData {
username: string;
}

export interface ITask {
title: string;
definition: string;
owner: string;
board: string;
}

export interface IBoard {
owner: string;
title: string;
tasks: ITask[];
}
78 changes: 78 additions & 0 deletions server/src/routes/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import express from "express";
import * as dotenv from "dotenv";
import path from "path";
import { Deta } from "deta";
import argon2 from "argon2";
import { ILoginForm, IRegisterForm } from "../interfaces/interfaces";
import jwt from "jsonwebtoken";
dotenv.config({ path: path.resolve(__dirname, "../.env") });

// deta setup
const projectKey: string = process.env.DETA_PROJECT_KEY;
const deta = Deta(projectKey);
const auth = deta.Base("users");

const jwtSecret: string = process.env.JWT_SECRET;

const router = express.Router();


router.post("/register", async (req, res) => {
try {
const registrationFormData: IRegisterForm = req.body as IRegisterForm;

if (registrationFormData.username == null || registrationFormData.password == null || registrationFormData.username == "" || registrationFormData.password == "") {
throw new Error("Invalid Request");
}
if (!(await auth.get(registrationFormData.username))) {
const passwordHash = await argon2.hash(registrationFormData.password);
const auhtFormDataJson = {
key: registrationFormData.username,
password: passwordHash
};
const newUser = await auth.insert(auhtFormDataJson);

res.status(201).json({
username: newUser.key,
success: true
});
} else {
throw new Error("Failed to register user!");
}
} catch (err) {
res.status(503).json({ error: err.message });
}
});

router.post("/login", async (req, res) => {
try {
const loginFormData: ILoginForm = req.body as ILoginForm;

if (loginFormData.username == null || loginFormData.password == null || loginFormData.username == "" || loginFormData.password == "") {
throw new Error("Invalid Request");
}

const user = await auth.get(loginFormData.username);
if (user === null) {
throw new Error("Wrong credentials! Please try again.");
}

const password = user.password as string;

if (await argon2.verify(password, loginFormData.password)) {
const token = jwt.sign({ username: user.key }, jwtSecret, { expiresIn: "21600s" });
res.status(200).json({ token, success: true });
} else {
res.status(401).json({
error: "Wrong credentials! Please try again.",
success: false
});
}
} catch (err) {
res.status(503).json({ error: err.message });
}
});



export default router;
50 changes: 50 additions & 0 deletions server/src/routes/board.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import express from "express";
import * as dotenv from "dotenv";
import path from "path";
import { Deta } from "deta";
import { ITask, IBoard } from "../interfaces/interfaces";
dotenv.config({ path: path.resolve(__dirname, "../.env") });

// deta setup
const projectKey: string = process.env.DETA_PROJECT_KEY;
const deta = Deta(projectKey);
const boardSets = deta.Base("board");

const router = express.Router();

router.post("/create", async (req, res) => {
try {
const boardDataSet: IBoard = req.body as IBoard;
if (typeof boardDataSet.title !== "string" || typeof boardDataSet.owner !== "string") {
throw new Error("Invalid 'title' or 'owner' in the request.");
}

const key = boardDataSet.title.trim() + boardDataSet.owner.trim();

if (await boardSets.get(boardDataSet.title)) {
throw new Error("This board name exists already. Please try to edit this!");
} else if (await boardSets.get(key)) {
throw new Error("This task name exists already. Please try to edit this!");
}

const tasks: ITask[] = [];

const boardDataSetJSON = {
key: key,
title: boardDataSet.title,
owner: boardDataSet.owner,
tasks: tasks
};

const newtaskDataSet = await boardSets.insert(JSON.parse(JSON.stringify(boardDataSetJSON)));

res.status(201).json({
title: boardDataSet.title,
owner: boardDataSet.owner
});
} catch (err) {
res.status(503).json({ error: err.message });
}
});

export default router;
Loading