UNPKG

5.52 kBJavaScriptView Raw
1/*
2 * Copyright 2018 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13'use strict';
14
15const path = require('path');
16
17const git = require('isomorphic-git');
18git.plugins.set('fs', require('fs'));
19const { debug } = require('@adobe/helix-log');
20const { resolveCommit, getObject } = require('./git');
21const { resolveRepositoryPath } = require('./utils');
22
23/**
24 * Export the api handler (express middleware) through a parameterizable function
25 *
26 * @param {object} options configuration hash
27 * @returns {function(*, *, *)} handler function
28 */
29function createMiddleware(options) {
30 /**
31 * Express middleware handling Git API Contents requests
32 *
33 * Only a small subset will be implemented
34 *
35 * @param {Request} req Request object
36 * @param {Response} res Response object
37 * @param {callback} next next middleware in chain
38 *
39 * @see https://developer.github.com/v3/repos/contents/#get-contents
40 */
41 return (req, res, next) => {
42 // GET /repos/:owner/:repo/contents/:path?ref=:ref
43 const { owner } = req.params;
44 const repoName = req.params.repo;
45 const refName = req.query.ref || 'master';
46 let fpath = req.params[0] || '';
47
48 // issue #247: lenient handling of redundant leading slashes in path
49 while (fpath.length && fpath[0] === '/') {
50 // trim leading slash
51 fpath = fpath.substr(1);
52 }
53
54 const repPath = resolveRepositoryPath(options, owner, repoName);
55
56 async function dirEntryToJson(sha, dirPath) {
57 const host = req.mappedSubDomain ? `localhost:${options.listen[req.protocol].port}` : req.headers.host;
58 const url = `${req.protocol}://${host}${req.path}?ref=${refName}`;
59 const gitUrl = `${req.protocol}://${host}/api/repos/${owner}/${repoName}/git/trees/${sha}`;
60 const htmlUrl = `${req.protocol}://${host}/${owner}/${repoName}/tree/${refName}/${dirPath}`;
61 return {
62 type: 'dir',
63 name: path.basename(dirPath),
64 path: dirPath,
65 sha,
66 size: 0,
67 url,
68 html_url: htmlUrl,
69 git_url: gitUrl,
70 download_url: null,
71 _links: {
72 self: url,
73 git: gitUrl,
74 html: htmlUrl,
75 },
76 };
77 }
78
79 async function fileEntryToJson(sha, content, filePath, withContent) {
80 const host = req.mappedSubDomain ? `localhost:${options.listen[req.protocol].port}` : req.headers.host;
81 const url = `${req.protocol}://${host}${req.path}?ref=${refName}`;
82 const gitUrl = `${req.protocol}://${host}/api/repos/${owner}/${repoName}/git/blobs/${sha}`;
83 const htmlUrl = `${req.protocol}://${host}/${owner}/${repoName}/blob/${refName}/${filePath}`;
84 const rawlUrl = `${req.protocol}://${host}/raw/${owner}/${repoName}/${refName}/${filePath}`;
85 const result = {
86 type: 'file',
87 name: path.basename(filePath),
88 path: filePath,
89 sha,
90 size: content.length,
91 url,
92 html_url: htmlUrl,
93 git_url: gitUrl,
94 download_url: rawlUrl,
95 _links: {
96 self: url,
97 git: gitUrl,
98 html: htmlUrl,
99 },
100 };
101 if (withContent) {
102 result.content = `${content.toString('base64')}\n`;
103 result.encoding = 'base64';
104 }
105 return result;
106 }
107
108 async function treeEntriesToJson(entries, dirPath) {
109 return Promise.all(entries.map(async (entry) => {
110 if (entry.type === 'blob') {
111 const { object: content } = await getObject(repPath, entry.oid);
112 return fileEntryToJson(entry.oid, content, path.join(dirPath, entry.path), false);
113 }
114 return dirEntryToJson(entry.oid, path.join(dirPath, entry.path));
115 }));
116 }
117
118 resolveCommit(repPath, refName)
119 .then((commitOid) => git.readObject({ dir: repPath, oid: commitOid, filepath: fpath }))
120 .then(({ type, oid, object }) => {
121 if (type === 'blob') {
122 // file
123 return fileEntryToJson(oid, object, fpath, true);
124 }
125 // dir
126 return treeEntriesToJson(object.entries, fpath);
127 })
128 .then((json) => {
129 res.json(json);
130 })
131 .catch((err) => {
132 // TODO: use generic errors
133 if (err.code === 'ResolveRefError') {
134 debug(`[contentHandler] resource not found: ${err.message}`);
135 res.status(404).json({
136 message: `No commit found for the ref ${refName}`,
137 documentation_url: 'https://developer.github.com/v3/repos/contents/',
138 });
139 } else if (err.code === 'TreeOrBlobNotFoundError') {
140 debug(`[contentHandler] resource not found: ${err.message}`);
141 res.status(404).json({
142 message: 'Not Found',
143 documentation_url: 'https://developer.github.com/v3/repos/contents/#get-contents',
144 });
145 } else {
146 debug(`[contentHandler] code: ${err.code} message: ${err.message} stack: ${err.stack}`);
147 next(err);
148 }
149 });
150 };
151}
152module.exports = createMiddleware;