Skip to content

Commit

Permalink
setupServer fix, add script wrapper (#23)
Browse files Browse the repository at this point in the history
* Fix setupServer to return the express app, listen immediately

* Add script wrapper

* Add postCreate to user model to allow more sign up data

* Fixes for scripts, add every minute to cron
  • Loading branch information
joshgachnang authored Dec 25, 2021
1 parent dcf6dee commit 452caee
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 6 deletions.
60 changes: 59 additions & 1 deletion src/expressServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,15 @@ export function setupServer(UserModel: UserModel, addRoutes: AddRoutes) {
}

// Convenince method to execute cronjobs with an always-running server.
export function cronjob(name: string, schedule: "hourly" | string, callback: () => void) {
export function cronjob(
name: string,
schedule: "hourly" | "minutely" | string,
callback: () => void
) {
if (schedule === "hourly") {
schedule = "0 * * * *";
} else if (schedule === "minutely") {
schedule = "* * * * *";
}
console.info(`Adding cronjob ${name}, running at: ${schedule}`);
try {
Expand Down Expand Up @@ -205,3 +211,55 @@ export async function sendToSlack(text: string, channel = "bots") {
console.error("Error posting to slack", (e as any).text);
}
}

export interface WrapScriptOptions {
onFinish?: (result?: any) => void | Promise<void>;
terminateTimeout?: number; // in seconds, defaults to 300. Set to 0 to have no termination timeout.
slackChannel?: string;
}
// Wrap up a script with some helpers, such as catching errors, reporting them to sentry, notifying
// Slack of runs, etc. Also supports timeouts.
export async function wrapScript(func: () => Promise<any>, options: WrapScriptOptions = {}) {
const name = require.main?.filename
.split("/")
.slice(-1)[0]
.replace(".ts", "");
console.log(`Running script ${name}`);
sendToSlack(`Running script ${name}`, options.slackChannel);

if (options.terminateTimeout !== 0) {
const warnTime = ((options.terminateTimeout ?? 300) / 2) * 1000;
const closeTime = (options.terminateTimeout ?? 300) * 1000;
setTimeout(() => {
const msg = `Script ${name} is taking a while, currently ${warnTime / 1000} seconds`;
sendToSlack(msg);
console.warn(msg);
}, warnTime);

setTimeout(async () => {
const msg = `Script ${name} took too long, exiting`;
await sendToSlack(msg);
console.error(msg);
Sentry.captureException(new Error(`Script ${name} took too long, exiting`));
await Sentry.flush();
process.exit(2);
}, closeTime);
}

let result: any;
try {
result = await func();
if (options.onFinish) {
await options.onFinish(result);
}
} catch (e) {
Sentry.captureException(e);
console.error(`Error running script ${name}: ${e}\n${(e as Error).stack}`);
sendToSlack(`Error running script ${name}: ${e}\n${(e as Error).stack}`);
await Sentry.flush();
process.exit(1);
}
await sendToSlack(`Success running script ${name}: ${result}`);
// Unclear why we have to exit here to prevent the script for continuing to run.
process.exit(0);
}
22 changes: 19 additions & 3 deletions src/mongooseRestFramework.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface User {
admin: boolean;
username: string;
email: string;
age?: number;
}

interface Food {
Expand All @@ -33,11 +34,16 @@ interface Food {
const userSchema = new Schema<User>({
username: String,
admin: {type: Boolean, default: false},
age: Number,
});

userSchema.plugin(passportLocalMongoose, {usernameField: "email"});
userSchema.plugin(tokenPlugin);
userSchema.plugin(createdDeletedPlugin);
userSchema.methods.postCreate = async function(body: any) {
this.age = body.age;
return this.save();
};

const UserModel = model<User>("User", userSchema);

Expand Down Expand Up @@ -522,7 +528,6 @@ describe("mongoose rest framework", () => {
.post("/auth/login")
.send({email: "notAdmin@example.com", password: "password"})
.expect(200);
console.log("RES", res.body);
const foodRes = await agent.get("/food");
const carrots = foodRes.body.data.find((food: Food) => food.name === "Carrots");
const carrotRes = await agent
Expand Down Expand Up @@ -791,7 +796,6 @@ describe("test token auth", function() {
.get("/auth/me")
.set("authorization", `Bearer ${token}`)
.expect(200);
console.log("ME RES", meRes.body.data);
assert.isDefined(meRes.body.data._id);
assert.isDefined(meRes.body.data.id);
assert.isUndefined(meRes.body.data.hash);
Expand Down Expand Up @@ -832,6 +836,19 @@ describe("test token auth", function() {
assert.equal(updateRes.body.data.name, "PeasAndCarrots");
});

it("signup with extra data", async function() {
const res = await server
.post("/auth/signup")
.send({email: "new@example.com", password: "123", age: 25})
.expect(200);
const {userId, token} = res.body.data;
assert.isDefined(userId);
assert.isDefined(token);

const user = await UserModel.findOne({email: "new@example.com"});
assert.equal(user?.age, 25);
});

it("completes token login e2e", async function() {
const res = await server
.post("/auth/login")
Expand All @@ -845,7 +862,6 @@ describe("test token auth", function() {
.get("/auth/me")
.set("authorization", `Bearer ${token}`)
.expect(200);
console.log("ME RES", meRes.body.data);
assert.isDefined(meRes.body.data._id);
assert.isDefined(meRes.body.data.id);
assert.isUndefined(meRes.body.data.hash);
Expand Down
13 changes: 11 additions & 2 deletions src/mongooseRestFramework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export interface UserModel extends Model<User> {
deserializeUser(): any;
createAnonymousUser?: (id?: string) => Promise<User>;
isValidPassword: (password: string) => boolean;
// Allows additional setup during signup. This will be passed the rest of req.body from the signup
// request.
postCreate?: (body: any) => Promise<void>;
}

type PermissionMethod<T> = (method: RESTMethod, user?: User, obj?: T) => boolean;
Expand Down Expand Up @@ -261,11 +264,17 @@ export function setupAuth(app: express.Application, userModel: UserModel) {
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true,
},
async (email, password, done) => {
async (req, email, password, done) => {
try {
const user = await (userModel as any).register({email}, password);
await (user as any).setPassword(password);
if (user.postCreate) {
const body = req.body;
delete body.email;
delete body.password;
await user.postCreate(body);
}
await user.save();
if (!user.token) {
throw new Error("Token not created");
Expand Down

0 comments on commit 452caee

Please sign in to comment.