Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public profile pages and basic contribution management #96

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions adventure/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"viewDirectory": "views",
"resDirectory": "res",
"usersCanChangeTheme": true,
"allowContributions": true,

"constants": {
"tagMappings": {
Expand Down
33 changes: 33 additions & 0 deletions adventure/libraryRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,39 @@ function libraryRoute(req, res) {
});
}
}

// These are first so that they aren't overridden by the category routes
server.get("/library/contribute", restrictedRoute(), function (req, res) {
if (!config.allowContributions) { // If allowContributions is FALSE
return res.status(500).render("error", {
message: "Contributions aren't being accepted at this time."
});
}
return res.render("contribute", {
platformMappingsInverted: formatting.invertObject(config.constants.platformMappings)
});
});
server.get("/library/my/contributions", restrictedRoute(), function (req, res) {
var page = req.query.page || 1;
var uuidAsBuf = req.user.UserID;
database.execute("SELECT COUNT(*) FROM `Contributions` WHERE `UserUUID` = ?", [uuidAsBuf] ,function (cErr, cRes, cFields) {
var count = cRes[0]["COUNT(*)"];
var pages = Math.ceil(count / config.perPage);
database.execute("SELECT * FROM `Contributions` WHERE `UserUUID` = ? ORDER BY ContributionCreated DESC LIMIT ?,?", [uuidAsBuf, (page - 1) * config.perPage, config.perPage], function (coErr, coRes, coFields) {
var contributions = coRes.map(function (x) {
x.UserUUID = formatting.binToHex(x.UserUUID);
x.ContributionUUID = formatting.binToHex(x.ContributionUUID);
return x;
});
return res.render("mycontributions", {
contributions: contributions,
page: page,
pages: pages
});
});
});
});

server.get("/library/:category", libraryRoute);
server.get("/library/:category/:tag", libraryRoute);
server.get("/library", function (req, res) {
Expand Down
53 changes: 51 additions & 2 deletions adventure/saContributionRoutes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var express = require("express"),
var express = require("express"),
fs = require("fs"),
path = require("path"),
middleware = require("./middleware.js"),
Expand All @@ -18,7 +18,7 @@ server.get("/sa/contributions", restrictedRoute("sa"), function (req, res) {
var pages = Math.ceil(count / config.perPage);
database.execute("SELECT * FROM `Contributions` WHERE `Status` = ? ORDER BY ContributionCreated DESC LIMIT ?,?", [status, (page - 1) * config.perPage, config.perPage], function (coErr, coRes, coFields) {
var contributions = coRes.map(function (x) {
x.UserUUID = formatting.binToHex(x.ContributionUUID);
x.UserUUID = formatting.binToHex(x.UserUUID);
x.ContributionUUID = formatting.binToHex(x.ContributionUUID);
return x;
});
Expand All @@ -32,6 +32,55 @@ server.get("/sa/contributions", restrictedRoute("sa"), function (req, res) {
});
});

server.get("/sa/contribution/:contribution", restrictedRoute("sa"), function (req, res) {
database.execute("SELECT * FROM `Contributions` WHERE `ContributionUUID` = ?", [formatting.hexToBin(req.params.contribution)], function (cErr, cRes, cFields) {
// now get any perphiery stuff

var contribution = cRes[0] || null;
if (cErr || contribution == null) {
return res.status(404).render("error", {
message: "There is no contribution."
});
}

var contributions = cRes.map(function (x) {
x.UserUUID = formatting.binToHex(x.UserUUID);
x.ContributionUUID = formatting.binToHex(x.ContributionUUID);
return x;
});

return res.render("saContribution", {
contribution: contribution,
platformMappingsInverted: formatting.invertObject(config.constants.platformMappings)
});

});
});

server.post("/sa/rejectContribution/:contribution", restrictedRoute("sa"), urlencodedParser, function (req, res) {
if (req.body && req.body.rejectionReason) {
var uuid = req.params.contribution;
var uuidAsBuf = formatting.hexToBin(uuid);
var status = "Rejected";
var rejectionReason = req.body.rejectionReason;
var userIdAsBuf = req.user.UserID;
database.execute("UPDATE `Contributions` SET `Status` = ?, `RejectionReason` = ?, ProcessTime = NOW(), ProcessBy = ? WHERE `ContributionUUID` = ?", [status, rejectionReason, userIdAsBuf, uuidAsBuf], function (scErr, scRes, scFields) {
if (scErr) {
return res.status(500).render("error", {
message: "The contribution could not be rejected."
});
} else {
return res.redirect("/sa/contributions");
}
});
} else {
return res.status(400).render("error", {
message: "The request was malformed."
});
}
});


module.exports = function (c, d) {
config = c
database = d;
Expand Down
34 changes: 34 additions & 0 deletions adventure/userRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,40 @@ server.get("/user/edit", restrictedRoute(), function (req, res) {
return res.render("editProfile");
});

server.get("/user/profile", restrictedRoute(), function (req, res) {
return res.redirect("/user/profile/" + req.user.ShortName);
});

server.get("/user/profile/:name", function (req, res) {
database.userByName(req.params.name, function (err, user) {
if (err) {
return res.status(500).render("error", {
message: "There was an error fetching from the database."
});
} else if (user == null) {
database.userById(formatting.hexToBin(req.params.name), function (err, user) {
if (err) {
return res.status(500).render("error", {
message: "There was an error fetching from the database."
});
} else if (user == null) {
return res.status(404).render("error", {
message: "There was no user."
});
} else {
return res.redirect("/user/profile/" + user.ShortName);
}
});
} else {
user.UserID = formatting.binToHex(user.UserID);
res.render("publicProfile", {
viewingUser: user,
userFlags: database.userFlags,
});
}
});
});

server.post("/user/changepw", restrictedRoute(), urlencodedParser, function (req, res) {
if (req.body && req.body.password && req.body.newPassword && req.body.newPasswordR) {
formatting.checkPassword(req.body.password, req.user.Salt, req.user.Password, function(checkErr, success) {
Expand Down
104 changes: 104 additions & 0 deletions adventure/views/contribute.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<%- include("head", {title: "Contribute"}) %>
<h1>Contribute</h1>
<h2>Basic metadata</h2>
<form method="post" action="/library/contribute/redirectToSecondStep">
<div class="row">
<div class="form-group col">
<label for="name">Product name</label>
<input required type="text" class="form-control" id="productName" name="productName" placeholder="Product name" value="">
<small class="form-text text-muted">
e.g. Windows 95
</small>
</div>
<div class="form-group col">
<label for="name">Release name</label>
<input required type="text" class="form-control" id="name" name="name" placeholder="Release name" value="">
<small class="form-text text-muted">
e.g. OSR2
</small>
</div>
<div class="form-group col">
<label for="vendorName">Vendor name</label>
<input type="text" class="form-control" id="vendorName" name="vendorName" placeholder="Vendor name" value="">
</div>
</div>
<%
// HACK: datetime-local != toISOString
function pad(number) {
if (number < 10) {
return '0' + number;
}
return number;
}
function shortIso(d) {
return d.getUTCFullYear() +
'-' + pad(d.getUTCMonth() + 1) +
'-' + pad(d.getUTCDate()) +
'T' + pad(d.getUTCHours()) +
':' + pad(d.getUTCMinutes());
}
%>
<div class="row">
<div class="form-group col">
<label for="releaseDate">Release date</label>
<input type="datetime-local" class="form-control" id="releaseDate" name="releaseDate" placeholder="Release date" value="">
<small class="form-text text-muted">
If your browser doesn't support HTML 5 datetime fields, use a format like <pre>2000-02-15T06:00</pre>.
</small>
</div>
<div class="form-group col">
<label for="endOfLife">End of life </label>
<input type="datetime-local" class="form-control" id="endOfLife" name="endOfLife" placeholder="End of life" value="">
<small class="form-text text-muted">
If your browser doesn't support HTML 5 datetime fields, use a format like <pre>2000-02-15T06:00</pre>.
</small>
</div>
</div>
<div class="row">
<div class="form-group col">
<label for="notes">Notes</label>
<textarea rows="5" class="form-control" id="notes" name="notes"></textarea>
<small class="form-text text-muted">
This field is formatted in Markdown
</small>
</div>
<div class="form-group col">
<label for="installInstructions">Installation instructions</label>
<textarea rows="5" class="form-control" id="installInstructions" name="installInstructions"></textarea>
<small class="form-text text-muted">
This field is formatted in Markdown
</small>
</div>
</div>
<div class="form-group">
<label for="platform">Platforms</label>
<select multiple class="form-control" id="platform" name="platform">
<% Object.keys(platformMappingsInverted).forEach(function (i, n, a) { %>
<option><%= i %></option>
<% }); %>
</select>
</div>
<div class="row">
<div class="form-group col">
<label for="cpuRequirement">Required CPU</label>
<input type="text" class="form-control" id="cpuRequirement" name="cpuRequirement" placeholder="Required CPU" value="">
</div>
<div class="form-group col">
<label for="ramRequirement">Required RAM</label>
<input required type="number" class="form-control" id="ramRequirement" name="ramRequirement" placeholder="Required RAM" value="">
<small class="form-text text-muted">
This field is converted to the most sensible human-readable byte size. Enter 0 to blank.
</small>
</div>
<div class="form-group col">
<label for="diskSpaceRequired">Required free disk space</label>
<input required type="number" class="form-control" id="diskSpaceRequired" name="diskSpaceRequired" placeholder="Required free disk space" value="">
<small class="form-text text-muted">
This field is converted to the most sensible human-readable byte size. Enter 0 to blank.
</small>
</div>
</div>
<button type="submit" class="btn btn-primary">Next Step</button>
<a class="btn btn-outline-primary" href="/library">Cancel</a>
</form>
<%- include("foot") %>
1 change: 1 addition & 0 deletions adventure/views/editProfile.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<%- include("head", {title: "Edit profile"}) %>
<h1>Edit profile</h1>
<a href="/user/profile/<%= user.ShortName %>" class="btn btn-primary">View profile</a>
<h2>Details</h2>
<form>
<div class="form-group">
Expand Down
2 changes: 2 additions & 0 deletions adventure/views/head.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
<%= user.ShortName %>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userMenuLink">
<a class="dropdown-item" href="/library/my/contributions">My contributions</a>
<a class="dropdown-item" href="/user/profile">View profile</a>
<a class="dropdown-item" href="/user/edit">Edit profile</a>
<a class="dropdown-item" href="/user/logout">Log out</a>
</div>
Expand Down
3 changes: 3 additions & 0 deletions adventure/views/library.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<% for (var i in categoryMappings) { %>
<a class="nav-link <%= category == i ? 'active' : '' %>" href="/library/<%= i %>"><%= categoryMappings[i] %></a>
<% } %>
<% if (config.allowContributions) { %>
<a class="btn btn-outline-info ml-2" id="contributeButton" href="/library/contribute">Contribute</a>
<% } %>
</nav>
<dl id="libraryList">
<% products.forEach(function(i, n, a) {%>
Expand Down
3 changes: 3 additions & 0 deletions adventure/views/libraryOS.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<% for (var i in categoryMappings) { %>
<a class="nav-link <%= 'operating-systems' == i ? 'active' : '' %>" href="/library/<%= i %>"><%= categoryMappings[i] %></a>
<% } %>
<% if (config.allowContributions) { %>
<a class="btn btn-outline-info ml-2" id="contributeButton" href="/library/contribute">Contribute</a>
<% } %>
</nav>
<div class="row">
<div class="col-md-3">
Expand Down
63 changes: 63 additions & 0 deletions adventure/views/mycontributions.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<%- include("head", {title: "My contributions"}) %>
<h1>My contributions</h1>
<table id="saContributionsTable" class="table table-striped table-sm table-responsive">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% (contributions || []).forEach(function(i, n, a) {%>
<tr>
<td>
<%= i.ProductTitle %> <%= i.ReleaseTitle %>
</td>
<td>
<%= i.Status %> (<%= i.Status == "Rejected" && i.RejectionReason ? i.RejectionReason : i.ContributionCreated.toDateString() %>)
</td>
</tr>
<% }); %>
</tbody>
</table>
<nav aria-label="Page navigation">
<ul id="contributionsPagination" class="pagination">
<%# HACK: How the hell does EJS get a string when we passed a number? %>
<% page = Number(page); pageBounds = config.perPageBounds; %>
<%# Anyways, do bounds checking so we can make the pagination manageable. %>
<% if (page - pageBounds > 1) { %>
<li class="page-item">
<a class="page-link" href="/library/my/contributions?page=1">1</a>
</li>
<% } %>
<% if (page - pageBounds > 2) { %>
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">...</a>
</li>
<% } %>
<% for (var i = page - pageBounds > 0 ? page - pageBounds : 1; i <= (page + pageBounds < pages ? page + pageBounds : pages); i++) { %>
<% if (i != page) { %>
<li class="page-item">
<a class="page-link" href="/library/my/contributions?page=<%= i %>"><%= i %></a>
</li>
<% } else { %>
<li class="page-item active">
<a class="page-link" href="/library/my/contributions?page=<%= i %>">
<%= i %><span class="sr-only">(current)</span>
</a>
</li>
<% } %>
<% } %>
<% if (page + pageBounds < pages - 1) { %>
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">...</a>
</li>
<% } %>
<% if (page + pageBounds < pages) { %>
<li class="page-item">
<a class="page-link" href="/library/my/contributions?page=<%= pages %>"><%= pages %></a>
</li>
<% } %>
</ul>
</nav>
<%- include("foot") %>
24 changes: 24 additions & 0 deletions adventure/views/publicProfile.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<%- include("head", {title: viewingUser.ShortName + "\'s Profile"}) %>
<h1><%= viewingUser.ShortName %></h1>
<p>
<% if (viewingUser.UserFlags && viewingUser.UserFlags.length > 0) { %>
<% viewingUser.UserFlags.forEach(function (i, n, a) { %>
<span class="badge badge-<%= i.FlagColour %>"><%= i.LongName %></span>
<% }); %>
<% } %>
</p>
<p><strong>Joined</strong> <%= new Date(viewingUser.RegistrationTime).toLocaleDateString() %></p>
<p><strong>Last Active</strong> <%= new Date(viewingUser.LastSeenTime).toLocaleString() %></p>
<% if (user && config.usersCanChangeTheme) { %>
<p><strong>Using Theme</strong> <%= viewingUser.ThemeName %></p>
<% } %>
<% if (config.useVanilla) { %>
<p><a href="<%= config.vanillaBaseUrl %>/profile/<%= viewingUser.ShortName %>">Show profile on forum</a></p>
<% } %>
<% if (user && user.UserFlags.some(function (x) { return x.FlagName == "sa"; })) { %>
<p><a href="/sa/user/<%= viewingUser.UserID %>">Manage user</a></p>
<% } %>
<% if (user && user.ShortName == viewingUser.ShortName) { %>
<p><a href="/user/edit">Edit profile</a></p>
<% } %>
<%- include("foot") %>
Loading