Builds and deploys

This commit is contained in:
Amit Jakubowicz 2018-04-02 20:23:44 +02:00
parent d7feb26208
commit 07f2537382
17 changed files with 411 additions and 0 deletions

17
.gcloudignore Normal file
View File

@ -0,0 +1,17 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
node_modules
#!include:.gitignore

16
functions/.gcloudignore Normal file
View File

@ -0,0 +1,16 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
node_modules

8
functions/lib/config.js Normal file
View File

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.domain = 'quepasaalpujarra.com';
exports.mailgun = {
apiKey: 'key-64fc7260cecfd4a8d5fb51e97791b330',
domain: exports.domain
};
exports.projectId = 'calendar-189316';

23
functions/lib/index.js Normal file
View File

@ -0,0 +1,23 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const url_1 = require("url");
const user_1 = require("./user");
exports.isUserAvailable = (req, res) => __awaiter(this, void 0, void 0, function* () {
const params = url_1.parse(req.url, true).query;
const user = yield user_1.getUser({
email: params.email,
username: params.username
});
res.send({
exists: !!user
});
return true;
});

View File

@ -0,0 +1,21 @@
{
"name": "ts-gcf-functions",
"version": "1.0.0",
"main": "lib/index.js",
"license": "MIT",
"dependencies": {
"@google-cloud/datastore": "^1.4.0",
"mailgun-js": "^0.16.0",
"node-pre-gyp": "^0.9.0",
"randomstring": "^1.1.5"
},
"devDependencies": {
"@types/es6-promise": "^3.3.0",
"@types/node": "^9.6.1",
"tslint": "^5.9.1",
"typescript": "^2.8.1"
},
"scripts": {
"build": "./node_modules/.bin/tsc"
}
}

View File

@ -0,0 +1,25 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const mailgun_js_1 = require("mailgun-js");
const config_1 = require("./config");
const client = mailgun_js_1.default(config_1.mailgun);
exports.sendEmail = (email) => __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
mailgun_js_1.default.messages().send(email, function (error, body) {
if (error) {
reject(error);
}
else {
resolve(body);
}
});
});
});

View File

@ -0,0 +1,51 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const Datastore = require('@google-cloud/datastore');
const config_1 = require("./config");
const datastore = Datastore({
projectId: config_1.projectId,
});
class Repository {
static createUser(user) {
const entity = {
key: datastore.key('User'),
data: user
};
return datastore.save(entity);
}
static saveSessionInvite(invite) {
return __awaiter(this, void 0, void 0, function* () {
const entity = {
key: datastore.key('SessionInvite'),
data: invite
};
return yield datastore.save(entity);
});
}
static getUser(user) {
return __awaiter(this, void 0, void 0, function* () {
let query = datastore
.createQuery('user');
if (user.email) {
query = query.filter('email', '=', user.email);
}
if (user.username) {
query = query.filter('username', '=', user.username);
}
return yield datastore.runQuery(query)
.then(results => {
return results[0];
});
});
}
;
}
exports.default = Repository;

46
functions/lib/session.js Normal file
View File

@ -0,0 +1,46 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
// Free API to get location from IP: http://freegeoip.net/json/149.11.144.50
const randomstring_1 = require("randomstring");
const post_office_1 = require("./post_office");
const config_1 = require("./config");
const repository_1 = require("./repository");
const newInvite = (user) => {
return {
oneTimeKey: randomstring_1.default.generate({
length: 24,
charset: 'alphabetic'
}),
userId: user.id
};
};
exports.inviteUser = (user) => __awaiter(this, void 0, void 0, function* () {
const invite = newInvite(user);
try {
yield repository_1.default.saveSessionInvite(invite);
}
catch (e) {
console.error('Failed to save invite', invite);
throw e;
}
try {
yield post_office_1.sendEmail({
to: user.email,
from: `signin@${config_1.domain}`,
text: `invitation for session key: ${invite.oneTimeKey}`,
subject: 'Invitation for session'
});
}
catch (e) {
console.error('Failed to send invitation email', invite);
throw e;
}
});

14
functions/lib/user.js Normal file
View File

@ -0,0 +1,14 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const repository_1 = require("./repository");
exports.getUser = (keys) => __awaiter(this, void 0, void 0, function* () {
return yield repository_1.default.getUser(keys);
});

7
functions/src/config.ts Normal file
View File

@ -0,0 +1,7 @@
export const domain = 'quepasaalpujarra.com'
export const mailgun = {
apiKey: 'key-64fc7260cecfd4a8d5fb51e97791b330',
domain
}
export const projectId = 'calendar-189316'

17
functions/src/index.ts Normal file
View File

@ -0,0 +1,17 @@
import {parse} from 'url'
import { getUser } from './user'
export const isUserAvailable = async (req, res) => {
const params = parse(req.url, true).query
const user = await getUser({
email: params.email as string,
username: params.username as string
})
res.send({
exists: !!user
});
return true
}

View File

@ -0,0 +1,24 @@
import mailgun from 'mailgun-js';
import {mailgun as mailgunConfig} from './config';
const client = mailgun(mailgunConfig);
interface Email {
from: string
to: string
subject: string
text: string
}
export const sendEmail = async (email: Email) => {
return new Promise((resolve, reject) => {
mailgun.messages().send(email, function (error, body) {
if (error) {
reject(error)
} else {
resolve(body)
}
});
})
}

View File

@ -0,0 +1,43 @@
const Datastore = require('@google-cloud/datastore')
import {SessionInvite, User, UserKeys} from './types'
import { projectId } from './config'
const datastore = Datastore({
projectId: projectId,
});
export default class Repository {
static createUser(user: User) {
const entity = {
key: datastore.key('User'),
data: user
}
return datastore.save(entity)
}
static async saveSessionInvite(invite: SessionInvite) {
const entity = {
key: datastore.key('SessionInvite'),
data: invite
}
return await datastore.save(entity)
}
static async getUser (user: UserKeys) {
let query = datastore
.createQuery('user')
if (user.email) {
query = query.filter('email', '=', user.email)
}
if (user.username) {
query = query.filter('username', '=', user.username)
}
return await datastore.runQuery(query)
.then(results => {
return results[0]
});
};
}

40
functions/src/session.ts Normal file
View File

@ -0,0 +1,40 @@
// Free API to get location from IP: http://freegeoip.net/json/149.11.144.50
import randomstring from 'randomstring'
import { sendEmail } from './post_office'
import { domain } from './config'
import repository from './repository'
import {SessionInvite, User} from "./types";
const newInvite = (user: User): SessionInvite => {
return {
oneTimeKey: randomstring.generate({
length: 24,
charset: 'alphabetic'
}),
userId: user.id
}
};
export const inviteUser = async (user: User) => {
const invite = newInvite(user)
try {
await repository.saveSessionInvite(invite)
} catch (e) {
console.error('Failed to save invite', invite)
throw e;
}
try {
await sendEmail({
to: user.email,
from: `signin@${domain}`,
text: `invitation for session key: ${invite.oneTimeKey}`,
subject: 'Invitation for session'
})
} catch (e) {
console.error('Failed to send invitation email', invite)
throw e;
}
}

28
functions/src/types.d.ts vendored Normal file
View File

@ -0,0 +1,28 @@
export interface Session {
createdAt: Date
ttlMs: number
expired: boolean
authMethod: string
hash: string
userAgent: string
location: string
}
export interface SessionInvite {
oneTimeKey: string
userId: string
}
interface DBEntity {
id: string
}
interface UserKeys {
username: string,
email: string
}
interface User extends UserKeys, DBEntity {
firstName: string
lastName?: string
}

6
functions/src/user.ts Normal file
View File

@ -0,0 +1,6 @@
import {UserKeys} from './types'
import repository from './repository'
export const getUser = async (keys: UserKeys) => {
return await repository.getUser(keys)
}

25
webpack.config.js Normal file
View File

@ -0,0 +1,25 @@
const path = require('path')
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: './src/index.ts',
target: 'node',
module: {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts']
},
output: {
filename: 'index.js', // <-- Important
libraryTarget: 'this' // <-- Important
},
externals: [nodeExternals()]
};