Skip to content

Commit

Permalink
Add schools functionality (through Cloud Functions)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaVR committed Dec 23, 2018
1 parent 13650bd commit 8aa1021
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 238 deletions.
50 changes: 40 additions & 10 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { UserRecord } from "firebase-functions/lib/providers/auth";
import { CallableContext, HttpsError } from "firebase-functions/lib/providers/https";
import { schoolsCollection } from "./shared/firebase/firestoreConstants";
import { IFirebaseFunctionParam as Param } from "./shared/firebase/interfaces";
import { ISchool, validateSchool } from "./shared/models/School";
import { IFirebaseFunctionParam } from "./shared/firebase/interfaces";

admin.initializeApp();
const db = admin.firestore();
const auth = admin.auth();

export const test = functions.https.onCall((data, context) => {
///////////////
// Utilities //
///////////////

function checkAuthentication(context: CallableContext): void {
if (!context.auth) {
throw new HttpsError("permission-denied", "You need permissions");
throw new HttpsError("unauthenticated", "You need permissions"); // TODO: translate
}
});
}

function errorCallback(details: any): void {
throw new HttpsError("unknown", "Something went wrong... Please try again later!", details); // TODO: translate
}

/////////////////////////
// Database operations //
/////////////////////////

export const addSchool = functions.https.onCall((param: Param<ISchool>, context): Promise<ISchool> => {
checkAuthentication(context);

export const addSchool = functions.https.onCall((param: IFirebaseFunctionParam<ISchool>, context: CallableContext) => {
return new Promise(((resolve, reject) => {
if (validateSchool(param.data).hasErrors) {
// TODO: failed!
return new Promise<ISchool>(((resolve) => {
const validated = validateSchool(param.data);
if (validated.hasErrors()) {
throw new HttpsError("invalid-argument", "Fields are not correct", validated); // TODO: translate
} else {
db.collection(schoolsCollection).add(param.data)
.then((ref) => {
return ref.get();
})
.then((doc) => {
const school = {
...doc.data(),
id: doc.id,
};
resolve(school as ISchool);
})
.catch(errorCallback);
}
}));
});
Expand All @@ -29,7 +59,7 @@ export const addSchool = functions.https.onCall((param: IFirebaseFunctionParam<I
/**
* Disables users after they sign up. This prevents random people somehow succeeded to sign up from logging in.
*/
export const disableUserAfterSignUp = functions.auth.user().onCreate((user: admin.auth.UserRecord): void => {
export const disableUserAfterSignUp = functions.auth.user().onCreate((user: UserRecord): void => {
auth.updateUser(user.uid, {
disabled: true,
});
Expand Down
1 change: 1 addition & 0 deletions functions/src/shared/firebase/firestoreConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const schoolsCollection = "/schools";
4 changes: 0 additions & 4 deletions functions/src/shared/firebase/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@ export interface IFirebaseFunctionParam<T> {
lang: Language;
data: T;
}

export interface IFirebaseFunctionResponse<T> {
// TODO
}
6 changes: 3 additions & 3 deletions functions/src/shared/models/LoginDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ValidationResult } from "../validators/ValidationResult";
import { StringValidator } from "../validators/StringValidator";
import { ValidationError } from "../validators/ValidationError";
import { ValidationResult } from "../validators/ValidationResult";

export interface ILoginDetails {
username: string;
Expand All @@ -14,14 +14,14 @@ export function validateLoginDetails(loginDetails: ILoginDetails): ValidationRes
* username
*/
if (StringValidator.isEmpty(loginDetails.username)) {
result.add(new ValidationError<ILoginDetails>("username", "validation.field_should_not_be_empty"))
result.add(new ValidationError<ILoginDetails>("username", "validation.field_should_not_be_empty"));
}

/**
* password
*/
if (StringValidator.isEmpty(loginDetails.password)) {
result.add(new ValidationError<ILoginDetails>("password", "validation.field_should_not_be_empty"))
result.add(new ValidationError<ILoginDetails>("password", "validation.field_should_not_be_empty"));
}

return result;
Expand Down
4 changes: 2 additions & 2 deletions functions/src/shared/models/School.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FirebaseValidator } from "../validators/FirebaseValidator";
import { StringValidator } from "../validators/StringValidator";
import { ValidationResult } from "../validators/ValidationResult";
import { ValidationError } from "../validators/ValidationError";
import { ValidationResult } from "../validators/ValidationResult";

export interface ISchool {
id?: string;
Expand All @@ -15,7 +15,7 @@ export function validateSchool(school: ISchool): ValidationResult<ISchool> {
* id
*/
if (FirebaseValidator.hasId(school)) {
result.add(new ValidationError<ISchool>("id", "validation.model_should_not_have_id"))
result.add(new ValidationError<ISchool>("id", "validation.model_should_not_have_id"));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion functions/src/shared/translations/En.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ILanguage, I18nextResourceTranslations } from "./types";
import { I18nextResourceTranslations, ILanguage } from "./types";

export class En implements ILanguage {
public getTranslations(): I18nextResourceTranslations {
Expand Down
2 changes: 1 addition & 1 deletion functions/src/shared/validators/ValidationResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export class ValidationResult<T> {
}

public getErrorsWithField(field: keyof T): Array<ValidationError<T>> {
return this.errors.filter(error => error.field === field);
return this.errors.filter((error) => error.field === field);
}
}
120 changes: 1 addition & 119 deletions functions/tslint.json
Original file line number Diff line number Diff line change
@@ -1,121 +1,3 @@
{
"rules": {
// -- Strict errors --
// These lint rules are likely always a good idea.

// Force function overloads to be declared together. This ensures readers understand APIs.
"adjacent-overload-signatures": true,

// Do not allow the subtle/obscure comma operator.
"ban-comma-operator": true,

// Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
"no-namespace": true,

// Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
"no-parameter-reassignment": true,

// Force the use of ES6-style imports instead of /// <reference path=> imports.
"no-reference": true,

// Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
// code currently being edited (they may be incorrectly handling a different type case that does not exist).
"no-unnecessary-type-assertion": true,

// Disallow nonsensical label usage.
"label-position": true,

// Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
"no-conditional-assignment": true,

// Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
"no-construct": true,

// Do not allow super() to be called twice in a constructor.
"no-duplicate-super": true,

// Do not allow the same case to appear more than once in a switch block.
"no-duplicate-switch-case": true,

// Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
// rule.
"no-duplicate-variable": [true, "check-parameters"],

// Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
// instead use a separate variable name.
"no-shadowed-variable": true,

// Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
"no-empty": [true, "allow-empty-catch"],

// Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
// This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
"no-floating-promises": true,

// Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
// deployed.
"no-implicit-dependencies": true,

// The 'this' keyword can only be used inside of classes.
"no-invalid-this": true,

// Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
"no-string-throw": true,

// Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
"no-unsafe-finally": true,

// Do not allow variables to be used before they are declared.
"no-use-before-declare": true,

// Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
"no-void-expression": [true, "ignore-arrow-function-shorthand"],

// Disallow duplicate imports in the same file.
"no-duplicate-imports": true,


// -- Strong Warnings --
// These rules should almost never be needed, but may be included due to legacy code.
// They are left as a warning to avoid frustration with blocked deploys when the developer
// understand the warning and wants to deploy anyway.

// Warn when an empty interface is defined. These are generally not useful.
"no-empty-interface": {"severity": "warning"},

// Warn when an import will have side effects.
"no-import-side-effect": {"severity": "warning"},

// Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
// most values and let for values that will change.
"no-var-keyword": {"severity": "warning"},

// Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
"triple-equals": {"severity": "warning"},

// Warn when using deprecated APIs.
"deprecation": {"severity": "warning"},

// -- Light Warnings --
// These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
// if TSLint supported such a level.

// prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
// (Even better: check out utils like .map if transforming an array!)
"prefer-for-of": {"severity": "warning"},

// Warns if function overloads could be unified into a single function with optional or rest parameters.
"unified-signatures": {"severity": "warning"},

// Warns if code has an import or variable that is unused.
"no-unused-variable": {"severity": "warning"},

// Prefer const for values that will not change. This better documents code.
"prefer-const": {"severity": "warning"},

// Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts.
"trailing-comma": {"severity": "warning"}
},

"defaultSeverity": "error"
"extends": "tslint:recommended"
}
24 changes: 21 additions & 3 deletions web/src/components/auth/AuthChecker/AuthChecker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ITranslations } from "@studentplanner/functions/dist/shared/translations/types";
import { notification, Spin } from "antd";
import { notification, Spin, Layout } from "antd";
import React from "react";
import { withNamespaces, WithNamespaces } from "react-i18next";
import { connect } from "react-redux";
Expand All @@ -14,6 +14,8 @@ interface IAuthCheckerProps {
type AuthCheckerProps = IAuthCheckerProps & IStateProps & IDispatchProps & WithNamespaces;

interface IAuthCheckerState {
isDoingInitialCheck: boolean;
initialCheckDone: boolean;
}

class AuthChecker extends React.Component<AuthCheckerProps, IAuthCheckerState> {
Expand All @@ -27,6 +29,18 @@ class AuthChecker extends React.Component<AuthCheckerProps, IAuthCheckerState> {
constructor(props: AuthCheckerProps) {
super(props);

this.state = {
initialCheckDone: false,
isDoingInitialCheck: true,
};

window.setTimeout(() => {
this.setState({
initialCheckDone: true,
isDoingInitialCheck: false,
});
}, 1000);

const sessionReloadedValue = sessionStorage.getItem(this.pageWasReloadedSessionKey);
this.pageWasReloaded = sessionReloadedValue !== null ? JSON.parse(sessionReloadedValue) === true : false;
}
Expand Down Expand Up @@ -68,8 +82,12 @@ class AuthChecker extends React.Component<AuthCheckerProps, IAuthCheckerState> {

public render(): React.ReactNode {
return (
<Spin spinning={this.props.authStore.status === "CHECKING_LOGGED_IN"} size="large">
{this.props.children}
<Spin spinning={this.state.isDoingInitialCheck} size="large" style={{ minHeight: "100vh" }}>
<Layout>
{this.state.initialCheckDone &&
this.props.children
}
</Layout>
</Spin>
);
}
Expand Down
Loading

0 comments on commit 8aa1021

Please sign in to comment.