Merge pull request #2 from ieeeucsd/ray-dev

code clean up, initiate eslint action
This commit is contained in:
Raymond Wang 2022-10-23 17:07:48 -07:00 committed by GitHub
commit 281d36fc2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1814 additions and 2667 deletions

20
.eslintrc.json Normal file
View file

@ -0,0 +1,20 @@
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/unbound-method": "warn"
},
"overrides": [],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"ignorePatterns": ["node_modules", "build", "webpack**", "src/public/"]
}

3483
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,53 +4,50 @@
"description": "Website for IEEE of UC San Diego",
"main": "build/index.js",
"scripts": {
"build": "webpack & tsc",
"watch": "npm-watch build"
},
"watch": {
"build": {
"patterns": [
"src"
],
"quiet": true,
"silent": true,
"extensions": "js,jsx,ts,tsx,html,css"
}
"build": "webpack & tsc --build --verbose & node build/index.js generate",
"start": "node build/index.js",
"watch": "webpack watch",
"generate": "node build/index.js generate",
"lint": "eslint ."
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.11.7",
"@types/react-dom": "^16.9.14",
"@types/ws": "^7.4.7",
"express": "^4.17.1",
"mongoose": "^6.1.7",
"react": "^16.14.0",
"react-dom": "^16.14.0"
"@types/express": "^4.17.14",
"@types/mongoose": "^5.11.97",
"@types/node": "^18.11.4",
"@types/react-dom": "^18.0.6",
"@types/ws": "^8.5.3",
"express": "^4.18.2",
"mongoose": "^6.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"@babel/core": "^7.19.6",
"@babel/preset-env": "^7.19.4",
"@babel/preset-react": "^7.14.5",
"@types/react": "^16.14.20",
"@types/react": "^18.0.21",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"babel-loader": "^8.2.3",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^5.2.7",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.10",
"html-webpack-plugin": "^4.5.2",
"license-webpack-plugin": "^2.3.21",
"style-loader": "^2.0.0",
"ts-loader": "^8.3.0",
"html-webpack-plugin": "^5.5.0",
"license-webpack-plugin": "^4.0.2",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1"
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}

View file

@ -25,7 +25,7 @@ export const ACTIVE_PAGES = [
// Urls of team photos that will go in the "About" slideshow
export const TEAM_PHOTOS: string[] = [];
export const PROJECT_SPACE: string[] = [];
export const EVENTS: any[] = [];
export const EVENTS: unknown[] = [];
export const SOCIALS = [
{
@ -44,6 +44,7 @@ export const SOCIALS = [
message: "@ieeeucsd",
},
];
export const EMAIL = [
{
icon: "img/email.svg",
@ -51,6 +52,7 @@ export const EMAIL = [
message: "ieee@eng.ucsd.edu",
},
];
export const EMAIL_WHITE = [
{
icon: "img/email_white.svg",

View file

@ -9,14 +9,12 @@ import SocialCard from "./components/SocialCard";
import Carousel from "./components/Carousel";
import Footer from "./components/Footer";
interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
class Main extends React.Component {
constructor() {
super({});
this.state = {};
}
public render() {
return (
<>
@ -67,6 +65,7 @@ class Main extends React.Component<MainProps, MainState> {
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
key={n.url} // Hacky fix: use url as key
url={n.url}
image={n.icon}
message={n.message}

View file

@ -38,7 +38,7 @@ export default class Carousel extends Component<CarouselProps, CarouselState> {
}
public render() {
let arr = this.chunkArray(this.props.items);
const arr = this.chunkArray(this.props.items);
return (
<div className="carousel">
<img
@ -52,6 +52,7 @@ export default class Carousel extends Component<CarouselProps, CarouselState> {
<div className="carousel-items">
{arr.map((items, i) => (
<CarouselPage
key={i}
items={items}
visible={i === this.state.page}
></CarouselPage>
@ -72,7 +73,7 @@ export default class Carousel extends Component<CarouselProps, CarouselState> {
}
private chunkArray(array: CarouselItemProps[]): CarouselItemProps[][] {
let returnArr = [] as CarouselItemProps[][];
const returnArr = [] as CarouselItemProps[][];
for (let i = 0; i < array.length; i += this.props.itemsPerPage) {
returnArr.push(array.slice(i, i + this.props.itemsPerPage));
}

View file

@ -7,12 +7,8 @@ export interface CarouselItemProps {
email: string;
photo: string;
}
interface CarouselItemState {}
export default class CarouselItem extends Component<
CarouselItemProps,
CarouselItemState
> {
export default class CarouselItem extends Component<CarouselItemProps> {
constructor(props: CarouselItemProps) {
super(props);
this.state = {};

View file

@ -6,12 +6,8 @@ interface CarouselPageProps {
items: CarouselItemProps[];
visible: boolean;
}
interface CarouselPageState {}
export default class CarouselPage extends Component<
CarouselPageProps,
CarouselPageState
> {
export default class CarouselPage extends Component<CarouselPageProps> {
constructor(props: CarouselPageProps) {
super(props);
this.state = {};
@ -24,7 +20,7 @@ export default class CarouselPage extends Component<
style={this.props.visible ? {} : { display: "none" }}
>
{this.props.items.map((n) => (
<CarouselItem {...n}></CarouselItem>
<CarouselItem {...n} key={n.name}></CarouselItem>
))}
</div>
);

View file

@ -5,13 +5,10 @@ interface DefaultSectionProps {
title: string;
paragraphs?: string[];
className?: string;
children?: React.ReactNode;
}
interface DefaultSectionState {}
export default class DefaultSection extends Component<
DefaultSectionProps,
DefaultSectionState
> {
export default class DefaultSection extends Component<DefaultSectionProps> {
constructor(props: DefaultSectionProps) {
super(props);
this.state = {};
@ -27,7 +24,7 @@ export default class DefaultSection extends Component<
<div className="section-title">{this.props.title}</div>
{(this.props.paragraphs ? this.props.paragraphs : []).map(
(n) => (
<p>{n}</p>
<p key={n}>{n}</p>
)
)}
{this.props.children}

View file

@ -3,12 +3,9 @@ import { Component } from "react";
import SocialCard from "./SocialCard";
import { EMAIL_WHITE, SOCIALS_WHITE } from "../Config";
interface FooterProps {}
interface FooterState {}
export default class Footer extends Component<FooterProps, FooterState> {
constructor(props: FooterProps) {
super(props);
export default class Footer extends Component {
constructor() {
super({});
this.state = {};
}
@ -19,6 +16,7 @@ export default class Footer extends Component<FooterProps, FooterState> {
<div className="footer-scls">
{[...EMAIL_WHITE, ...SOCIALS_WHITE].map((n) => (
<SocialCard
key={n.url}
url={n.url}
image={n.icon}
message={n.message}

View file

@ -6,12 +6,8 @@ interface InvolveBoxProps {
image: string;
description: string;
}
interface InvolveBoxState {}
export default class InvolveBox extends Component<
InvolveBoxProps,
InvolveBoxState
> {
export default class InvolveBox extends Component<InvolveBoxProps> {
constructor(props: InvolveBoxProps) {
super(props);
this.state = {};

View file

@ -6,12 +6,8 @@ interface SocialCardProps {
image: string;
message: string;
}
interface SocialCardState {}
export default class SocialCard extends Component<
SocialCardProps,
SocialCardState
> {
export default class SocialCard extends Component<SocialCardProps> {
constructor(props: SocialCardProps) {
super(props);
this.state = {};

View file

@ -21,7 +21,7 @@ export default class Splash extends Component<SplashProps, SplashState> {
this.interval = setInterval(
this.changeImage.bind(this),
this.props.delay
) as any as number;
) as unknown as number;
}
private changeImage(): void {

View file

@ -20,14 +20,18 @@ export default class TopBar extends Component<TopBarProps, TopBarState> {
showBurger: this.shouldShowBurger(),
menuVisible: false,
};
this.toggleMenu.bind(this);
window.addEventListener("resize", this.checkResize.bind(this));
}
private toggleMenu() {
this.setState({ menuVisible: !this.state.menuVisible });
}
private checkResize() {
this.setState({ showBurger: this.shouldShowBurger() });
}
private shouldShowBurger(): boolean {
return window.innerWidth < TopBar.HAMBURGER_POINT;
}
@ -48,7 +52,7 @@ export default class TopBar extends Component<TopBarProps, TopBarState> {
style={{
display: this.state.showBurger ? "initial" : "none",
}}
onClick={this.toggleMenu.bind(this)}
onClick={this.toggleMenu}
role="menubar"
>
@ -69,7 +73,7 @@ export default class TopBar extends Component<TopBarProps, TopBarState> {
>
{this.props.links.map((l) => {
return (
<a className="navlink" href={l.url}>
<a className="navlink" href={l.url} key={l.url}>
{l.name}
</a>
);

View file

@ -1,22 +1,18 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
import SocialCard from "./components/SocialCard";
import Carousel from "./components/Carousel";
import Footer from "./components/Footer";
interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
class Main extends React.Component {
constructor() {
super({});
this.state = {};
}
public render() {
return (
<>
@ -51,6 +47,7 @@ class Main extends React.Component<MainProps, MainState> {
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
key={n.url}
url={n.url}
image={n.icon}
message={n.message}

View file

@ -9,14 +9,12 @@ import SocialCard from "./components/SocialCard";
import Carousel from "./components/Carousel";
import Footer from "./components/Footer";
interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
class Main extends React.Component {
constructor() {
super({});
this.state = {};
}
public render() {
return (
<>
@ -84,6 +82,7 @@ class Main extends React.Component<MainProps, MainState> {
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
key={n.url}
url={n.url}
image={n.icon}
message={n.message}

View file

@ -1,22 +1,19 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
import SocialCard from "./components/SocialCard";
import Carousel from "./components/Carousel";
import Footer from "./components/Footer";
interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
class Main extends React.Component {
constructor() {
super({});
this.state = {};
}
public render() {
return (
<>
@ -78,6 +75,7 @@ class Main extends React.Component<MainProps, MainState> {
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
key={n.url} // Hacky fix: use url as key
url={n.url}
image={n.icon}
message={n.message}

View file

@ -1,236 +1,247 @@
import { Schema, Document, LeanDocument } from "mongoose";
import * as mongoose from "mongoose";
import { promisify } from "util";
import { randomBytes, pbkdf2 } from "crypto";
const pbkdf = promisify(pbkdf2);
const User = mongoose.model(
"User",
new Schema({
display: String,
email: {
public: Boolean,
text: String,
},
picture: String,
password: {
hash: Buffer,
salt: Buffer,
},
bio: { type: String, default: "" },
isOfficer: { type: Boolean, default: false },
title: { type: String, default: "Member" },
uuid: String,
})
);
export interface DBUser extends Document {
display: string;
email: string;
picture: string;
password: HashSalt;
isOfficer: boolean;
title: string;
uuid: string;
}
interface HashSalt {
hash: Buffer;
salt: Buffer;
}
export default class UserDatabase {
// How many iterations pbkdf2 will iterate the password hashing algorithm
private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64;
// How many bytes will be used to generate a UUID of length 2 times this variable
private static UUID_LENGTH = 32;
/**
* Makes a new UserDatabase
* @param url the mongodb database url to connect to
*/
constructor(url: string) {
mongoose.connect(url);
mongoose.set("returnOriginal", false);
}
// Functions to edit current users
/**
* Changes a user's display name on the website
* @param uuid the UUID of the user
* @param newName the user's new name
*/
public async setName(uuid: string, newName: string) {
await User.findOneAndUpdate({ uuid: uuid }, { name: newName }).exec();
}
/**
* Gets the display name of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's display name
*/
public async getName(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.display;
}
/**
* Updates a user's display email
* @param uuid the UUID of the user
* @param newEmail the user's new display email
*/
public async setEmail(uuid: string, newEmail: string) {
await User.findOneAndUpdate({ uuid: uuid }, { email: newEmail }).exec();
}
/**
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's email
*/
public async getEmail(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.email;
}
/**
* Updates a user's password
* @param uuid the UUID of the user
* @param newPassword the user's new password
*/
public async setPassword(uuid: string, newPassword: string) {
let hashsalt = await UserDatabase.hash(newPassword);
await User.findOneAndUpdate(
{ uuid: uuid },
{ password: hashsalt }
).exec();
}
/**
* Gets the password of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's password
*/
public async getPasswordHash(uuid: string): Promise<Buffer> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.password.hash;
}
/**
* Updates a member's position
* @param uuid the UUID of the user
* @param newTitle the user's new officer title/position
*/
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
await User.findOneAndUpdate(
{ uuid: uuid },
{ title: newTitle, isOfficer: isOfficer }
).exec();
}
/**
* Gets the title of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's title
*/
public async getRank(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.title;
}
/**
* Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user
* @returns true if the user is an officer
*/
public async isOfficer(uuid: string): Promise<boolean> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.isOfficer;
}
/**
* Sets the user's new profile picture
* @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture
*/
public async setPicture(uuid: string, newPic: string) {
await User.findOneAndUpdate({ uuid: uuid }, { picture: newPic }).exec();
}
/**
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's profile picture
*/
public async getPicture(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.picture;
}
public async getUser(uuid: string): Promise<DBUser> {
return User.findOne({ uuid: uuid }).exec() as Promise<DBUser>;
}
// User creation and deletion functions
/**
* Deletes a user with the given UUID
* @param uuid the UUID of the user
*/
public async deleteUser(uuid: string) {
await User.findOneAndDelete({ uuid: uuid }).exec();
}
/**
* Makes a new user and adds it to the database
* @param display the user's display name
* @param email the user's display email
* @param picture the user's picture
* @param password the user's plaintext password
*/
public async makeNewUser(
display: string,
email: string,
picture: string,
password: string,
title?: string,
isOfficer?: boolean
): Promise<void> {
let encrypted = await UserDatabase.hash(password);
await new User({
display: display,
email: email,
picture: picture,
password: encrypted,
uuid: UserDatabase.genUUID(),
isOfficer: !!isOfficer,
title: title ? title : "Member",
}).save();
}
// Utility functions
/**
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
* @param password the password as a string that should be hashed
* @returns the hashed password
*/
public static async hash(password: string): Promise<HashSalt> {
let salt = randomBytes(UserDatabase.SALT_LENGTH);
let pass = Buffer.from(String.prototype.normalize(password));
return {
hash: await pbkdf(
pass,
salt,
UserDatabase.PW_ITERATIONS,
512,
"sha512"
),
salt: salt,
};
}
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map((val) => val.toString(16).padStart(2, "0"))
.join("");
}
}
import { Schema } from "mongoose";
import * as mongoose from "mongoose";
import { promisify } from "util";
import { randomBytes, pbkdf2 } from "crypto";
const pbkdf = promisify(pbkdf2);
const User = mongoose.model(
"User",
new Schema({
display: String,
email: {
public: Boolean,
text: String,
},
picture: String,
password: {
hash: Buffer,
salt: Buffer,
},
bio: { type: String, default: "" },
isOfficer: { type: Boolean, default: false },
title: { type: String, default: "Member" },
uuid: String,
})
);
interface HashSalt {
hash: Buffer;
salt: Buffer;
}
export interface DBUser {
display: string;
email: {
public: boolean;
text: string;
};
picture: string;
password: HashSalt;
isOfficer: boolean;
title: string;
uuid: string;
}
export default class UserDatabase {
// How many iterations pbkdf2 will iterate the password hashing algorithm
private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64;
// How many bytes will be used to generate a UUID of length 2 times this variable
private static UUID_LENGTH = 32;
/**
* Makes a new UserDatabase
* @param url the mongodb database url to connect to
*/
constructor(url: string) {
mongoose
.connect(url)
.then(() => {
mongoose.set("returnOriginal", false);
})
.catch((err) => {
console.error(err);
});
}
// Functions to edit current users
/**
* Changes a user's display name on the website
* @param uuid the UUID of the user
* @param newName the user's new name
*/
public async setName(uuid: string, newName: string) {
await User.findOneAndUpdate({ uuid: uuid }, { name: newName }).exec();
}
/**
* Gets the display name of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's display name
*/
public async getName(uuid: string): Promise<string> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.display;
}
/**
* Updates a user's display email
* @param uuid the UUID of the user
* @param newEmail the user's new display email
*/
public async setEmail(uuid: string, newEmail: string) {
await User.findOneAndUpdate({ uuid: uuid }, { email: newEmail }).exec();
}
/**
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's email
*/
public async getEmail(uuid: string): Promise<string> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.email.text;
}
/**
* Updates a user's password
* @param uuid the UUID of the user
* @param newPassword the user's new password
*/
public async setPassword(uuid: string, newPassword: string) {
const hashsalt = await UserDatabase.hash(newPassword);
await User.findOneAndUpdate(
{ uuid: uuid },
{ password: hashsalt }
).exec();
}
/**
* Gets the password of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's password
*/
public async getPasswordHash(uuid: string): Promise<Buffer> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.password.hash;
}
/**
* Updates a member's position
* @param uuid the UUID of the user
* @param newTitle the user's new officer title/position
*/
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
await User.findOneAndUpdate(
{ uuid: uuid },
{ title: newTitle, isOfficer: isOfficer }
).exec();
}
/**
* Gets the title of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's title
*/
public async getRank(uuid: string): Promise<string> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.title;
}
/**
* Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user
* @returns true if the user is an officer
*/
public async isOfficer(uuid: string): Promise<boolean> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.isOfficer;
}
/**
* Sets the user's new profile picture
* @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture
*/
public async setPicture(uuid: string, newPic: string) {
await User.findOneAndUpdate({ uuid: uuid }, { picture: newPic }).exec();
}
/**
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's profile picture
*/
public async getPicture(uuid: string): Promise<string> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user.picture;
}
public async getUser(uuid: string): Promise<DBUser> {
const user = await User.findOne({ uuid: uuid }).lean().exec();
return user as DBUser;
}
// User creation and deletion functions
/**
* Deletes a user with the given UUID
* @param uuid the UUID of the user
*/
public async deleteUser(uuid: string) {
await User.findOneAndDelete({ uuid: uuid }).exec();
}
/**
* Makes a new user and adds it to the database
* @param display the user's display name
* @param email the user's display email
* @param picture the user's picture
* @param password the user's plaintext password
*/
public async makeNewUser(
display: string,
email: string,
picture: string,
password: string,
title?: string,
isOfficer?: boolean
): Promise<void> {
const encrypted = await UserDatabase.hash(password);
await new User({
display: display,
email: email,
picture: picture,
password: encrypted,
uuid: UserDatabase.genUUID(),
isOfficer: !!isOfficer,
title: title ? title : "Member",
}).save();
}
// Utility functions
/**
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
* @param password the password as a string that should be hashed
* @returns the hashed password
*/
public static async hash(password: string): Promise<HashSalt> {
const salt = randomBytes(UserDatabase.SALT_LENGTH);
const pass = Buffer.from(String.prototype.normalize(password));
return {
hash: await pbkdf(
pass,
salt,
UserDatabase.PW_ITERATIONS,
512,
"sha512"
),
salt: salt,
};
}
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map((val) => val.toString(16).padStart(2, "0"))
.join("");
}
}

View file

@ -1,129 +1,134 @@
import express from "express";
import { Request, Response } from "express";
import * as path from "path";
import * as fs from "fs";
import UserDatabase from "./Database";
interface Website {
[key: string]: string;
sitename: string;
title: string;
description: string;
jsfile: string;
cssfile: string;
themecolor: string;
}
const APP = express();
const TEMPLATE = fs
.readFileSync(path.join(__dirname, "public/template.html"))
.toString();
const WEBSITES = [
{
sitename: "index",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "events",
title: "IEEE at UCSD",
description: "",
jsfile: "js/events.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "projects",
title: "IEEE at UCSD",
description: "",
jsfile: "js/projects.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "committees",
title: "IEEE at UCSD",
description: "",
jsfile: "js/committees.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "contact",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
] as Website[];
const PORT = 9000;
// Make the public directory traversible to people online
APP.use(express.static(path.join(__dirname, "public")));
// Put the cookies as a variable in the request
APP.use((req: Request, res: Response, next: any) => {
req.cookies = req.headers.cookie;
next();
});
// Receive json post requests and urlencoded requests
APP.use(express.json());
APP.use(express.urlencoded({ extended: true }));
// Send main page
APP.get("/", (req: Request, res: Response) => {
respond(res, "index");
});
APP.get("/events", (req: Request, res: Response) => {
respond(res, "events");
});
APP.get("/projects", (req: Request, res: Response) => {
respond(res, "projects");
});
APP.get("/committees", (req: Request, res: Response) => {
respond(res, "committees");
});
APP.get("/contact", (req: Request, res: Response) => {
respond(res, "contact");
});
/**
* Utility functions for above methods
*/
function respond(res: any, filename: string) {
res.set({
"Content-Type": "text/html",
});
res.send(generatePage(filename));
}
function generatePage(name: string): string {
let site = WEBSITES.find((e) => e.sitename === name);
let html = TEMPLATE;
for (let key of Object.keys(site)) {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
}
return html;
}
function generateFilePages() {
for (let site of WEBSITES) {
let html = generatePage(site.sitename);
fs.writeFileSync(
require("path").join(__dirname, "public/", `${site.sitename}.html`),
html
);
}
}
if (process.argv[2] === "generate") {
generateFilePages();
} else {
APP.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}
import express, { NextFunction } from "express";
import { Request, Response } from "express";
import * as path from "path";
import * as fs from "fs";
interface Website {
[key: string]: string;
sitename: string;
title: string;
description: string;
jsfile: string;
cssfile: string;
themecolor: string;
}
const APP = express();
const TEMPLATE = fs
.readFileSync(path.join(__dirname, "public/template.html"))
.toString();
const WEBSITES = [
{
sitename: "index",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "events",
title: "IEEE at UCSD",
description: "",
jsfile: "js/events.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "projects",
title: "IEEE at UCSD",
description: "",
jsfile: "js/projects.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "committees",
title: "IEEE at UCSD",
description: "",
jsfile: "js/committees.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "contact",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
] as Website[];
const PORT = 9000;
// Make the public directory traversible to people online
APP.use(express.static(path.join(__dirname, "public")));
// Put the cookies as a variable in the request
APP.use((req: Request, res: Response, next: NextFunction) => {
req.cookies = req.headers.cookie;
next();
});
// Receive json post requests and urlencoded requests
APP.use(express.json());
APP.use(express.urlencoded({ extended: true }));
// Send main page
APP.get("/", (req: Request, res: Response) => {
respond(res, "index");
});
APP.get("/events", (req: Request, res: Response) => {
respond(res, "events");
});
APP.get("/projects", (req: Request, res: Response) => {
respond(res, "projects");
});
APP.get("/committees", (req: Request, res: Response) => {
respond(res, "committees");
});
APP.get("/contact", (req: Request, res: Response) => {
respond(res, "contact");
});
/**
* Utility functions for above methods
*/
function respond(res: Response, filename: string) {
res.set({
"Content-Type": "text/html",
});
res.send(generatePage(filename));
}
function generatePage(name: string): string {
const site = WEBSITES.find((e) => e.sitename === name);
let html = TEMPLATE;
let key: string;
for (key of Object.keys(site)) {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
}
return html;
}
function generateFilePages() {
let site: Website;
for (site of WEBSITES) {
const html = generatePage(site.sitename);
fs.writeFileSync(
path.join(__dirname, "public/", `${site.sitename}.html`),
html
);
}
}
if (process.argv[2] === "generate") {
generateFilePages();
} else {
APP.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}

View file

@ -1,12 +1,11 @@
{
"compilerOptions": {
"outDir": "./build/",
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"jsx": "react",
"esModuleInterop": true
},
"include": ["./src"],
"exclude": ["./src/public"]
}
"compilerOptions": {
"outDir": "./build/",
"noImplicitAny": false,
"module": "commonjs",
"target": "es6",
"jsx": "react",
"esModuleInterop": true
},
"include": ["./src/util"]
}

View file

@ -1,7 +1,5 @@
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const LicensePlugin = require("license-webpack-plugin").LicenseWebpackPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const fs = require("fs");
module.exports = {
@ -10,6 +8,7 @@ module.exports = {
path: path.resolve(__dirname, "build/public"),
filename: "./js/[name].js",
},
mode: "production",
module: {
rules: [
{
@ -29,44 +28,24 @@ module.exports = {
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
mode: "production",
plugins: [
new CopyPlugin({
patterns: [
{
from: "./src/public",
to: ".",
globOptions: {
ignore: ["**/*.tsx", "**/*.ts"],
},
from: "./src/util",
},
],
}),
new LicensePlugin({
outputFilename: "third-party-notice.txt",
}),
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
extractComments: false,
}),
],
},
//devtool: "source-map"
devServer: {
static: "./build/public",
},
};
/**
* Load all entries from a directory
* @param {*} dir Directory to load entries from
* @returns Object with entries
*/
function loadEntries(dir) {
let files = fs.readdirSync(path.join(__dirname, dir));
const files = fs.readdirSync(path.join(__dirname, dir));
let entries = {};
files.forEach((file) => {
let name = file.match(/^(.*)\.tsx$/);