mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-03-10 06:35:31 +00:00
migrate JavaScript to TypeScript
This commit is contained in:
107
src/routes/connection.ts
Normal file
107
src/routes/connection.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as redis from "redis";
|
||||
import * as passport from "passport";
|
||||
import * as session from "express-session";
|
||||
import * as connectRedis from "connect-redis";
|
||||
import * as OAuth2Strategy from "passport-oauth2";
|
||||
import { Profile, Strategy } from "passport-github2";
|
||||
import * as express from "express";
|
||||
|
||||
import config from "../../config";
|
||||
import UserModel from "../database/users/users.model";
|
||||
|
||||
const RedisStore = connectRedis(session);
|
||||
|
||||
export function ensureAuthenticated(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
) {
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
res.status(401).json({ error: "not_connected" });
|
||||
}
|
||||
|
||||
const verify = async (
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Profile,
|
||||
done: OAuth2Strategy.VerifyCallback
|
||||
): Promise<void> => {
|
||||
let user;
|
||||
try {
|
||||
user = await UserModel.findOne({ username: profile.username });
|
||||
if (user) {
|
||||
user.accessToken = accessToken;
|
||||
user.email = profile.emails[0]?.value;
|
||||
user.photo = profile.photos[0]?.value;
|
||||
await user.save();
|
||||
} else {
|
||||
user = await new UserModel({
|
||||
username: profile.username,
|
||||
accessToken: accessToken,
|
||||
email: profile.emails[0]?.value,
|
||||
photo: profile.photos[0]?.value,
|
||||
}).save();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
done(null, {
|
||||
username: profile.username,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
profile,
|
||||
user,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
passport.use(
|
||||
new Strategy(
|
||||
{
|
||||
clientID: config.CLIENT_ID,
|
||||
clientSecret: config.CLIENT_SECRET,
|
||||
callbackURL: config.AUTH_CALLBACK,
|
||||
},
|
||||
verify
|
||||
)
|
||||
);
|
||||
|
||||
passport.serializeUser((user: Express.User, done) => {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser((user: Express.User, done) => {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
export const appSession = session({
|
||||
secret: "keyboard cat",
|
||||
store: new RedisStore({
|
||||
client: redis.createClient({
|
||||
port: config.REDIS_PORT,
|
||||
host: config.REDIS_HOSTNAME,
|
||||
}),
|
||||
}),
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
});
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.get(
|
||||
"/login",
|
||||
passport.authenticate("github", { scope: ["repo"] }), // Note the scope here
|
||||
function (req: express.Request, res: express.Response) {
|
||||
res.redirect("/");
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/auth",
|
||||
passport.authenticate("github", { failureRedirect: "/" }),
|
||||
function (req: express.Request, res: express.Response) {
|
||||
res.redirect("/");
|
||||
}
|
||||
);
|
||||
38
src/routes/file.ts
Normal file
38
src/routes/file.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as express from "express";
|
||||
import AnonymizedFile from "../AnonymizedFile";
|
||||
import { getRepo, handleError } from "./route-utils";
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.get(
|
||||
"/:repoId/file/:path*",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
let anonymizedPath = req.params.path;
|
||||
if (req.params[0]) {
|
||||
anonymizedPath += req.params[0];
|
||||
}
|
||||
anonymizedPath = anonymizedPath;
|
||||
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
|
||||
await repo.countView();
|
||||
|
||||
try {
|
||||
const f = new AnonymizedFile(repo, {
|
||||
anonymizedPath,
|
||||
});
|
||||
if (!(await f.isFileSupported())) {
|
||||
return res.status(500).send({ error: "file_not_supported" });
|
||||
}
|
||||
res.attachment(
|
||||
anonymizedPath.substring(anonymizedPath.lastIndexOf("/") + 1)
|
||||
);
|
||||
await f.send(res);
|
||||
} catch (error) {
|
||||
return handleError(error, res);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
13
src/routes/index.ts
Normal file
13
src/routes/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import repositoryPrivate from "./repository-private";
|
||||
import repositoryPublic from "./repository-public";
|
||||
import file from "./file";
|
||||
import webview from "./webview";
|
||||
import user from "./user";
|
||||
|
||||
export default {
|
||||
repositoryPrivate,
|
||||
repositoryPublic,
|
||||
file,
|
||||
webview,
|
||||
user,
|
||||
};
|
||||
270
src/routes/repository-private.ts
Normal file
270
src/routes/repository-private.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import * as express from "express";
|
||||
import { ensureAuthenticated } from "./connection";
|
||||
|
||||
import * as db from "../database/database";
|
||||
import { getRepo, getUser, handleError } from "./route-utils";
|
||||
import RepositoryModel from "../database/repositories/repositories.model";
|
||||
import {
|
||||
GitHubRepository,
|
||||
getRepositoryFromGitHub,
|
||||
} from "../source/GitHubRepository";
|
||||
import gh = require("parse-github-url");
|
||||
import GitHubBase from "../source/GitHubBase";
|
||||
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
|
||||
import config from "../../config";
|
||||
import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types";
|
||||
import Repository from "../Repository";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// user needs to be connected for all user API
|
||||
router.use(ensureAuthenticated);
|
||||
|
||||
// claim a repository
|
||||
router.post("/claim", async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
if (!req.body.repoId) {
|
||||
return res.status(500).json({ error: "repoId_not_defined" });
|
||||
}
|
||||
if (!req.body.repoUrl) {
|
||||
return res.status(500).json({ error: "repoUrl_not_defined" });
|
||||
}
|
||||
|
||||
const repoConfig = await db.getRepository(req.body.repoId);
|
||||
if (repoConfig == null) {
|
||||
return res.status(500).json({ error: "repo_not_found" });
|
||||
}
|
||||
|
||||
const r = gh(req.body.repoUrl);
|
||||
const repo = await getRepositoryFromGitHub({
|
||||
owner: r.owner,
|
||||
repo: r.name,
|
||||
accessToken: user.accessToken,
|
||||
});
|
||||
if ((repoConfig.source as GitHubBase).githubRepository.id != repo.id) {
|
||||
return res.status(500).json({ error: "repo_not_found" });
|
||||
}
|
||||
|
||||
console.log(`${user.username} claims ${r.repository}.`);
|
||||
repoConfig.owner = user;
|
||||
|
||||
await AnonymizedRepositoryModel.updateOne(
|
||||
{ repoId: repoConfig.repoId },
|
||||
{ $set: { owner: user.username } }
|
||||
);
|
||||
return res.send("Ok");
|
||||
} catch (error) {
|
||||
console.error(req.path, error);
|
||||
return res.status(500).json({ error });
|
||||
}
|
||||
});
|
||||
|
||||
// refresh a repository
|
||||
router.post(
|
||||
"/:repoId/refresh",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
const user = await getUser(req);
|
||||
if (repo.owner.username != user.username) {
|
||||
return res.status(401).json({ error: "not_authorized" });
|
||||
}
|
||||
await repo.anonymize();
|
||||
res.end("ok");
|
||||
}
|
||||
);
|
||||
|
||||
// delete a repository
|
||||
router.delete(
|
||||
"/:repoId/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res, { nocheck: false });
|
||||
if (!repo) return;
|
||||
const user = await getUser(req);
|
||||
if (repo.owner.username != user.username) {
|
||||
return res.status(401).json({ error: "not_authorized" });
|
||||
}
|
||||
await repo.remove();
|
||||
console.log(`${req.params.repoId} is removed`);
|
||||
return res.json("ok");
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:owner/:repo/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
const repo = await getRepositoryFromGitHub({
|
||||
owner: req.params.owner,
|
||||
repo: req.params.repo,
|
||||
accessToken: user.accessToken,
|
||||
});
|
||||
res.json(repo.toJSON());
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:owner/:repo/branches",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
const repository = await getRepositoryFromGitHub({
|
||||
accessToken: user.accessToken,
|
||||
owner: req.params.owner,
|
||||
repo: req.params.repo,
|
||||
});
|
||||
return res.json(
|
||||
await repository.branches({
|
||||
accessToken: user.accessToken,
|
||||
force: req.query.force == "1",
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:owner/:repo/readme",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
const repo = await RepositoryModel.findOne({
|
||||
name: `${req.params.owner}/${req.params.repo}`,
|
||||
});
|
||||
if (!repo) return res.status(404).send({ error: "repo_not_found" });
|
||||
const repository = new GitHubRepository(repo);
|
||||
return res.send(
|
||||
await repository.readme({
|
||||
accessToken: user.accessToken,
|
||||
force: req.query.force == "1",
|
||||
branch: req.query.branch as string,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function validateNewRepo(repoUpdate) {
|
||||
const validCharacters = /^[0-9a-zA-Z\-\_]+$/;
|
||||
if (
|
||||
!repoUpdate.repoId.match(validCharacters) ||
|
||||
repoUpdate.repoId.length < 3
|
||||
) {
|
||||
throw new Error("invalid_repoId");
|
||||
}
|
||||
if (!repoUpdate.branch) {
|
||||
throw new Error("branch_not_specified");
|
||||
}
|
||||
if (!repoUpdate.options) {
|
||||
throw new Error("options_not_provided");
|
||||
}
|
||||
if (!Array.isArray(repoUpdate.terms)) {
|
||||
throw new Error("invalid_terms_format");
|
||||
}
|
||||
if (!/^[a-f0-9]+$/.test(repoUpdate.commit)) {
|
||||
throw new Error("invalid_commit_format");
|
||||
}
|
||||
}
|
||||
|
||||
function updateRepoModel(model: IAnonymizedRepositoryDocument, repoUpdate) {
|
||||
model.source.commit = repoUpdate.commit;
|
||||
model.source.branch = repoUpdate.branch;
|
||||
model.conference = repoUpdate.conference;
|
||||
model.options = {
|
||||
terms: repoUpdate.terms,
|
||||
expirationMode: repoUpdate.options.expirationMode,
|
||||
expirationDate: repoUpdate.options.expirationDate
|
||||
? new Date(repoUpdate.options.expirationDate)
|
||||
: null,
|
||||
update: repoUpdate.options.update,
|
||||
image: repoUpdate.options.image,
|
||||
pdf: repoUpdate.options.pdf,
|
||||
notebook: repoUpdate.options.notebook,
|
||||
link: repoUpdate.options.link,
|
||||
page: repoUpdate.options.page,
|
||||
pageSource: repoUpdate.options.pageSource,
|
||||
};
|
||||
}
|
||||
// update a repository
|
||||
router.post(
|
||||
"/:repoId/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res, { nocheck: true });
|
||||
if (!repo) return;
|
||||
const user = await getUser(req);
|
||||
|
||||
if (repo.owner.username != user.username) {
|
||||
return res.status(401).json({ error: "not_authorized" });
|
||||
}
|
||||
|
||||
const repoUpdate = req.body;
|
||||
|
||||
try {
|
||||
validateNewRepo(repoUpdate);
|
||||
} catch (error) {
|
||||
return handleError(error, res);
|
||||
}
|
||||
|
||||
if (repoUpdate.commit != repo.model.source.commit) {
|
||||
repo.model.anonymizeDate = new Date();
|
||||
repo.model.source.commit = repoUpdate.commit;
|
||||
}
|
||||
|
||||
updateRepoModel(repo.model, repoUpdate);
|
||||
|
||||
await repo.updateStatus("preparing");
|
||||
|
||||
await repo.model.save();
|
||||
res.send("ok");
|
||||
repo.anonymize();
|
||||
}
|
||||
);
|
||||
|
||||
// add repository
|
||||
router.post("/", async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
const repoUpdate = req.body;
|
||||
|
||||
try {
|
||||
validateNewRepo(repoUpdate);
|
||||
} catch (error) {
|
||||
return handleError(error, res);
|
||||
}
|
||||
const r = gh(repoUpdate.fullName);
|
||||
const repository = await getRepositoryFromGitHub({
|
||||
accessToken: user.accessToken,
|
||||
owner: r.owner,
|
||||
repo: r.name,
|
||||
});
|
||||
const repo = new AnonymizedRepositoryModel();
|
||||
repo.repoId = repoUpdate.repoId;
|
||||
repo.anonymizeDate = new Date();
|
||||
repo.owner = user.username;
|
||||
repo.source = {
|
||||
type:
|
||||
repoUpdate.options.mode == "download" ? "GitHubDownload" : "GitHubStream",
|
||||
accessToken: user.accessToken,
|
||||
repositoryId: repository.model.id,
|
||||
repositoryName: repoUpdate.fullName,
|
||||
};
|
||||
|
||||
if (repo.source.type == "GitHubDownload") {
|
||||
// details.size is in kilobytes
|
||||
if (repository.size > config.MAX_REPO_SIZE) {
|
||||
return res.status(500).send({ error: "invalid_mode" });
|
||||
}
|
||||
}
|
||||
|
||||
updateRepoModel(repo, repoUpdate);
|
||||
|
||||
await repo.save();
|
||||
res.send("ok");
|
||||
new Repository(repo).anonymize();
|
||||
});
|
||||
|
||||
export default router;
|
||||
43
src/routes/repository-public.ts
Normal file
43
src/routes/repository-public.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as express from "express";
|
||||
|
||||
import * as db from "../database/database";
|
||||
import { getRepo, getUser, handleError } from "./route-utils";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/:repoId/", async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res, { nocheck: true });
|
||||
if (!repo) return;
|
||||
res.json((await db.getRepository(req.params.repoId)).toJSON());
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/:repoId/zip",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
res.attachment(`${repo.repoId}.zip`);
|
||||
repo.zip().pipe(res);
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:repoId/files",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
res.json(await repo.anonymizedFiles({ force: true }));
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:repoId/options",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
await repo.updateIfNeeded();
|
||||
res.json(repo.options);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
63
src/routes/route-utils.ts
Normal file
63
src/routes/route-utils.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as express from "express";
|
||||
import * as db from "../database/database";
|
||||
import UserModel from "../database/users/users.model";
|
||||
import User from "../User";
|
||||
|
||||
export async function getRepo(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
opt?: { nocheck?: boolean }
|
||||
) {
|
||||
try {
|
||||
const repo = await db.getRepository(req.params.repoId);
|
||||
if (opt?.nocheck == true) {
|
||||
} else {
|
||||
// redirect if the repository is expired
|
||||
if (
|
||||
repo.status == "expired" &&
|
||||
repo.options.expirationMode == "redirect" &&
|
||||
repo.source.url
|
||||
) {
|
||||
res.redirect(repo.source.url);
|
||||
return null;
|
||||
}
|
||||
|
||||
repo.check();
|
||||
}
|
||||
return repo;
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(error: any, res: express.Response) {
|
||||
console.log(error);
|
||||
let message = error;
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
let status = 500;
|
||||
if (message && message.indexOf("not_found") > -1) {
|
||||
status = 400;
|
||||
} else if (message && message.indexOf("not_connected") > -1) {
|
||||
status = 401;
|
||||
}
|
||||
|
||||
res.status(status).send({ error: message });
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getUser(req: express.Request) {
|
||||
const user = (req.user as any).user;
|
||||
if (!user) {
|
||||
req.logout();
|
||||
throw new Error("not_connected");
|
||||
}
|
||||
const model = await UserModel.findById(user._id);
|
||||
if (!model) {
|
||||
req.logout();
|
||||
throw new Error("not_connected");
|
||||
}
|
||||
return new User(model);
|
||||
}
|
||||
96
src/routes/user.ts
Normal file
96
src/routes/user.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as express from "express";
|
||||
import config from "../../config";
|
||||
import { ensureAuthenticated } from "./connection";
|
||||
import { handleError, getUser } from "./route-utils";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// user needs to be connected for all user API
|
||||
router.use(ensureAuthenticated);
|
||||
|
||||
router.get("/logout", async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
req.logout();
|
||||
res.redirect("/");
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/", async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const user = await getUser(req);
|
||||
res.json({ username: user.username, photo: user.photo });
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/quota", async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const user = await getUser(req);
|
||||
const sizes = await Promise.all(
|
||||
(await user.getRepositories())
|
||||
.filter((r) => r.status == "ready")
|
||||
.map((r) => r.computeSize())
|
||||
);
|
||||
res.json({
|
||||
used: sizes.reduce((sum, i) => sum + i, 0),
|
||||
total: config.DEFAULT_QUOTA,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/default", async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
res.json(user.default);
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/default", async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
const d = req.body;
|
||||
user.default = d;
|
||||
res.send("ok");
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/anonymized_repositories",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
res.json(
|
||||
(await user.getRepositories()).map((x) => {
|
||||
return x.toJSON();
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/all_repositories",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
const repos = await user.getGitHubRepositories({
|
||||
force: req.query.force == "1",
|
||||
});
|
||||
res.json(
|
||||
repos.map((x) => {
|
||||
return {
|
||||
fullName: x.fullName,
|
||||
id: x.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
54
src/routes/webview.ts
Normal file
54
src/routes/webview.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as express from "express";
|
||||
import { getRepo, handleError } from "./route-utils";
|
||||
import * as path from "path";
|
||||
import AnonymizedFile from "../AnonymizedFile";
|
||||
import GitHubDownload from "../source/GitHubDownload";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
async function webView(req: express.Request, res: express.Response) {
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
try {
|
||||
if (!repo.options.page) {
|
||||
throw "page_not_activated";
|
||||
}
|
||||
if (!repo.options.pageSource) {
|
||||
throw "page_not_activated";
|
||||
}
|
||||
|
||||
if (
|
||||
repo.options.pageSource?.branch !=
|
||||
(repo.source as GitHubDownload).branch.name
|
||||
) {
|
||||
throw "page_not_supported_on_different_branch";
|
||||
}
|
||||
|
||||
let requestPath = path.join(
|
||||
repo.options.pageSource?.path,
|
||||
req.path.substring(
|
||||
req.path.indexOf(req.params.repoId) + req.params.repoId.length
|
||||
)
|
||||
);
|
||||
if (requestPath[requestPath.length - 1] == "/") {
|
||||
requestPath = path.join(requestPath, "index.html");
|
||||
}
|
||||
requestPath = requestPath;
|
||||
const f = new AnonymizedFile(repo, {
|
||||
anonymizedPath: requestPath,
|
||||
});
|
||||
if (!(await f.isFileSupported())) {
|
||||
return res.status(500).send({ error: "file_not_supported" });
|
||||
}
|
||||
f.send(res);
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/:repoId/*", webView);
|
||||
router.get("/:repoId", (req: express.Request, res: express.Response) => {
|
||||
res.redirect("/w" + req.url + "/");
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user