Skip to content

Instantly share code, notes, and snippets.

@nodkz
Created November 8, 2017 13:28
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nodkz/812519ca9473b28493122872ae57e9c3 to your computer and use it in GitHub Desktop.
Save nodkz/812519ca9473b28493122872ae57e9c3 to your computer and use it in GitHub Desktop.
Mongoose with flow example
/* @flow */
/* eslint-disable func-names */
import { Schema } from 'mongoose';
import DB from 'schema/db';
import composeWithMongoose from 'graphql-compose-mongoose';
import composeWithRelay from 'graphql-compose-relay';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import type { $Request } from 'express';
import { AvatarUrlSchema, type AvatarUrlDoc } from 'schema/customTypes/avatarUrl';
import { Cabinet, type CabinetDoc } from 'schema/cabinet';
export const UserSchema = new Schema(
{
email: {
type: String,
set: v => v.toLowerCase().trim(),
// validate: (v) => UserSchema.isValidEmail(v), TODO
required: true,
},
provider: {
type: String,
required: true,
},
providerId: {
type: String,
set: v => v.toLowerCase().trim(),
description: 'String code of provider',
required: true,
},
token: {
type: String,
required: true,
default() {
// if token not exists, generate random password
return this.encryptPassword(Math.random().toString());
},
},
name: String,
avatarUrl: AvatarUrlSchema,
oneTimeTokenExp: String,
meta: {
type: Schema.Types.Mixed,
},
lastIp: String,
lastLoginAt: Date,
},
{
setDefaultsOnInsert: true,
timestamps: true,
collection: 'user',
}
);
UserSchema.index({ email: 1 }, { background: true });
UserSchema.index({ provider: 1, providerId: 1 }, { unique: true, background: true });
export class UserDoc /* :: extends Mongoose$Document */ {
email: string;
provider: string;
providerId: string;
token: string;
name: ?string;
avatarUrl: ?(AvatarUrlDoc | $Shape<AvatarUrlDoc>);
oneTimeTokenExp: ?string;
meta: ?any;
lastIp: ?string;
lastLoginAt: ?Date;
_plainPassword: ?string; // internal field, exists only on object create
_oneTimeToken: ?string; // internal field
/** @deprecated remove when will be unused in other places of codebase */
static async findByProviderDeprecated(provider, providerId, cb) {
return this.findOne({ provider, providerId: providerId.toLowerCase().trim() }, cb);
}
static isValidEmail(email) {
const r = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/; // eslint-disable-line
return r.test(email);
}
static async findByProvider(provider, providerId): Promise<?UserDoc> {
return this.findOne({
provider,
providerId: providerId.toLowerCase().trim(),
}).exec();
}
static async findByEmail(email): Promise<?UserDoc> {
return this.findOne({ email });
}
static async findByEmailOrCreate({ email, password, provider, providerId }): Promise<UserDoc> {
const existsUser = await this.findOne({ email });
if (existsUser) return existsUser;
const newUser = new this({ email, password, provider, providerId });
await newUser.save();
return newUser;
}
set password(password: string) {
this._plainPassword = password;
this.token = this.encryptPassword(password);
}
get password(): ?string {
return this._plainPassword;
}
encryptPassword(password: string): string {
return bcrypt.hashSync(password, 10);
}
checkPassword(password: string): boolean {
if (this.token) {
return bcrypt.compareSync(password, this.token);
}
return false;
}
genPassword(len: number = 8): string {
const newPass = crypto
.randomBytes(Math.ceil(len * 3 / 4)) // eslint-disable-line
.toString('base64') // convert to base64 format
.slice(0, len) // return required number of characters
.replace(/\+/g, '0') // replace '+' with '0'
.replace(/\//g, '0'); // replace '/' with '0'
this.password = newPass;
return newPass;
}
// by default valid 1 hour
oneTimeTokenGenerate(EXPIRED_AFTER?: number = 3600000) {
let token;
// If current one-time-token is valid at least half of expire period, then return existed
// The reason is that user may require several emails in short period of time
// So we should return same token, cause user may click by link from any email.
if (this.oneTimeTokenExp) {
const [tokenInDB, expireAtInDB] = this.oneTimeTokenExp.split(':');
const expireAtInDBInt = parseInt(expireAtInDB, 10);
if (expireAtInDBInt > Date.now() + EXPIRED_AFTER / 2) {
token = tokenInDB;
}
}
// Generate new token
if (!token) {
token = crypto.randomBytes(20).toString('hex');
const expireAt = Date.now() + EXPIRED_AFTER;
this.oneTimeTokenExp = `${token}:${expireAt}`;
}
this._oneTimeToken = token;
return token;
}
oneTimeTokenCheck(token: string): boolean {
if (!token || !this.oneTimeTokenExp) return false;
const [tokenInDB, expireAt] = this.oneTimeTokenExp.split(':');
return parseInt(expireAt, 10) > Date.now() && token === tokenInDB;
}
oneTimeTokenRevoke(): void {
this.oneTimeTokenExp = null;
}
async oneTimeLoginGenerate(EXPIRED_AFTER: number): Promise<string> {
this.oneTimeTokenRevoke();
const token = this.oneTimeTokenGenerate(EXPIRED_AFTER);
await this.save();
const email64 = Buffer.from(this.email).toString('base64');
return `${email64}:${token}`;
}
touchLastLoginAt(req: $Request): this {
let ip = '';
if (req) {
ip =
(req.headers && req.headers['x-forwarded-for']) ||
(req.connection && req.connection.remoteAddress);
}
this.lastIp = ip;
this.lastLoginAt = new Date();
return this;
}
async getCabinets(all: boolean = false): Promise<Array<CabinetDoc>> {
if (!this.email) return Promise.resolve([]);
if (all) {
return Cabinet.find({
$or: [{ users: this.email }, { owner: this.email }],
}).exec();
}
return Cabinet.find({ owner: this.email }).exec();
}
}
UserSchema.loadClass(UserDoc);
export const User = DB.data.model('User', UserSchema);
export const UserTC = composeWithRelay(composeWithMongoose(User));
UserTC.getResolver('createOne')
.getArgTC('input')
.getFieldTC('record')
.addFields({
password: 'String!',
});
@oliverzheng
Copy link

What's the definition of DB? With flow 1.7.0, the type of User seems to be just UserDoc and has no mongoose methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment