diff --git a/public/index.html b/public/index.html
index 0ad01da..b467ec5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -74,6 +74,7 @@
+
diff --git a/public/partials/admin/conferences.htm b/public/partials/admin/conferences.htm
new file mode 100644
index 0000000..171f797
--- /dev/null
+++ b/public/partials/admin/conferences.htm
@@ -0,0 +1,290 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
+ Conference ID: '{{conference.conferenceID}}'
+
+
+
+
+
+ {{::conference.repositories.length || 0 | number}}
+
+
+ Total: {{conference.price || 0 | number}} €
+
+
+
+ From {{conference.startDate | date}} to {{conference.endDate |
+ date}}
+
+
+
+
+ -
+ There is no conference to display.
+
+
+
+
diff --git a/public/partials/admin/queues.htm b/public/partials/admin/queues.htm
new file mode 100644
index 0000000..3006c4b
--- /dev/null
+++ b/public/partials/admin/queues.htm
@@ -0,0 +1,86 @@
+
+
+
Download jobs
+
+ -
+
+ {{job}}
+
+
+
+ -
+ There is no job to display.
+
+
+
Remove jobs
+
+ -
+
+ {{job}}
+
+
+
+ -
+ There is no job to display.
+
+
+
+
diff --git a/public/partials/admin/repositories.htm b/public/partials/admin/repositories.htm
new file mode 100644
index 0000000..8874a6f
--- /dev/null
+++ b/public/partials/admin/repositories.htm
@@ -0,0 +1,367 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @
+
+ anonymized {{repo.anonymizeDate | humanTime}}
+
+
+
+
+
+ {{repo.conference}}
+
+
+
+ {{::repo.options.terms.length | number}}
+
+
+ {{::repo.size.storage |
+ humanFileSize}}
+
+
+ {{::repo.pageView || 0 | number}}
+
+
+
+ Last view: {{::repo.lastView | humanTime}}
+
+
+ Expire: {{repo.options.expirationDate | humanTime}}
+
+
+
+
+ -
+ There is no repository to display.
+
+
+
+
diff --git a/public/partials/admin/users.htm b/public/partials/admin/users.htm
new file mode 100644
index 0000000..1edb6b5
--- /dev/null
+++ b/public/partials/admin/users.htm
@@ -0,0 +1,213 @@
+
+
+
+
+ -
+
+
+
+ {{user.username}}
+
+
+
+
+
+
+
+
+ -
+ There is no user to display.
+
+
+
+
diff --git a/public/partials/header.htm b/public/partials/header.htm
index 14cc323..f5fc973 100644
--- a/public/partials/header.htm
+++ b/public/partials/header.htm
@@ -47,6 +47,29 @@
>Anonymize
+
+
+
+
-
diff --git a/public/script/admin.js b/public/script/admin.js
new file mode 100644
index 0000000..73e6a9f
--- /dev/null
+++ b/public/script/admin.js
@@ -0,0 +1,206 @@
+angular
+ .module("admin", [])
+ .controller("repositoriesAdminController", [
+ "$scope",
+ "$http",
+ "$location",
+ function ($scope, $http, $location) {
+ $scope.$watch("user.status", () => {
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+ });
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+
+ $scope.repositories = [];
+ $scope.total = -1;
+ $scope.totalPage = 0;
+ $scope.query = {
+ page: 1,
+ limit: 25,
+ sort: "source.repositoryName",
+ search: "",
+ ready: true,
+ expired: true,
+ removed: true,
+ error: true,
+ preparing: true,
+ };
+
+ function getRepositories() {
+ $http.get("/api/admin/repos", { params: $scope.query }).then(
+ (res) => {
+ $scope.total = res.data.total;
+ $scope.totalPage = Math.ceil(res.data.total / $scope.query.limit);
+ $scope.repositories = res.data.results;
+ $scope.$apply();
+ },
+ (err) => {
+ console.error(err);
+ }
+ );
+ }
+ getRepositories();
+
+ let timeClear = null;
+ $scope.$watch(
+ "query",
+ () => {
+ clearTimeout(timeClear);
+ timeClear = setTimeout(getRepositories, 500);
+ },
+ true
+ );
+ },
+ ])
+ .controller("usersAdminController", [
+ "$scope",
+ "$http",
+ "$location",
+ function ($scope, $http, $location) {
+ $scope.$watch("user.status", () => {
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+ });
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+
+ $scope.users = [];
+ $scope.total = -1;
+ $scope.totalPage = 0;
+ $scope.query = {
+ page: 1,
+ limit: 25,
+ sort: "username",
+ search: "",
+ };
+
+ function getUsers() {
+ $http.get("/api/admin/users", { params: $scope.query }).then(
+ (res) => {
+ $scope.total = res.data.total;
+ $scope.totalPage = Math.ceil(res.data.total / $scope.query.limit);
+ $scope.users = res.data.results;
+ $scope.$apply();
+ },
+ (err) => {
+ console.error(err);
+ }
+ );
+ }
+ getUsers();
+
+ let timeClear = null;
+ $scope.$watch(
+ "query",
+ () => {
+ clearTimeout(timeClear);
+ timeClear = setTimeout(getUsers, 500);
+ },
+ true
+ );
+ },
+ ])
+ .controller("conferencesAdminController", [
+ "$scope",
+ "$http",
+ "$location",
+ function ($scope, $http, $location) {
+ $scope.$watch("user.status", () => {
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+ });
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+
+ $scope.conferences = [];
+ $scope.total = -1;
+ $scope.totalPage = 0;
+ $scope.query = {
+ page: 1,
+ limit: 25,
+ sort: "name",
+ search: ""
+ };
+
+ function getConferences() {
+ $http.get("/api/admin/conferences", { params: $scope.query }).then(
+ (res) => {
+ $scope.total = res.data.total;
+ $scope.totalPage = Math.ceil(res.data.total / $scope.query.limit);
+ $scope.conferences = res.data.results;
+ $scope.$apply();
+ },
+ (err) => {
+ console.error(err);
+ }
+ );
+ }
+ getConferences();
+
+ let timeClear = null;
+ $scope.$watch(
+ "query",
+ () => {
+ clearTimeout(timeClear);
+ timeClear = setTimeout(getConferences, 500);
+ },
+ true
+ );
+ },
+ ]).controller("queuesAdminController", [
+ "$scope",
+ "$http",
+ "$location",
+ function ($scope, $http, $location) {
+ $scope.$watch("user.status", () => {
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+ });
+ if ($scope.user == null) {
+ $location.url("/");
+ }
+
+ $scope.downloadJobs = [];
+ $scope.removeJobs = [];
+ $scope.total = -1;
+ $scope.totalPage = 0;
+ $scope.query = {
+ page: 1,
+ limit: 25,
+ sort: "name",
+ search: ""
+ };
+
+ function getQueues() {
+ $http.get("/api/admin/queues", { params: $scope.query }).then(
+ (res) => {
+ $scope.downloadJobs = res.data.downloadQueue;
+ $scope.removeJobs = res.data.removeQueue;
+ $scope.$apply();
+ },
+ (err) => {
+ console.error(err);
+ }
+ );
+ }
+ getQueues();
+
+ let timeClear = null;
+ $scope.$watch(
+ "query",
+ () => {
+ clearTimeout(timeClear);
+ timeClear = setTimeout(getQueues, 500);
+ },
+ true
+ );
+ },
+ ]);
diff --git a/public/script/app.js b/public/script/app.js
index 2600bc6..a7f6a41 100644
--- a/public/script/app.js
+++ b/public/script/app.js
@@ -6,6 +6,7 @@ angular
"ngPDFViewer",
"pascalprecht.translate",
"angular-google-analytics",
+ "admin",
])
.config(function (
$routeProvider,
@@ -90,6 +91,26 @@ angular
title: "Anonymized Repository - Anonymous GitHub",
reloadOnUrl: false,
})
+ .when("/admin/", {
+ templateUrl: "/partials/admin/repositories.htm",
+ controller: "repositoriesAdminController",
+ title: "Repositories Admin - Anonymous GitHub",
+ })
+ .when("/admin/users", {
+ templateUrl: "/partials/admin/users.htm",
+ controller: "usersAdminController",
+ title: "Users Admin - Anonymous GitHub",
+ })
+ .when("/admin/conferences", {
+ templateUrl: "/partials/admin/conferences.htm",
+ controller: "conferencesAdminController",
+ title: "Conferences Admin - Anonymous GitHub",
+ })
+ .when("/admin/queues", {
+ templateUrl: "/partials/admin/queues.htm",
+ controller: "queuesAdminController",
+ title: "Queues Admin - Anonymous GitHub",
+ })
.when("/404", {
templateUrl: "/partials/404.htm",
title: "Page not found - Anonymous GitHub",
diff --git a/src/routes/admin.ts b/src/routes/admin.ts
new file mode 100644
index 0000000..4e09ad7
--- /dev/null
+++ b/src/routes/admin.ts
@@ -0,0 +1,165 @@
+import * as express from "express";
+import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
+import ConferenceModel from "../database/conference/conferences.model";
+import RepositoryModel from "../database/repositories/repositories.model";
+import UserModel from "../database/users/users.model";
+import { downloadQueue, removeQueue } from "../queue";
+import Repository from "../Repository";
+import { ensureAuthenticated } from "./connection";
+import { handleError, getUser, isOwnerOrAdmin } from "./route-utils";
+
+const router = express.Router();
+
+// user needs to be connected for all user API
+router.use(ensureAuthenticated);
+router.use(
+ async (
+ req: express.Request,
+ res: express.Response,
+ next: express.NextFunction
+ ) => {
+ const user = await getUser(req);
+ try {
+ // only admins are allowed here
+ isOwnerOrAdmin([], user);
+ next();
+ } catch (error) {
+ handleError(error, res);
+ }
+ }
+);
+
+router.get("/queues", async (req, res) => {
+ const out = await Promise.all([
+ downloadQueue.getJobs([
+ "waiting",
+ "active",
+ "completed",
+ "failed",
+ "delayed",
+ ]),
+ removeQueue.getJobs([
+ "waiting",
+ "active",
+ "completed",
+ "failed",
+ "delayed",
+ ]),
+ ]);
+ res.json({
+ downloadQueue: out[0],
+ removeQueue: out[1],
+ });
+});
+
+router.get("/repos", async (req, res) => {
+ const page = parseInt(req.query.page as string) || 1;
+ const limit = parseInt(req.query.limit as string) || 10;
+ const ready = req.query.ready == "true";
+ const error = req.query.error == "true";
+ const preparing = req.query.preparing == "true";
+ const remove = req.query.remove == "true";
+ const expired = req.query.expired == "true";
+
+ let sort: any = { _id: 1 };
+ if (req.query.sort) {
+ sort = {};
+ sort[req.query.sort as string] = -1;
+ }
+ let query = [];
+ if (req.query.search) {
+ query.push({ repoId: { $regex: req.query.search } });
+ }
+ let status = [];
+ query.push({ $or: status });
+ if (ready) {
+ status.push({ status: "ready" });
+ }
+ if (error) {
+ status.push({ status: "error" });
+ }
+ if (expired) {
+ status.push({ status: "expiring" });
+ status.push({ status: "expired" });
+ }
+ if (remove) {
+ status.push({ status: "removing" });
+ status.push({ status: "removed" });
+ }
+ if (preparing) {
+ status.push({ status: "preparing" });
+ status.push({ status: "download" });
+ }
+ const skipIndex = (page - 1) * limit;
+ res.json({
+ query: { $and: query },
+ page,
+ total: await AnonymizedRepositoryModel.find({ $and: query }).estimatedDocumentCount(),
+ sort,
+ results: await AnonymizedRepositoryModel.find({ $and: query })
+ .sort(sort)
+ .limit(limit)
+ .skip(skipIndex),
+ });
+});
+
+router.get("/users", async (req, res) => {
+ const page = parseInt(req.query.page as string) || 1;
+ const limit = parseInt(req.query.limit as string) || 10;
+ const skipIndex = (page - 1) * limit;
+
+ let sort: any = { _id: 1 };
+ if (req.query.sort) {
+ sort = {};
+ sort[req.query.sort as string] = -1;
+ }
+ let query = {};
+ if (req.query.search) {
+ query = { username: { $regex: req.query.search } };
+ }
+
+ res.json({
+ query: query,
+ page,
+ total: await UserModel.find(query).estimatedDocumentCount(),
+ sort,
+ results: await UserModel.find(query)
+ .sort(sort)
+ .limit(limit)
+ .skip(skipIndex),
+ });
+});
+
+router.get("/conferences", async (req, res) => {
+ const page = parseInt(req.query.page as string) || 1;
+ const limit = parseInt(req.query.limit as string) || 10;
+ const skipIndex = (page - 1) * limit;
+
+ let sort: any = { _id: 1 };
+ if (req.query.sort) {
+ sort = {};
+ sort[req.query.sort as string] = -1;
+ }
+ let query = {};
+ if (req.query.search) {
+ query = {
+ $or: [
+ { name: { $regex: req.query.search } },
+ { conferenceID: { $regex: req.query.search } },
+ ],
+ };
+ }
+
+ res.json({
+ query: query,
+ page,
+ total: await ConferenceModel.find(query).estimatedDocumentCount(),
+ sort,
+ results: await ConferenceModel.find(query)
+ .sort(sort)
+ .limit(limit)
+ .skip(skipIndex),
+ });
+});
+
+export default router;
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 6511bf4..3e0174e 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -5,6 +5,7 @@ import file from "./file";
import webview from "./webview";
import user from "./user";
import option from "./option";
+import admin from "./admin";
export default {
repositoryPrivate,
@@ -13,5 +14,6 @@ export default {
webview,
user,
option,
- conference
+ conference,
+ admin,
};
diff --git a/src/routes/user.ts b/src/routes/user.ts
index 41a727b..252bc7b 100644
--- a/src/routes/user.ts
+++ b/src/routes/user.ts
@@ -20,7 +20,11 @@ router.get("/logout", async (req: express.Request, res: express.Response) => {
router.get("/", async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
- res.json({ username: user.username, photo: user.photo });
+ res.json({
+ username: user.username,
+ photo: user.photo,
+ isAdmin: user.isAdmin,
+ });
} catch (error) {
handleError(error, res);
}
diff --git a/src/server.ts b/src/server.ts
index 4709ab3..00ca5e2 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -59,6 +59,7 @@ export default async function start() {
app.use("/github", rate, connection.router);
// api routes
+ app.use("/api/admin", rate, router.admin);
app.use("/api/options", rate, router.option);
app.use("/api/conferences", rate, router.conference);
app.use("/api/user", rate, router.user);