Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
439 changes: 429 additions & 10 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"express": "^4.17.1",
"google-auth-library": "^7.0.4",
"https": "^1.0.0",
"i18next": "^19.9.0",
"jest-coverage-badges": "^1.1.2",
"jsonwebtoken": "^8.5.1",
"mongodb": "^3.6.3",
"morgan": "^1.10.0",
"node-schedule": "^1.3.2",
"prettier": "^2.2.1",
"supertest": "^6.1.3",
"ts-node": "^9.1.1",
"typescript": "^3.8.2"
},
Expand Down
2 changes: 1 addition & 1 deletion src/actions/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const interactive = (platform: Platform, props: InteractiveProps): void =
["accept-coffee"]: acceptCoffee,
["reject-coffee"]: rejectCoffee,
["try-again-coffee"]: tryAgainCoffee,
["stop-coffee"]: stopCoffee
["stop-coffee"]: stopCoffee,
}
const command = mapper[nextStep];

Expand Down
21 changes: 21 additions & 0 deletions src/actions/register/register-confirmation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Platform } from "../../services/platform/platform";
import { Database } from "../../services/database/database";
import { I18n } from "../../services/i18n/i18n";
import { Logger } from "../../services/logger/logger";
import { RegisterProps } from "./register";

export const registerConfirmation = async (
platform: Platform,
{ userId, userName }: RegisterProps,
db: Database = Database.make()
): Promise<void> => {
const i18n: I18n = await I18n.getInstance()
const senderId = userId
const savedUser = await db.saveUser({ userId: userId, userName: userName })
if (savedUser) {
await platform.sendMessage(senderId, i18n.translate("register.success"))
Logger.log(`Registered user: { userId: ${savedUser.userId}, userName: ${savedUser.userName}`)
} else {
await platform.sendMessage(senderId, i18n.translate("register.failure"))
}
}
20 changes: 20 additions & 0 deletions src/actions/register/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Platform } from "../../services/platform/platform";
import { Database } from "../../services/database/database";
import * as jwt from "jsonwebtoken";

export interface RegisterProps {
userId: string
userName: string,
}

export const register = async (platform: Platform, data: RegisterProps): Promise<void> => {
const db: Database = Database.make()
const user = await db.getUser(data.userId)
console.log(user)
if (user) {
await platform.sendMessage(data.userId, "user already exists")
} else {
const token = jwt.sign(data, process.env.PASS)
await platform.sendMessage(data.userId, `http://localhost:3000/signup?id=${token}`)
}
}
37 changes: 37 additions & 0 deletions src/models/database/dtos/user-dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { User } from "../user";
import { JsonData } from "../../json-data";

export class UserDto {
private constructor (
private userId: string,
private userName: string,
) { }

static fromJson(data: JsonData): UserDto {
return new UserDto(
data.userId,
data.userName
)
}

toJson(): JsonData {
return {
userId: this.userId,
userName: this.userName
}
}

static fromModel(model: User): UserDto {
return new UserDto(
model.userId,
model.userName
)
}

toModel(): User {
return new User(
this.userId,
this.userName
)
}
}
4 changes: 0 additions & 4 deletions src/models/database/gratitude-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,4 @@ export interface GratitudeSummary {
user: Id,
sent: GratitudeSummaryMessage[],
received: GratitudeSummaryMessage[],
}

export interface GratitudeMessageOptions {
days?: number
}
6 changes: 6 additions & 0 deletions src/models/database/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class User {
constructor(
public userId: string,
public userName: string
) { }
}
25 changes: 25 additions & 0 deletions src/services/api/api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as supertest from "supertest"
import { httpsApp } from "./api";

const request = supertest(httpsApp)

describe('API', () => {
it.todo('POST interactive', async done => {
const response = await request.post('/interactive')
expect(response.status).toBe(200)
done()
})

it.todo('POST thanks', async done => {
const response = await request.post('/thanks')
expect(response.status).toBe(200)
done()
})

it.todo('POST coffeeRoulette', async done => {
const response = await request.post('/coffeeRoulette')
expect(response.status).toBe(200)
done()
})
})

91 changes: 80 additions & 11 deletions src/services/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as https from "https"
import * as fs from "fs"
import * as jwt from "jsonwebtoken";
import { json, urlencoded } from "body-parser"
import { getDateFormatted, Logger } from "../logger/logger"
import { config } from "../../config"
Expand All @@ -8,15 +9,45 @@ import { Emojis } from "../../models/emojis"
import * as morgan from "morgan"
import { registerCommunity } from "./methods/register-community"
import { getPlatformData } from "./methods/get-platform-data"
import { Slack } from "../platform/slack/slack"
import { Database } from "../database/database"
import { QueryOptions } from "../database/mongo/methods/query"
import { OAuth2Client } from "google-auth-library"

const decodeGoogleToken = async (client: OAuth2Client, request: any) => {
const authorization: string = request.get("authorization")
const bearer = "bearer "
if (authorization && authorization.toLowerCase().startsWith(bearer)) {
const token = authorization.substring(bearer.length)
return await client.verifyIdToken({
idToken: token,
audience: process.env.CLIENT_ID
}).then(ticket => ticket.getPayload())
}
}

const decodeIdToken = (token: string) => {
console.log(token)
return jwt.verify(token, process.env.PASS, function (error, decode){
if(error){
throw new Error(error)
}
return decode
})
}

interface SignUpQuery {
id: string
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const app = require("express")()
const client = new OAuth2Client(process.env.CLIENT_ID)

app.use(json())
app.use(urlencoded({ extended: true }))

morgan.token("date", () => {
return getDateFormatted()
})
morgan.token("date", () => getDateFormatted())
app.use(morgan(":date :method :url :status :res[content-length] - :response-time ms"))

Endpoints.forEach(({ name, action, getProps }: EndpointInstance) => {
Expand All @@ -40,11 +71,49 @@ Endpoints.forEach(({ name, action, getProps }: EndpointInstance) => {
})
})

export const httpsApp = https.createServer(
{
key: fs.readFileSync(process.env.HTTPS_KEY ?? ""),
cert: fs.readFileSync(process.env.HTTPS_CERT ?? ""),
ca: fs.readFileSync(process.env.HTTPS_CHAIN ?? ""),
},
app
)
const db: Database = Database.make()

app.get("/gratitudeMessages", async (request: any, response: any) => {
// TODO: maintenance??
const options: QueryOptions = request.query
const gratitudeMessages = await db.getGratitudeMessages(options)
return response.send(gratitudeMessages)
})

app.get("/coffeeBreaks", async (request: any, response: any) => {
const options: QueryOptions = request.query
const coffeeBreaks = await db.getCoffeeBreaks(options)
return response.send(coffeeBreaks)
})

app.get("/users/:id", async (request: any, response: any) => {
const userId: string = request.params.id
const { id, name } = await Slack.getInstance().getUserInfo(userId) ?? { id: "", name: ""}
return response.send({ id, name })
})

app.post("/auth", async (request: any, response: any) => {
const decodedToken = await decodeGoogleToken(client, request);
console.log("/auth <--", decodedToken)

return response.send()
})

app.post("/signup", async (request: any, response: any) => {
const options: SignUpQuery = request.query
try {
const decodedGoogleToken = await decodeGoogleToken(client, request)
const decodedIdToken = decodeIdToken(options.id)
console.log("/signup <--", decodedGoogleToken, decodedIdToken)
return response.send()
} catch (error) {
console.log(error)
return response.send(`Invalid token: ${error}`)
}
})

export const httpsApp = https.createServer({
key: fs.readFileSync(process.env.HTTPS_KEY ?? ""),
cert: fs.readFileSync(process.env.HTTPS_CERT ?? ""),
ca: fs.readFileSync(process.env.HTTPS_CHAIN ?? "")
}, app)
Empty file added src/services/api/auth.ts
Empty file.
7 changes: 7 additions & 0 deletions src/services/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { interactive } from "../../actions/interactive"
import { sendGratitudeSummaries } from "../../actions/thanks"
import { thanks } from "../../actions/thanks"
import { Platform } from "../platform/platform"
import { register } from "../../actions/register/register"

export interface EndpointInstance {
name: Endpoint
Expand All @@ -15,6 +16,7 @@ export enum Endpoint {
thanks = "/thanks",
coffeeRoulette = "/coffee-roulette",
sendSummary = "/send-summary",
register = "/register"
}

export const Endpoints: EndpointInstance[] = [
Expand All @@ -38,4 +40,9 @@ export const Endpoints: EndpointInstance[] = [
action: (_) => sendGratitudeSummaries(),
getProps: async () => undefined,
},
{
name: Endpoint.register,
action: register,
getProps: (platform, data) => platform.getRegisterProps(data)
}
]
11 changes: 9 additions & 2 deletions src/services/database/database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { GratitudeMessage } from "../../models/database/gratitude-message"
import { QueryOptions } from "./mongo/methods/query"
import { User } from "../../models/database/user"
import { CoffeeBreak } from "../../models/database/coffee-break"
import { Community } from "../../models/database/community"
import { GratitudeMessage, GratitudeMessageOptions } from "../../models/database/gratitude-message"
import { Logger } from "../logger/logger"

export type DatabaseName = "mongo"
Expand All @@ -27,7 +29,12 @@ export abstract class Database {
abstract getCommunities: () => Promise<Community[]>

abstract saveGratitudeMessages: (gratitudeMessages: GratitudeMessage[]) => Promise<void>
abstract getGratitudeMessages: (options: GratitudeMessageOptions) => Promise<GratitudeMessage[]>
abstract getGratitudeMessages: (options: QueryOptions) => Promise<GratitudeMessage[]>

abstract saveCoffeeBreak: (coffeeBreak: CoffeeBreak) => Promise<void>
abstract getCoffeeBreaks: (options: QueryOptions) => Promise<CoffeeBreak[]>

abstract saveUser: (user: User) => Promise<User | undefined>
abstract getUser: (userId: string) => Promise<User | undefined>

}
1 change: 1 addition & 0 deletions src/services/database/mongo/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum Collection {
communities = "communities",
gratitudeMessages = "communities.gratitudemessages",
coffeeBreaks = "communities.coffeebreaks",
users = "users"
}
45 changes: 45 additions & 0 deletions src/services/database/mongo/methods/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export interface QueryOptions {
communityId?: string,
days?: number,
userId?: string,
startDate?: string,
endDate?: string
}

const queryDays = (query: any, days: number): any => {
const nowTime = (new Date()).getTime()
const queryTime = days * 24 * 60 * 60 * 1000
query["createdAtTime"] = {
$gte: nowTime - queryTime,
$lt: nowTime,
}
return query
}

export const makeQuery = (options: QueryOptions): any => {
const query = {}

if (options.communityId) {
query["communityId"] = options.communityId
}

if (options.days) {
queryDays(query, options.days)
} else if (options.startDate || options.endDate) {
query["createdAtTime"] = {}
if (options.startDate) {
query["createdAtTime"]["$gte"] = (new Date(options.startDate)).getTime()
}
if (options.endDate) {
query["createdAtTime"]["$lt"] = (new Date(options.endDate)).getTime()
}
}

if (options.userId) {
query["$or"] = [
{ senderId: options.userId },
{ recipientId: options.userId }
]
}
return query
}
Loading