Skip to content

Commit

Permalink
🎉 Display user's playlists
Browse files Browse the repository at this point in the history
  • Loading branch information
jh0ker committed Apr 2, 2018
0 parents commit fa5b8e9
Show file tree
Hide file tree
Showing 33 changed files with 10,814 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
build
dist
.rpt2_cache
functions/lib

node_modules
functions/node_modules

yarn-error.log
firebase-debug.log

spotify.config.js
.firebaserc
30 changes: 30 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
},
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/auth/exchangeCode",
"function": "exchangeCode"
},
{
"source": "**",
"destination": "/index.html"
}
]
}
}
3 changes: 3 additions & 0 deletions firestore.indexes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"indexes": []
}
7 changes: 7 additions & 0 deletions firestore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
31 changes: 31 additions & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "functions",
"version": "0.0.1",
"description": "CloudFunctions for ForkTune",
"main": "lib/index.js",
"author": "Jannes Höke, Marcus Weiner",
"license": "MIT",
"private": true,
"scripts": {
"build": "tsc",
"lint": "tslint -p tslint.json"
},
"dependencies": {
"cors": "^2.8.4",
"firebase-admin": "^5.11.0",
"firebase-functions": "^0.9.1",
"request": "^2.85.0",
"request-promise-native": "^1.0.5"
},
"devDependencies": {
"rollup": "^0.57.1",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.2.0",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-typescript2": "^0.12.0",
"tslint": "^5.9.1",
"typescript": "^2.8.1"
}
}
104 changes: 104 additions & 0 deletions functions/src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { https } from "firebase-functions";
import * as request from 'request-promise-native';
import { Agent } from "https";
import { crypto } from './util/crypto';
import { config } from './util/config';
import createCors from 'cors';

const cors = createCors({ origin: true });
const agent = new Agent({ keepAlive: true });

const { client_id, client_secret, encryption_key } = config().spotify;

async function fetchToken(params) {

const authKey = new Buffer(`${ client_id }:${ client_secret }`).toString('base64');

return request.post({
agent,
uri: 'https://accounts.spotify.com/api/token',
form: params,
headers: {
'Authorization': `Basic ${authKey}`
},
json: true,
});

}

export interface ExchangeCodeResult {
accessToken: string;
expiresIn: number;
refreshToken: string;
}

export interface ExchangeCodeParams {
code: string;
redirectUri: string;
}

export const exchangeCode = https.onRequest( (req, resp) => cors(req, resp, async () => {
const { code, redirectUri }: ExchangeCodeParams = req.body;

if (!code || !redirectUri) {
resp.status(400).send('Parameter code or redirectUri missing');
return;
}

try {
const { access_token, expires_in, refresh_token } = await fetchToken({
'grant_type': 'authorization_code',
code,
'redirect_uri': redirectUri,
});

resp.send({
accessToken: access_token,
expiresIn: expires_in,
refreshToken: crypto.encrypt(refresh_token, encryption_key),
});
} catch (e) {
resp.status(500).send(e.message);
}
}));


export interface RefreshAccessTokenParams {
refreshToken: string;
}

export interface RefreshAccessTokenResult {
accessToken: string;
scope: string;
expiresIn: number;
}

export const refreshAccessToken = https.onRequest( (req, resp) => cors(req, resp, async () => {
const { refreshToken }: RefreshAccessTokenParams = req.body;

if (!refreshToken) {
resp.status(400).send('Parameter refreshToken missing');
return;
}

try {

const {
access_token,
token_type,
scope,
expires_in,
} = await fetchToken({
'grant_type': 'refresh_token',
'refresh_token': refreshToken,
});

resp.send({
accessToken: access_token,
scope,
expiresIn: expires_in,
} as RefreshAccessTokenResult);
} catch (e) {
resp.status(500).send(e.message);
}
}));
1 change: 1 addition & 0 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './auth';
17 changes: 17 additions & 0 deletions functions/src/util/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { config as firebaseConfig } from "firebase-functions";
// import { CLIENT_ID, CLIENT_SECRET, ENCRYPTION_KEY } from './spotify.config'

declare function require(name:string);

export function config() {
const firebaseConfigObject = firebaseConfig();
if (!('spotify' in firebaseConfigObject)) {
const spotifyConfig = require('../../spotify.config');
firebaseConfigObject.spotify = {
client_id: spotifyConfig.CLIENT_ID,
client_secret: spotifyConfig.CLIENT_SECRET,
encryption_key: spotifyConfig.ENCRYPTION_KEY,
}
}
return firebaseConfigObject;
}
15 changes: 15 additions & 0 deletions functions/src/util/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createCipher, createDecipher } from 'crypto';

const algo = 'aes-256-cbc';

export const crypto = {
encrypt(text, key) {
const cipher = createCipher(algo, key);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
},

decrypt(text, key) {
const decipher = createDecipher(algo, key);
return decipher.update(text, 'hex', 'utf8') + decipher.final('utf8');
},
};
15 changes: 15 additions & 0 deletions functions/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": ["es6"],
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "lib",
"sourceMap": true,
"target": "es6",
"esModuleInterop": true
},
"compileOnSave": true,
"include": [
"src"
]
}
121 changes: 121 additions & 0 deletions functions/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"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 Warnigns --
// 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 liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
"trailing-comma": {"severity": "warning"}
},

"defaultSeverity": "error"
}
Loading

0 comments on commit fa5b8e9

Please sign in to comment.