configure eslint and tsconfig
This commit is contained in:
parent
5b814172fb
commit
8a57631324
15 changed files with 1668 additions and 2619 deletions
|
@ -1,27 +1,20 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
"node": true
|
||||
},
|
||||
"root": true,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-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",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"rules": {},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": ["node_modules/", "dist/", "build/", "webpack.config.*"]
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"ignorePatterns": ["node_modules", "build", "webpack**", "src/public/"]
|
||||
}
|
||||
|
|
3371
package-lock.json
generated
3371
package-lock.json
generated
File diff suppressed because it is too large
Load diff
44
package.json
44
package.json
|
@ -4,32 +4,36 @@
|
|||
"description": "Website for IEEE of UC San Diego",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"build": "webpack & tsc",
|
||||
"lint": "eslint \"**/*.{ts, tsx}\""
|
||||
"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",
|
||||
|
@ -38,12 +42,12 @@
|
|||
"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": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -10,10 +10,11 @@ import Carousel from "./components/Carousel";
|
|||
import Footer from "./components/Footer";
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super({});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -5,6 +5,7 @@ interface DefaultSectionProps {
|
|||
title: string;
|
||||
paragraphs?: string[];
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class DefaultSection extends Component<DefaultSectionProps> {
|
||||
|
@ -26,7 +27,6 @@ export default class DefaultSection extends Component<DefaultSectionProps> {
|
|||
<p key={n}>{n}</p>
|
||||
)
|
||||
)}
|
||||
{/* eslint-disable-next-line react/prop-types */}
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,8 +4,8 @@ import SocialCard from "./SocialCard";
|
|||
import { EMAIL_WHITE, SOCIALS_WHITE } from "../Config";
|
||||
|
||||
export default class Footer extends Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super({});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
≡
|
||||
|
|
|
@ -8,10 +8,11 @@ import SocialCard from "./components/SocialCard";
|
|||
import Footer from "./components/Footer";
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super({});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -10,10 +10,11 @@ import Carousel from "./components/Carousel";
|
|||
import Footer from "./components/Footer";
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super({});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -9,10 +9,11 @@ import SocialCard from "./components/SocialCard";
|
|||
import Footer from "./components/Footer";
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super({});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,236 +1,247 @@
|
|||
import { Schema, Document } 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) {
|
||||
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> {
|
||||
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> {
|
||||
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("");
|
||||
}
|
||||
}
|
||||
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("");
|
||||
}
|
||||
}
|
|
@ -1,130 +1,134 @@
|
|||
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;
|
||||
for (key of Object.keys(site)) {
|
||||
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function generateFilePages() {
|
||||
let site;
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
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;
|
||||
for (key of Object.keys(site)) {
|
||||
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function generateFilePages() {
|
||||
let site;
|
||||
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}`);
|
||||
});
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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$/);
|
||||
|
|
Loading…
Reference in a new issue