diff --git a/404.html b/404.html new file mode 100644 index 0000000..e80aa8a --- /dev/null +++ b/404.html @@ -0,0 +1,16 @@ + + + + + + Page not Found + + + + +
+

Page not found, sorry.

+

Kamontat Chantrachirathumrong

+
+ + diff --git a/_redirects b/_redirects index 64b4fb3..d0f3eaa 100644 --- a/_redirects +++ b/_redirects @@ -1,4 +1,8 @@ -# /* /.netlify/functions/:splat 200! +# redirect to docs +/ /docs 200! + +# redirect netlify function path +/* /.netlify/functions/:splat 200! /latest/* /.netlify/functions/:splat 200! /v2/* /.netlify/functions/:splat 200! \ No newline at end of file diff --git a/lib/analysic.js b/lib/analysic.js deleted file mode 100644 index 5a5e08f..0000000 --- a/lib/analysic.js +++ /dev/null @@ -1,27 +0,0 @@ -export const defaultUser = "net"; -export const defaultUsers = ["net", "prang"]; - -export const AnalysicEvent = (event, defaultUser, users, defaultPath, paths, defaultBranch = "master") => { - let user = event.queryStringParameters.user || defaultUser; - let branch = event.queryStringParameters.branch || defaultBranch; - let type = event.queryStringParameters.type || defaultPath; - let types = [type]; - - const userIndex = users.findIndex(u => event.path.includes(`/${u}`)); - if (userIndex >= 0) user = users[userIndex]; - - if (defaultPath) { - const matches = paths.filter(p => event.path.includes(`/${p}`)); - if (matches && matches.length > 0) { - type = matches[0]; - types = matches; - } - } - - return { - user: user, - type: type, - types: types, - branch: branch - }; -}; diff --git a/lib/content.js b/lib/content.js deleted file mode 100644 index 5e48d4b..0000000 --- a/lib/content.js +++ /dev/null @@ -1,31 +0,0 @@ -const axios = require("axios"); - -const { - Information -} = require("../lib/ghLink"); - -// return promise -export const QueryContent = (octokit, username, filename) => { - const path = `static/resources/${username}/${filename}`; - - return octokit.repos.getContents({ - owner: Information.owner, - repo: Information.repo, - path, - ref: Information.branch - }); -}; - -export const TransformResult = result => { - const data = result.data || result; - - return data.reduce((p, c) => { - if (c.name.includes(".json")) p.push(c.download_url); - return p; - }, []); -}; - -export const MultipleQueryResult = results => { - // TODO: implement performance here - return Promise.all(results.map(r => axios.get(r).then(v => v.data))); -}; \ No newline at end of file diff --git a/lib/ghApi.js b/lib/ghApi.js new file mode 100644 index 0000000..af18664 --- /dev/null +++ b/lib/ghApi.js @@ -0,0 +1,53 @@ +const Octokit = require("@octokit/rest"); +const Promise = require("bluebird"); + +import { + Information, + FetchUrl +} from "./ghLink"; + +export class GHAPIs { + _octokit = undefined; + + constructor(event) { + this._octokit = new Octokit({ + headers: { + authorization: event.headers.authorization + } + }); + } + + queryAll(types, opts = {}) { + return Promise.reduce(types, (p, c) => { + return this.queryOnce(c, opts).then(res => { + p[c] = res; + return p + }) + }, {}) + } + + queryOnce(type, opts = {}) { + return this.queryFileList(opts.user, type, opts).then(files => { + return Promise.all(files.map(url => FetchUrl(url))) + }) + } + + queryFileList(user, folder, opts = {}) { + const filter = opts.filter || '.json'; + const path = `static/resources/${user}/${folder}`; + + return this._octokit.repos.getContents({ + owner: Information.owner, + repo: Information.repo, + path, + ref: opts.branch || Information.branch + }).then(result => { + const data = result.data || result; + + return data.reduce((p, c) => { + if (c.name.includes(filter)) p.push(c.download_url); + return p; + }, []); + }) + } +} \ No newline at end of file diff --git a/lib/ghLink.js b/lib/ghLink.js index 1366559..07b5950 100644 --- a/lib/ghLink.js +++ b/lib/ghLink.js @@ -1,3 +1,5 @@ +const axios = require("axios") + const repo_owner = "kcnt-info"; const repo_name = "website"; const branch = "master"; @@ -9,6 +11,17 @@ export const Information = { branch: branch }; +/** + * Fetch url power by axios + * @param {String} url path to query + * @param {Object} opts options + * @param {Boolean} opts.all return all object from axios, otherwise return only data + * @param {Object} opts.axiosOptions option parsing to axios get method + */ +export const FetchUrl = (url, opts = {}) => { + return axios.get(url, opts.axiosOptions).then(v => opts.all ? v : v.data) +} + /** * generate github link to query result * @param {String} path path from root folder diff --git a/lib/parseUrl.js b/lib/parseUrl.js index 7e4eb7f..dee88dc 100644 --- a/lib/parseUrl.js +++ b/lib/parseUrl.js @@ -4,16 +4,27 @@ const matchPath = (path, results) => { return results[index] } +const matchesPath = (path, results) => { + return results.filter(p => path.includes(`/${p}`)) +} + const matchQuery = (query, options) => { if (options === undefined) return query; return options.includes(query) ? query : undefined } +const matchesQuery = (query, options) => { + if (options === undefined) return [query]; + if (query === undefined) return []; + + return options.filter(p => query.includes(p)) +} + /** * * @param {String} name name of the key result and query name * @param {Array} options possible values - * @param {String} defaultValue default value if value is not exist + * @param {String|Array} defaultValue default value if value is not exist */ export const GenObj = (name, options, defaultValue = undefined) => { @@ -33,7 +44,18 @@ export const ParseParameters = (event, ...obj) => { const path = event.path; const result = {}; obj.forEach(v => { - result[v.name] = matchPath(path, v.options) || matchQuery(query[v.name], v.options) || v.default + if (Array.isArray(v.default)) { + if (result[v.name] === undefined) result[v.name] = []; + + const _paths = matchesPath(path, v.options) + const _params = matchesQuery(query[v.name], v.options) + + if (_paths.length > 0) result[v.name].push(..._paths) + else if (_params.length > 0) result[v.name].push(..._params) + else if (v.default.length > 0) result[v.name].push(...v.default) + } else { + result[v.name] = matchPath(path, v.options) || matchQuery(query[v.name], v.options) || v.default + } }) return result } \ No newline at end of file diff --git a/package.json b/package.json index 4967859..0c4fd00 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,9 @@ "license": "MIT", "dependencies": { "@octokit/rest": "16.2.0", - "aglio": "^2.3.0", - "axios": "0.18.0" + "aglio": "2.3.0", + "axios": "0.18.0", + "bluebird": "3.5.3" }, "devDependencies": { "apidoc": "0.17.7", @@ -17,8 +18,8 @@ "scripts": { "start": "netlify-lambda serve src/apis", "build": "netlify-lambda build src/apis", - "docs:compile": "mkdir docs 2>/dev/null; aglio --theme-variables slate -i src/docs/index.apib -o docs/index.html && cp _redirects docs/_redirects", - "docs:start": "aglio --theme-variables slate -i src/docs/index.apib -o docs/index.html -s", + "docs:compile": "mkdir docs 2>/dev/null; aglio --theme-variables slate -i src/docs/index.apib -o docs/docs.html && cp _redirects docs/_redirects && cp 404.html docs/404.html", + "docs:start": "aglio --theme-variables slate -i src/docs/index.apib -o docs/docs.html -s", "deploy": "yarn docs:compile && yarn build" }, "apidoc": { diff --git a/src/apis/collection.js b/src/apis/collection.js index 398838b..dcfc4ff 100644 --- a/src/apis/collection.js +++ b/src/apis/collection.js @@ -1,9 +1,11 @@ -// const axios = require("axios"); -const Octokit = require("@octokit/rest"); +const { + ParseParameters, + GenObj +} = require('../../lib/parseUrl') -// const { PersonalInformationLink, PersonalSocialLink } = require("../lib/link"); -const { AnalysicEvent, defaultUser, defaultUsers } = require("../../lib/analysic"); -const { QueryContent, TransformResult, MultipleQueryResult } = require("../../lib/content"); +const { + GHAPIs +} = require('../../lib/ghApi') const query = async (octokit, key, user) => { const raw = await QueryContent(octokit, user, key); @@ -14,32 +16,26 @@ const query = async (octokit, key, user) => { return json; }; -exports.handler = function(event, context, callback) { - const octokit = new Octokit({ - headers: { authorization: event.headers.authorization } - }); - - const params = AnalysicEvent(event, defaultUser, defaultUsers, "projects", [ - "educations", - "interests", - "languages", - "projects", - "references", - "skills", - "volunteers", - "works" - ]); - - (async () => { - // TODO: implement performance here - const result = await Promise.all(params.types.map(async type => await query(octokit, type, params.user))); - return result.reduce((p, c) => { - const key = Object.keys(c)[0]; - p[key] = c[key]; - return p; - }, {}); - })() - .then(v => { +exports.handler = function (event, context, callback) { + const result = ParseParameters(event, + GenObj("user", ["net", "prang"]), + GenObj("branch", ["master", "dev"]), + GenObj("lang", ["en", "th"]), + GenObj("type", [ + "educations", + "interests", + "languages", + "projects", + "references", + "skills", + "volunteers", + "works" + ], ["projects"])); + + console.log(result); + const apis = new GHAPIs(event); + + apis.queryAll(result.type, result).then(v => { callback(undefined, { statusCode: 200, headers: { @@ -51,4 +47,32 @@ exports.handler = function(event, context, callback) { }); }) .catch(callback); -}; + + // const octokit = new Octokit({ + // headers: { + // authorization: event.headers.authorization + // } + // }); + + // (async () => { + // // TODO: implement performance here + // const response = await Promise.all(result.type.map(async type => await query(octokit, type, result.user))); + // return response.reduce((p, c) => { + // const key = Object.keys(c)[0]; + // p[key] = c[key]; + // return p; + // }, {}); + // })() + // .then(v => { + // callback(undefined, { + // statusCode: 200, + // headers: { + // "Content-Type": "application/json", + // "Access-Control-Allow-Origin": "*", + // "Access-Control-Allow-Headers": "*" + // }, + // body: JSON.stringify(v) + // }); + // }) + // .catch(callback); +}; \ No newline at end of file diff --git a/src/apis/logo.js b/src/apis/logo.js index 5dd27a1..9e31b31 100644 --- a/src/apis/logo.js +++ b/src/apis/logo.js @@ -1,11 +1,10 @@ -const axios = require("axios") - const { ParseParameters, GenObj } = require('../../lib/parseUrl') const { + FetchUrl, LogoLink } = require("../../lib/ghLink"); @@ -13,17 +12,18 @@ exports.handler = function (event, context, callback) { const result = ParseParameters(event, GenObj("branch", ["master", "dev"]), GenObj("color", ["primary", "light", "dark"]), - GenObj("size", ["normal", "smaller", "high"]), + GenObj("size", ["normal", "small", "high"]), GenObj("type", ["normal", "round"]), GenObj("extension", ["png", "svg"])) const url = LogoLink(result.color, result); - axios - .get(url, { - responseType: 'arraybuffer' - }) - .then(response => { + FetchUrl(url, { + axiosOptions: { + responseType: 'arraybuffer' + }, + all: true + }).then(response => { let image = Buffer.from(response.data, 'binary').toString('base64') callback(undefined, { diff --git a/src/apis/personal.js b/src/apis/personal.js index 2dea110..db38f7c 100644 --- a/src/apis/personal.js +++ b/src/apis/personal.js @@ -1,11 +1,10 @@ -const axios = require("axios"); - const { ParseParameters, GenObj } = require('../../lib/parseUrl') const { + FetchUrl, PersonalInformationLink, PersonalSocialLink } = require("../../lib/ghLink"); @@ -26,9 +25,7 @@ exports.handler = function (event, _, callback) { branch: result.branch }); - axios - .get(url) - .then(result => { + FetchUrl(url).then(result => { callback(undefined, { statusCode: 200, headers: { @@ -36,7 +33,7 @@ exports.handler = function (event, _, callback) { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "*" }, - body: JSON.stringify(result.data) + body: JSON.stringify(result) }); }) .catch(callback); diff --git a/src/docs/collection.doc.js b/src/docs/collection.doc.js deleted file mode 100644 index 154623f..0000000 --- a/src/docs/collection.doc.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @apiDefine CollectionParameters - * - * @apiParam {string=net,prang} [user="net"] username to query - * @apiParam {string=master,dev} [branch="master"] branch to query data - * @apiParam {string=educations,interests,languages,projects,references,skills,volunteers,works} [type="projects"] type of the dataset - * - */ - -/** - * @apiDefine Headers - * - * @apiHeader {String} [authorization] Authorization value. - */ - -/** - * @apiDefine SuccessText - * - * @apiSuccess {Object} response This result of personal information, you will find the schema at https://github.com/kcnt-info/website on static folder, and personal information section in json file - */ - -/** - * @api {get} /collection/:user/:type Collection of information - * @apiDescription Query the collection of information. be aware this api might take very long if you query to many information - * `NOTE: :type can be multiple path, see in the example (warn: this may slow)` - * - * - * @apiName GetCollection - * @apiGroup Collection - * - * @apiUse Headers - * @apiUse CollectionParameters - * - * @apiExample {curl} Example with path: - * curl -i /collection/net/languages or - * curl -i /collection/languages/net - * @apiExample {curl} Example with parameter: - * curl -i /collection?type=projects&user=prang - * @apiExample {curl} Example with mix: - * curl -i /collection/skills?user=prang - * @apiExample {curl} Example multiple type: - * curl -i /collection/skills/projects/languages?user=prang - * - * @apiUse SuccessText - * @apiSuccessExample Example query languages: - * HTTP/1.1 200 OK - * { - * "languages":[ - * { - * "language":"English", - * "fluency":3 - * }, - * { - * "language":"Chinese", - * "fluency":2 - * }, - * { - * "language":"Thai", - * "fluency":1 - * } - * ] - * } - * @apiSuccessExample Example general results: - * HTTP/1.1 200 OK - * {"key": [{"value": "object"}]} - */ diff --git a/src/docs/index.apib b/src/docs/index.apib index 924e736..f360541 100644 --- a/src/docs/index.apib +++ b/src/docs/index.apib @@ -114,7 +114,7 @@ This APIs is a wrapper of Github APIs so if you query too much Github will block + `th` + type (optional, enum[string]) - type of the query - If you add type via url path this can appear multiple times. + This `type` can appear multiple times + Default: `projects` + Members @@ -129,8 +129,8 @@ This APIs is a wrapper of Github APIs so if you query too much Github will block + Request with authorization - + Headers - Authorization: token OAUTH-TOKEN + + Headers + Authorization: token OAUTH-TOKEN + Response 200 (application/json) @@ -174,35 +174,47 @@ This APIs is a wrapper of Github APIs so if you query too much Github will block # Group Images -## Question Collection [/questions] +## Logo collection [/logo{branch}{color}{size}{type}{extension}{?branch,color,size,type,extension}] -### List All Questions [GET] +### get logo [GET] -+ Response 200 (application/json) ++ Parameters + + branch (optional, enum[string]) - branch to query the information + + Default: `master` + + Members + + `master` + + `dev + + color (optional, enum[string]) - color of the logo + + Default: `primary` + + Members + + `primary` + + `light` + + `dark` + + size (optional, enum[string]) - size of the logo - [ - { - "question": "Favourite programming language?", - "published_at": "2014-11-11T08:40:51.620Z", - "url": "/questions/1", - "choices": [ - { - "choice": "Swift", - "url": "/questions/1/choices/1", - "votes": 2048 - }, { - "choice": "Python", - "url": "/questions/1/choices/2", - "votes": 1024 - }, { - "choice": "Objective-C", - "url": "/questions/1/choices/3", - "votes": 512 - }, { - "choice": "Ruby", - "url": "/questions/1/choices/4", - "votes": 256 - } - ] - } - ] \ No newline at end of file + Normal is mean `512×512` + Smaller is `256x256` + High is mean high resolution at `300ppi` in `512x512` + + + Default: `normal` + + Members + + `normal` + + `small` + + `high` + + type (optional, enum[string]) - type of the logo + + Default: `normal` + + Members + + `normal` + + `round` + + extension (optional, enum[string]) - extension + + If you enter svg, `size` will be ignore since svg don't have size + + + Default: `png` + + Members + + `png` + + `svg` + ++ Response 200 (application/svg) + ++ Response 200 (application/png) diff --git a/src/docs/index.doc.js b/src/docs/index.doc.js deleted file mode 100644 index 83dfca8..0000000 --- a/src/docs/index.doc.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: Changes doc generate to https://github.com/danielgtaylor/aglio \ No newline at end of file diff --git a/src/docs/personal.doc.js b/src/docs/personal.doc.js deleted file mode 100644 index c4c823c..0000000 --- a/src/docs/personal.doc.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @apiDefine Parameters - * - * @apiParam {string=net,prang} [user="net"] username to query - * @apiParam {string=master,dev} [branch="master"] branch to query data - * @apiParam {string=information,social} [type="information"] type of the dataset - * - */ - -/** - * @apiDefine SuccessText - * - * @apiSuccess {Object} response This result of personal information, you will find the schema at https://github.com/kcnt-info/website on static folder, and personal information section in json file - */ - -/** - * @api {get} /personal/:user/:type Personal Information - * @apiDescription This will fetch personal data with custom type. `NOTE: you can query with /:type/:user too`. - * You can omit :user and :type, with this way it will query default value of each param - * - * @apiName GetPersonal - * @apiGroup Personal - * - * @apiUse Parameters - * - * @apiExample {curl} Example with path: - * curl -i /personal/net/information - * @apiExample {curl} Example with parameter: - * curl -i /personal?user=net&type=information - * @apiExample {curl} Example with mix: - * curl -i /personal/net?type=information - * - * @apiUse SuccessText - * @apiSuccessExample Information query example: - * HTTP/1.1 200 OK - * { - * "abbreviation":"kc", - * "picture":"/resources/images/base0137-small.jpg", - * "name":{ - * "firstName":"Kamontat", - * "lastName":"Chantrachirathumrong" - * }, - * "nickname":"net", - * "email":"kamontat_c@hotmail.com", - * "birthday":"08-Nov-1996", - * "location":{ - * "address":"8/32 Soi Phahon Yothin 34 Yaek 2", - * "city":"Chatuchak", - * "postalCode":"10900", - * "country":"Thailand" - * }, - * "phone":"0851811177" - * } - * @apiSuccessExample Social query example: - * HTTP/1.1 200 OK - * { - * "facebook":"kamontatc", - * "twitter":"kamontatc", - * "linkedin":"kamontat", - * "github":"kamontat", - * "website":"https://kcnt.info" - * } - */ diff --git a/yarn.lock b/yarn.lock index 8d4c398..54233d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -870,7 +870,7 @@ aglio-theme-olio@^1.6.3: moment "^2.8.4" stylus "^0.51.1" -aglio@^2.3.0: +aglio@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/aglio/-/aglio-2.3.0.tgz#9f7462f01520996415278a02d0e20b36ec96adcc" integrity sha1-n3Ri8BUgmWQVJ4oC0OILNuyWrcw= @@ -1215,7 +1215,7 @@ blob@0.0.4: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= -bluebird@^3.5.1: +bluebird@3.5.3, bluebird@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==