77 Commits
1.0.0 ... 1.0.2

Author SHA1 Message Date
43334b33e1 Fixed suggestions not hiding in the upload 2014-12-14 20:49:42 +01:00
839e97b1e2 Fixed history for posts uploaded anonymously
Until now posts uploaded anonymously were not so anonymous - the author
was clearly visible in post history (and tag history if the upload has
led to creation of any new tags). Both of these are fixed now.
2014-12-14 09:30:22 +01:00
8a33b9581d Tweaked font size for <small/> tag 2014-12-07 13:43:15 +01:00
7067d8e13d Added support for [small]text[/small] 2014-12-07 13:43:04 +01:00
5769034223 Fixed tag input behavior for initial text 2014-11-30 20:30:06 +01:00
c0a5c800e0 Fixed autocomplete staying even after hiding input 2014-11-30 13:05:31 +01:00
b70231bd7e Changed used tags in autocomplete to be grayed-out 2014-11-30 12:49:48 +01:00
bc757dd883 Fixed reused implied tags marked as duplicates 2014-11-30 12:49:48 +01:00
924592675c Fixed tag suggestions for implied tags 2014-11-30 12:40:22 +01:00
a3aea27a13 Fixed tag edits not triggering tag list updates 2014-11-30 12:40:22 +01:00
3c54671aeb Widened post edit form 2014-11-30 11:58:39 +01:00
d8df51f0c0 Moved suggestions before siblings 2014-11-30 11:55:24 +01:00
b693a5f4b3 Added tag siblings suggestion synchronization 2014-11-30 11:54:38 +01:00
303f91e15c Refactored tag suggestions 2014-11-30 11:45:49 +01:00
c350c47195 Added showing tag suggestions on click 2014-11-30 11:31:40 +01:00
24ce67b4ff Added auto completion to tag list presenter 2014-11-30 00:22:50 +01:00
06d7c19556 Added POST to tag merging API 2014-11-28 23:18:26 +01:00
728d1d65de Fixed mass tag 2014-11-28 23:18:23 +01:00
4b27b8a85d Fixed updating user settings 2014-11-28 23:18:14 +01:00
bfe31d87a1 Fixed history presenter 2014-11-27 18:22:48 +01:00
2fd371b10a Added absolute timestamp hints where necessary 2014-11-27 10:34:45 +01:00
f1647a5f7b Fixed precision loss in post disk space usage 2014-11-22 18:08:34 +01:00
cd688b25a3 Fixed example tag usages not showing up 2014-11-22 17:54:37 +01:00
a7c6e9f043 Fixed putting "null" in post sources 2014-11-22 17:49:35 +01:00
997c2a10ec Renamed SearchServices directory to Search 2014-11-22 14:56:30 +01:00
95cf0ca37b Moved Controllers/ViewProxies to ViewProxies 2014-11-22 14:53:40 +01:00
116522498d Removed obsolete method in InputReader 2014-11-22 14:47:25 +01:00
01a84ee4e2 Changed account settings editing
Account settings editing no longer encapsulates file content in base64.
2014-11-22 14:45:48 +01:00
2458935fdf Changed post editing
Post editing no longer encapsulates file content in base64.
2014-11-22 14:45:45 +01:00
f2b1e3bedb Changed post uploads
Post uploads no longer encapsulates file content in base64.
This means dramatic speed up for sending on local networks.
2014-11-22 14:37:00 +01:00
77c51d9a8a Added support for FormData in JS API facade 2014-11-22 14:31:53 +01:00
d8d65ed24c Changed PUT requests to POST
HTTP spec disallows multipart/form-data requests using PUT method.
2014-11-22 14:30:29 +01:00
58d3129548 Removed dangling console.log 2014-11-22 14:04:40 +01:00
0b032e7f94 Merge branch 'api' 2014-11-22 13:10:24 +01:00
4e6fe634e1 Fixed post list disregarding viewPosts privilege 2014-11-22 13:10:00 +01:00
b14f02810e Fixed everyone could view every post 2014-11-22 13:00:36 +01:00
796c2d1b1f Fixed everyone could feature posts 2014-11-22 12:58:02 +01:00
40197d6c39 Fixed everyone could delete posts 2014-11-22 12:57:52 +01:00
736c0a66ff Fixed comments disregarding viewUsers privilege 2014-11-22 12:56:44 +01:00
48230a64ad Simplified routing 2014-11-22 12:44:45 +01:00
da6b37b14c Merge branch 'master' into api 2014-11-21 22:34:04 +01:00
4b4ccf365a Fixed entity IDs being strings
This coincidentally fixes editing newly added comments.
2014-11-21 22:32:10 +01:00
2b0a4d1f76 Removed AJAX external caching
jQuery's caching caused problems in some scenarios, for example with
scoring/faving posts.
2014-11-21 22:24:06 +01:00
2d1b5308f3 Removed unneeded dependencies 2014-11-21 22:16:56 +01:00
8fb1b87ae5 Fixed post editing 2014-11-21 22:16:56 +01:00
9621810332 Fixed syntax error 2014-11-21 22:16:56 +01:00
bd33b09f7b Fixed private methods 2014-11-21 22:16:31 +01:00
3245c75187 Removed controller layer 2014-11-21 22:16:31 +01:00
e38152b921 Moved user avatar controller to routes 2014-11-21 22:16:31 +01:00
2195b2c9a1 Moved scores controller to routes 2014-11-21 22:16:31 +01:00
969f70318b Moved user controller to routes 2014-11-21 22:16:31 +01:00
06cc776438 Removed .swp files... 2014-11-21 22:05:53 +01:00
76edbfeddb Moved tag controller to routes 2014-11-21 15:49:53 +01:00
9de6e7e739 Moved post notes controller to routes 2014-11-21 12:54:33 +01:00
d8a4e1ec4e Moved post controller to routes 2014-11-21 12:45:47 +01:00
193d1c5f7a Moved post content controller to routes 2014-11-21 11:27:51 +01:00
7ff961fc21 Moved history controller to routes 2014-11-21 10:33:15 +01:00
a11436aa8c Added more tests to route repository test 2014-11-21 10:30:53 +01:00
a3b02adb7f Moved global param controller to routes 2014-11-21 10:30:52 +01:00
40b16f586b Fixed RouteRepositoryTest using real DB 2014-11-21 10:30:51 +01:00
602d7a1f45 Split favorites controller to routes 2014-11-21 10:23:50 +01:00
333c538f1e Split comment controller to routes 2014-11-21 10:23:49 +01:00
7c182f57a0 Added support for arguments in new Routes 2014-11-21 10:23:49 +01:00
2a7ca79b2d Added Route layer proof of concept 2014-11-21 10:23:49 +01:00
9e894bc41c Fixed ControllerRepositoryTest using real DB 2014-11-21 10:21:26 +01:00
cdd2726f30 Improved MySQL integration 2014-11-21 10:20:04 +01:00
029f0f00a4 Improved test database upgrade script 2014-11-21 10:03:08 +01:00
c6fe7a4320 Changed downloading YouTube posts in backend
Trying to download YouTube posts by visiting backend directly results in
a redirect to YouTube instead of showing an ugly error.
2014-11-19 19:47:54 +01:00
8bd4ae27c2 Fixed bad message when serving non-existing files 2014-11-19 19:47:54 +01:00
2a215ef51b Removed download link for YouTube posts 2014-11-19 19:47:50 +01:00
6473ed74d3 Fixed post note removal didn't generate snapshot 2014-11-19 19:40:06 +01:00
44a4184eb8 Changed snapshot merging to work for deletions
Creating and deleting stuff will remove history snapshots if it occurs
within five minute gap. This is to prevent spamming history with
tag names that are introduced by an accident and removed shortly after.
2014-11-19 19:37:10 +01:00
847f248829 Added snapshot compression 2014-11-19 10:22:59 +01:00
a0133ea632 Added snapshot merging 2014-11-18 21:22:46 +01:00
0b15ca1b05 Fixed current search not showing arrow 2014-11-18 16:06:12 +01:00
2996f27671 Fixed scoremin and scoremax 2014-11-17 23:04:23 +01:00
6a751ed0b2 Fixed user list presenter disrespecting privileges
If user had no right to view user accounts, the list presenter ignored
that and linked to pages that shown privilege errors. Now it shows the
links only if user has right to view user accounts.
2014-11-17 22:16:30 +01:00
163 changed files with 3121 additions and 1517 deletions

View File

@ -24,6 +24,10 @@ h3 {
font-size: 20px;
}
small {
font-size: 13px;
}
#middle {
padding: 0 2em;
position: relative;

View File

@ -18,10 +18,12 @@
#home .post .left {
display: inline-block;
float: left;
margin-right: 0.5em;
}
#home .post .right {
display: inline-block;
float: right;
margin-left: 0.5em;
}
#home .post-footer,
@ -35,5 +37,7 @@
#home .version {
opacity: .4;
font-size: 12px;
}
#home .subheader, #home .post-footer {
font-size: 85%;
}

View File

@ -68,7 +68,7 @@
.post-small {
position: relative;
}
.post-small a {
.post-small .link {
display: inline-block;
margin: 0.2em;
border: 1px solid #999;
@ -82,20 +82,20 @@
}
.post-small a:focus,
.post-small a:hover {
.post-small .link:focus,
.post-small .link:hover {
background: #64C2ED;
border-color: #64C2ED;
box-shadow: 0 0 0 2px #64C2ED;
outline: 0;
}
.post-small a:focus img:not(.loading),
.post-small a:hover img:not(.loading) {
.post-small .link:focus img:not(.loading),
.post-small .link:hover img:not(.loading) {
opacity: .8 !important;
}
.post-small a .info {
.post-small .link .info {
display: none;
text-align: center;
position: absolute;
@ -105,22 +105,22 @@
background: #64C2ED;
color: black;
}
.post-small a .info ul {
.post-small .link .info ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.post-small a .info li {
.post-small .link .info li {
display: inline-block;
margin: 0.1em 0.5em;
padding: 0;
}
.post-small a:focus .info,
.post-small a:hover .info {
.post-small .link:focus .info,
.post-small .link:hover .info {
display: block;
}
.post-small:not(.post-type-image) a::before {
.post-small:not(.post-type-image) .link::before {
display: block;
content: '';
z-index: 2;
@ -133,7 +133,7 @@
border-left: 50px solid transparent;
}
.post-small:not(.post-type-image) a::after {
.post-small:not(.post-type-image) .link::after {
display: block;
content: '...';
z-index: 3;
@ -148,14 +148,14 @@
color: white;
font-size: 15px;
}
.post-small.post-type-youtube a::after {
.post-small.post-type-youtube .link::after {
font-size: 13px;
content: 'youtube';
}
.post-small.post-type-video a::after {
.post-small.post-type-video .link::after {
content: 'video';
}
.post-small.post-type-flash a::after {
.post-small.post-type-flash .link::after {
content: 'flash';
}

View File

@ -157,12 +157,17 @@
#post-view #post-edit-target {
padding: 1em;
width: 50%;
min-width: 30em;
position: absolute;
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 1em 0.5em rgba(255, 255, 255, 0.8);
z-index: 2;
display: none;
}
#post-edit-target .form-wrapper {
min-width: 100%;
}
#post-view>* {
z-index: -1;
}
@ -224,6 +229,7 @@
position: absolute;
background: rgba(255, 255, 255, 0.3);
border: 1px solid rgba(0, 0, 0, 0.3);
font-size: 12pt;
}
.post-note .text-wrapper {
position: absolute;

View File

@ -39,12 +39,14 @@
#user-list .user img {
vertical-align: top;
margin-right: 1em;
display: block;
}
#user-list .user>a {
display: block;
#user-list .user .avatar {
float: left;
margin-right: 1em;
}
#user-list .user .avatar a {
display: block;
}
#user-list .user .details {
float: left;

View File

@ -138,7 +138,7 @@
<script type="text/javascript" src="/js/Presenters/PostPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/GlobalCommentListPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/PostCommentListPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/CommentListPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/TagListPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/TagPresenter.js"></script>

View File

@ -73,7 +73,7 @@ App.API = function(_, jQuery, promise, appState) {
var xhr = null;
var apiPromise = promise.make(function(resolve, reject) {
xhr = jQuery.ajax({
var options = {
headers: {
'X-Authorization-Token': appState.get('loginToken') || '',
},
@ -92,7 +92,13 @@ App.API = function(_, jQuery, promise, appState) {
type: method,
url: fullUrl,
data: data,
});
cache: false,
};
if (data instanceof FormData) {
options.processData = false;
options.contentType = false;
}
xhr = jQuery.ajax(options);
});
apiPromise.xhr = xhr;
return apiPromise;

View File

@ -70,7 +70,7 @@ App.BrowsingSettings = function(
var formData = {
browsingSettings: JSON.stringify(settings),
};
return api.put('/users/' + user.name, formData);
return api.post('/users/' + user.name, formData);
}
function save() {

View File

@ -17,12 +17,14 @@ App.Controls.AutoCompleteInput = function($input) {
maxResults: 15,
minLengthToArbitrarySearch: 3,
onApply: null,
onRender: null,
additionalFilter: null,
};
var showTimeout = null;
var cachedSource = null;
var results = [];
var activeResult = -1;
var monitorInputHidingInterval = null;
if ($input.length === 0) {
throw new Error('Input element was not found');
@ -133,6 +135,7 @@ App.Controls.AutoCompleteInput = function($input) {
function hide() {
$div.hide();
window.clearInterval(monitorInputHidingInterval);
}
function selectPrevious() {
@ -222,12 +225,16 @@ App.Controls.AutoCompleteInput = function($input) {
});
$list.append($listItem);
});
if (options.onRender) {
options.onRender($list);
}
refreshActiveResult();
$div.css({
left: ($input.offset().left) + 'px',
top: ($input.offset().top + $input.outerHeight() - 2) + 'px',
});
$div.show();
monitorInputHiding();
}
function refreshActiveResult() {
@ -237,5 +244,13 @@ App.Controls.AutoCompleteInput = function($input) {
}
}
function monitorInputHiding() {
monitorInputHidingInterval = window.setInterval(function() {
if (!$input.is(':visible')) {
hide();
}
}, 100);
}
return options;
};

View File

@ -14,6 +14,14 @@ App.Controls.TagInput = function($underlyingInput) {
var tagConfirmKeys = [KEY_RETURN, KEY_SPACE];
var inputConfirmKeys = [KEY_RETURN];
var SOURCE_INITIAL_TEXT = 1;
var SOURCE_AUTOCOMPLETION = 2;
var SOURCE_PASTE = 3;
var SOURCE_IMPLICATIONS = 4;
var SOURCE_INPUT_BLUR = 5;
var SOURCE_INPUT_ENTER = 6;
var SOURCE_SUGGESTIONS = 7;
var tags = [];
var options = {
beforeTagAdded: null,
@ -56,10 +64,10 @@ App.Controls.TagInput = function($underlyingInput) {
$input.focus();
});
$input.attr('placeholder', $underlyingInput.attr('placeholder'));
$suggestions.insertAfter($wrapper);
$siblings.insertAfter($wrapper);
$suggestions.insertAfter($wrapper);
processText($underlyingInput.val(), addTagDirectly);
processText($underlyingInput.val(), SOURCE_INITIAL_TEXT);
$underlyingInput.val('');
}
@ -67,7 +75,7 @@ App.Controls.TagInput = function($underlyingInput) {
function initAutoComplete() {
var autoComplete = new App.Controls.AutoCompleteInput($input);
autoComplete.onApply = function(text) {
processText(text, addTag);
processText(text, SOURCE_AUTOCOMPLETION);
$input.val('');
};
autoComplete.additionalFilter = function(results) {
@ -75,6 +83,14 @@ App.Controls.TagInput = function($underlyingInput) {
return !_.contains(getTags(), resultItem[0]);
});
};
autoComplete.onRender = function($list) {
$list.find('li').each(function() {
var $li = jQuery(this);
if (isTaggedWith($li.attr('data-key'))) {
$li.css('opacity', '0.5');
}
});
};
}
$input.bind('focus', function(e) {
@ -83,7 +99,7 @@ App.Controls.TagInput = function($underlyingInput) {
$input.bind('blur', function(e) {
$wrapper.removeClass('focused');
var tagName = $input.val();
addTag(tagName);
addTag(tagName, SOURCE_INPUT_BLUR);
$input.val('');
});
@ -101,7 +117,7 @@ App.Controls.TagInput = function($underlyingInput) {
return;
}
processTextWithoutLast(pastedText, addTag);
processTextWithoutLast(pastedText, SOURCE_PASTE);
});
$input.bind('keydown', function(e) {
@ -114,7 +130,7 @@ App.Controls.TagInput = function($underlyingInput) {
var tagName = $input.val();
e.preventDefault();
$input.val('');
addTag(tagName);
addTag(tagName, SOURCE_INPUT_ENTER);
} else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) {
e.preventDefault();
removeLastTag();
@ -127,19 +143,19 @@ App.Controls.TagInput = function($underlyingInput) {
});
}
function processText(text, callback) {
function processText(text, source) {
var tagNamesToAdd = explodeText(text);
_.map(tagNamesToAdd, function(tagName) { callback(tagName); });
_.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
}
function processTextWithoutLast(text, callback) {
function processTextWithoutLast(text, source) {
var tagNamesToAdd = explodeText(text);
var lastTagName = tagNamesToAdd.pop();
_.map(tagNamesToAdd, function(tagName) { callback(tagName); });
_.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
$input.val(lastTagName);
}
function addTag(tagName) {
function addTag(tagName, source) {
tagName = tagName.trim();
if (tagName.length === 0) {
return;
@ -157,39 +173,53 @@ App.Controls.TagInput = function($underlyingInput) {
if (isTaggedWith(tagName)) {
flashTagRed(tagName);
} else {
beforeTagAdded(tagName);
beforeTagAdded(tagName, source);
var exportedTag = getExportedTag(tagName);
if (!exportedTag || !exportedTag.banned) {
addTagDirectly(tagName);
tags.push(tagName);
var $elem = createListElement(tagName);
$tagList.append($elem);
}
afterTagAdded(tagName);
afterTagAdded(tagName, source);
}
}
function addTagDirectly(tagName) {
tags.push(tagName);
var $elem = createListElement(tagName);
$tagList.append($elem);
function beforeTagRemoved(tagName) {
if (typeof(options.beforeTagRemoved) === 'function') {
options.beforeTagRemoved(tagName);
}
}
function beforeTagAdded(tagName) {
function afterTagRemoved(tagName) {
refreshShownSiblings();
}
function beforeTagAdded(tagName, source) {
if (typeof(options.beforeTagAdded) === 'function') {
options.beforeTagAdded(tagName);
}
}
function afterTagAdded(tagName) {
var tag = getExportedTag(tagName);
if (tag) {
_.each(tag.implications, function(impliedTagName) {
addTag(impliedTagName);
flashTagYellow(impliedTagName);
});
showOrHideSuggestions(tag.suggestions);
} else {
flashTagGreen(tagName);
function afterTagAdded(tagName, source) {
if (source === SOURCE_IMPLICATIONS) {
flashTagYellow(tagName);
} else if (source !== SOURCE_INITIAL_TEXT) {
var tag = getExportedTag(tagName);
if (tag) {
_.each(tag.implications, function(impliedTagName) {
if (!isTaggedWith(impliedTagName)) {
addTag(impliedTagName, SOURCE_IMPLICATIONS);
}
});
if (source !== SOURCE_IMPLICATIONS && source !== SOURCE_SUGGESTIONS) {
showOrHideSuggestions(tagName);
refreshShownSiblings();
}
} else {
flashTagGreen(tagName);
}
}
}
@ -205,10 +235,9 @@ App.Controls.TagInput = function($underlyingInput) {
var oldTagNames = getTags();
var newTagNames = _.without(oldTagNames, tagName);
if (newTagNames.length !== oldTagNames.length) {
if (typeof(options.beforeTagRemoved) === 'function') {
options.beforeTagRemoved(tagName);
}
beforeTagRemoved(tagName);
setTags(newTagNames);
afterTagRemoved(tagName);
}
}
@ -262,7 +291,8 @@ App.Controls.TagInput = function($underlyingInput) {
$tagLink.text(tagName);
$tagLink.click(function(e) {
e.preventDefault();
showOrHideTagSiblings(tagName);
showOrHideSiblings(tagName);
showOrHideSuggestions(tagName);
});
$elem.append($tagLink);
@ -276,19 +306,13 @@ App.Controls.TagInput = function($underlyingInput) {
return $elem;
}
function showOrHideSuggestions(suggestedTagNames) {
if (_.size(suggestedTagNames) === 0) {
return;
}
var suggestions = filterSuggestions(suggestedTagNames);
if (suggestions.length > 0) {
attachTagsToSuggestionList($suggestions.find('ul'), suggestions);
$suggestions.slideDown('fast');
}
function showOrHideSuggestions(tagName) {
var tag = getExportedTag(tagName);
var suggestions = tag ? tag.suggestions : [];
updateSuggestions($suggestions, suggestions);
}
function showOrHideTagSiblings(tagName) {
function showOrHideSiblings(tagName) {
if ($siblings.data('lastTag') === tagName && $siblings.is(':visible')) {
$siblings.slideUp('fast');
$siblings.data('lastTag', null);
@ -298,48 +322,58 @@ App.Controls.TagInput = function($underlyingInput) {
promise.wait(getSiblings(tagName), promise.make(function(resolve, reject) {
$siblings.slideUp('fast', resolve);
})).then(function(siblings) {
siblings = _.pluck(siblings, 'name');
$siblings.data('lastTag', tagName);
if (!_.size(siblings)) {
return;
}
var suggestions = filterSuggestions(_.pluck(siblings, 'name'));
if (suggestions.length > 0) {
attachTagsToSuggestionList($siblings.find('ul'), suggestions);
$siblings.slideDown('fast');
}
$siblings.data('siblings', siblings);
updateSuggestions($siblings, siblings);
}).fail(function() {
});
}
function filterSuggestions(sourceTagNames) {
var tagNames = _.filter(sourceTagNames.slice(), function(tagName) {
return !isTaggedWith(tagName);
});
tagNames = tagNames.slice(0, 20);
return tagNames;
function refreshShownSiblings() {
updateSuggestions($siblings, $siblings.data('siblings'));
}
function attachTagsToSuggestionList($list, tagNames) {
$list.empty();
_.each(tagNames, function(tagName) {
var $li = jQuery('<li>');
var $a = jQuery('<a href="#/posts/query=' + tagName + '">');
$a.text(tagName);
$a.click(function(e) {
e.preventDefault();
addTag(tagName);
$li.fadeOut('fast', function() {
$li.remove();
if ($list.children().length === 0) {
$list.parent('div').slideUp('fast');
}
});
function updateSuggestions($target, suggestedTagNames) {
function filterSuggestions(sourceTagNames) {
if (!sourceTagNames) {
return [];
}
var tagNames = _.filter(sourceTagNames.slice(), function(tagName) {
return !isTaggedWith(tagName);
});
$li.append($a);
$list.append($li);
});
tagNames = tagNames.slice(0, 20);
return tagNames;
}
function attachTagsToSuggestionList($list, tagNames) {
$list.empty();
_.each(tagNames, function(tagName) {
var $li = jQuery('<li>');
var $a = jQuery('<a href="#/posts/query=' + tagName + '">');
$a.text(tagName);
$a.click(function(e) {
e.preventDefault();
addTag(tagName, SOURCE_SUGGESTIONS);
$li.fadeOut('fast', function() {
$li.remove();
if ($list.children().length === 0) {
$list.parent('div').slideUp('fast');
}
});
});
$li.append($a);
$list.append($li);
});
}
var suggestions = filterSuggestions(suggestedTagNames);
if (suggestions.length > 0) {
attachTagsToSuggestionList($target.find('ul'), suggestions);
$target.slideDown('fast');
} else {
$target.slideUp('fast');
}
}
function getSiblings(tagName) {
@ -364,6 +398,7 @@ App.Controls.TagInput = function($underlyingInput) {
function hideSuggestions() {
$siblings.hide();
$suggestions.hide();
$siblings.data('siblings', []);
}
_.extend(options, {

View File

@ -1,7 +1,7 @@
var App = App || {};
App.Presenters = App.Presenters || {};
App.Presenters.PostCommentListPresenter = function(
App.Presenters.CommentListPresenter = function(
_,
jQuery,
util,
@ -26,14 +26,16 @@ App.Presenters.PostCommentListPresenter = function(
privileges = {
canListComments: auth.hasPrivilege(auth.privileges.listComments),
canAddComments: auth.hasPrivilege(auth.privileges.addComments),
editOwnComments: auth.hasPrivilege(auth.privileges.editOwnComments),
editAllComments: auth.hasPrivilege(auth.privileges.editAllComments),
deleteOwnComments: auth.hasPrivilege(auth.privileges.deleteOwnComments),
deleteAllComments: auth.hasPrivilege(auth.privileges.deleteAllComments),
canEditOwnComments: auth.hasPrivilege(auth.privileges.editOwnComments),
canEditAllComments: auth.hasPrivilege(auth.privileges.editAllComments),
canDeleteOwnComments: auth.hasPrivilege(auth.privileges.deleteOwnComments),
canDeleteAllComments: auth.hasPrivilege(auth.privileges.deleteAllComments),
canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers),
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
};
promise.wait(
util.promiseTemplate('post-comment-list'),
util.promiseTemplate('comment-list'),
util.promiseTemplate('comment-list-item'),
util.promiseTemplate('comment-form'))
.then(function(
@ -101,10 +103,13 @@ App.Presenters.PostCommentListPresenter = function(
var $item = jQuery('<li>' + templates.commentListItem({
comment: comment,
formatRelativeTime: util.formatRelativeTime,
formatAbsoluteTime: util.formatAbsoluteTime,
formatMarkdown: util.formatMarkdown,
canVote: auth.isLoggedIn(),
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.editOwnComments : privileges.editAllComments,
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.deleteOwnComments : privileges.deleteAllComments,
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.canEditOwnComments : privileges.canEditAllComments,
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.canDeleteOwnComments : privileges.canDeleteAllComments,
canViewUsers: privileges.canViewUsers,
canViewPosts: privileges.canViewPosts,
}) + '</li>');
util.loadImagesNicely($item.find('img'));
$targetList.append($item);
@ -227,4 +232,4 @@ App.Presenters.PostCommentListPresenter = function(
};
App.DI.register('postCommentListPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'auth', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostCommentListPresenter);
App.DI.register('commentListPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'auth', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.CommentListPresenter);

View File

@ -5,17 +5,23 @@ App.Presenters.GlobalCommentListPresenter = function(
_,
jQuery,
util,
auth,
promise,
pagerPresenter,
topNavigationPresenter) {
var $el;
var privileges;
var templates = {};
function init(params, loaded) {
$el = jQuery('#content');
topNavigationPresenter.select('comments');
privileges = {
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
};
promise.wait(
util.promiseTemplate('global-comment-list'),
util.promiseTemplate('global-comment-list-item'),
@ -69,10 +75,11 @@ App.Presenters.GlobalCommentListPresenter = function(
util: util,
post: post,
postTemplate: templates.post,
canViewPosts: privileges.canViewPosts,
}) + '</li>');
util.loadImagesNicely($post.find('img'));
var presenter = App.DI.get('postCommentListPresenter');
var presenter = App.DI.get('commentListPresenter');
presenter.init({
post: post,
@ -95,4 +102,4 @@ App.Presenters.GlobalCommentListPresenter = function(
};
App.DI.register('globalCommentListPresenter', ['_', 'jQuery', 'util', 'promise', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.GlobalCommentListPresenter);
App.DI.register('globalCommentListPresenter', ['_', 'jQuery', 'util', 'auth', 'promise', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.GlobalCommentListPresenter);

View File

@ -63,6 +63,7 @@ App.Presenters.HistoryPresenter = function(
function renderHistory($page, historyItems) {
$page.append(templates.history({
formatRelativeTime: util.formatRelativeTime,
formatAbsoluteTime: util.formatAbsoluteTime,
history: historyItems}));
}

View File

@ -75,15 +75,11 @@ App.Presenters.PostEditPresenter = function(
}
function postContentChanged(files) {
postContentFileDropper.readAsDataURL(files[0], function(content) {
postContent = content;
});
postContent = files[0];
}
function postThumbnailChanged(files) {
postThumbnailFileDropper.readAsDataURL(files[0], function(content) {
postThumbnail = content;
});
postThumbnail = files[0];
}
function getPrivileges() {
@ -92,37 +88,36 @@ App.Presenters.PostEditPresenter = function(
function editPost() {
var $form = $target.find('form');
var formData = {};
formData.seenEditTime = post.lastEditTime;
formData.flags = {};
var formData = new FormData();
formData.append('seenEditTime', post.lastEditTime);
if (privileges.canChangeContent && postContent) {
formData.content = postContent;
formData.append('content', postContent);
}
if (privileges.canChangeThumbnail && postThumbnail) {
formData.thumbnail = postThumbnail;
formData.append('thumbnail', postThumbnail);
}
if (privileges.canChangeSource) {
formData.source = $form.find('[name=source]').val();
formData.append('source', $form.find('[name=source]').val());
}
if (privileges.canChangeSafety) {
formData.safety = $form.find('[name=safety]:checked').val();
formData.append('safety', $form.find('[name=safety]:checked').val());
}
if (privileges.canChangeTags) {
formData.tags = tagInput.getTags().join(' ');
formData.append('tags', tagInput.getTags().join(' '));
}
if (privileges.canChangeRelations) {
formData.relations = $form.find('[name=relations]').val();
formData.append('relations', $form.find('[name=relations]').val());
}
if (privileges.canChangeFlags) {
if (post.contentType === 'video') {
formData.flags.loop = $form.find('[name=loop]').is(':checked') ? 1 : 0;
formData.append('loop', $form.find('[name=loop]').is(':checked') ? 1 : 0);
}
}
@ -131,7 +126,7 @@ App.Presenters.PostEditPresenter = function(
return;
}
promise.wait(api.put('/posts/' + post.id, formData))
promise.wait(api.post('/posts/' + post.id, formData))
.then(function(response) {
tagList.refreshTags();
if (typeof(updateCallback) !== 'undefined') {

View File

@ -29,6 +29,7 @@ App.Presenters.PostListPresenter = function(
params.query = params.query || {};
privileges.canMassTag = auth.hasPrivilege(auth.privileges.massTag);
privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts);
promise.wait(
util.promiseTemplate('post-list'),
@ -165,6 +166,7 @@ App.Presenters.PostListPresenter = function(
util: util,
query: params.query,
post: post,
canViewPosts: privileges.canViewPosts,
}) + '</li>');
$post.data('post', post);
util.loadImagesNicely($post.find('img'));
@ -217,7 +219,7 @@ App.Presenters.PostListPresenter = function(
var formData = {};
formData.seenEditTime = post.lastEditTime;
formData.tags = tags.join(' ');
promise.wait(api.put('/posts/' + post.id, formData))
promise.wait(api.post('/posts/' + post.id, formData))
.then(function(response) {
post = response.json;
$post.data('post', post);

View File

@ -14,7 +14,7 @@ App.Presenters.PostPresenter = function(
postsAroundCalculator,
postEditPresenter,
postContentPresenter,
postCommentListPresenter,
commentListPresenter,
topNavigationPresenter,
messagePresenter) {
@ -70,7 +70,7 @@ App.Presenters.PostPresenter = function(
presenterManager.initPresenters([
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}],
[postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}],
[postCommentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]],
[commentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]],
function() { });
}).fail(function() {
@ -164,6 +164,7 @@ App.Presenters.PostPresenter = function(
postHistory: post.history,
formatRelativeTime: util.formatRelativeTime,
formatAbsoluteTime: util.formatAbsoluteTime,
formatFileSize: util.formatFileSize,
historyTemplate: templates.history,
@ -333,7 +334,7 @@ App.DI.register('postPresenter', [
'postsAroundCalculator',
'postEditPresenter',
'postContentPresenter',
'postCommentListPresenter',
'commentListPresenter',
'topNavigationPresenter',
'messagePresenter'],
App.Presenters.PostPresenter);

View File

@ -256,10 +256,9 @@ App.Presenters.PostUploadPresenter = function(
}
function addPostFromFile(file) {
var post = _.extend({}, getDefaultPost(), {fileName: file.name});
var post = _.extend({}, getDefaultPost(), {fileName: file.name, file: file});
fileDropper.readAsDataURL(file, function(content) {
post.content = content;
if (file.type.match('image.*')) {
post.thumbnail = content;
postThumbnailLoaded(post);
@ -442,7 +441,6 @@ App.Presenters.PostUploadPresenter = function(
function setPostsSource(posts, newSource) {
_.each(posts, function(post) {
var maxSourceLength = 200;
console.log(newSource);
if (newSource.length > maxSourceLength) {
newSource = newSource.substring(0, maxSourceLength - 5) + '(...)';
}
@ -576,17 +574,17 @@ App.Presenters.PostUploadPresenter = function(
var post = posts[0];
var $row = post.$tableRow;
var formData = {};
var formData = new FormData();
if (post.url) {
formData.url = post.url;
formData.append('url', post.url);
} else {
formData.content = post.content;
formData.contentFileName = post.fileName;
formData.append('content', post.file);
formData.append('contentFileName', post.fileName);
}
formData.source = post.source;
formData.safety = post.safety;
formData.anonymous = (post.anonymous | 0);
formData.tags = post.tags.join(' ');
formData.append('source', post.source || '');
formData.append('safety', post.safety);
formData.append('anonymous', (post.anonymous | 0));
formData.append('tags', post.tags.join(' '));
if (post.tags.length === 0) {
showUploadError('No tags set.');

View File

@ -10,8 +10,6 @@ App.Presenters.TagListPresenter = function(
pagerPresenter,
topNavigationPresenter) {
var KEY_RETURN = 13;
var $el = jQuery('#content');
var $searchInput;
var templates = {};
@ -78,8 +76,8 @@ App.Presenters.TagListPresenter = function(
function render() {
$el.html(templates.list());
$searchInput = $el.find('input[name=query]');
$searchInput.keydown(searchInputKeyPressed);
$el.find('form').submit(searchFormSubmitted);
App.Controls.AutoCompleteInput($searchInput);
softRender();
}
@ -88,13 +86,6 @@ App.Presenters.TagListPresenter = function(
}
function searchInputKeyPressed(e) {
if (e.which !== KEY_RETURN) {
return;
}
updateSearch();
}
function searchFormSubmitted(e) {
e.preventDefault();
updateSearch();

View File

@ -38,6 +38,7 @@ App.Presenters.TagPresenter = function(
privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory);
privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags);
privileges.canMerge = auth.hasPrivilege(auth.privileges.mergeTags);
privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts);
promise.wait(
util.promiseTemplate('tag'),
@ -87,6 +88,7 @@ App.Presenters.TagPresenter = function(
siblings: siblings,
tagCategories: JSON.parse(jQuery('head').attr('data-tag-categories')),
formatRelativeTime: util.formatRelativeTime,
formatAbsoluteTime: util.formatAbsoluteTime,
historyTemplate: templates.history,
}));
$el.find('.post-list').hide();
@ -126,6 +128,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.put('/tags/' + tag.name, formData))
.then(function(response) {
router.navigateInplace('#/tag/' + response.json.name);
tagList.refreshTags();
}).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.');
});
@ -138,6 +141,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.delete('/tags/' + tag.name))
.then(function(response) {
router.navigate('#/tags');
tagList.refreshTags();
}).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.');
});
@ -149,6 +153,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag}))
.then(function(response) {
router.navigate('#/tags');
tagList.refreshTags();
}).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.');
});
@ -162,6 +167,7 @@ App.Presenters.TagPresenter = function(
util: util,
post: post,
query: {query: tag.name},
canViewPosts: privileges.canViewPosts,
}) + '</li>');
$target.append($post);
});
@ -180,4 +186,16 @@ App.Presenters.TagPresenter = function(
};
App.DI.register('tagPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'api', 'tagList', 'router', 'keyboard', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.TagPresenter);
App.DI.register('tagPresenter', [
'_',
'jQuery',
'util',
'promise',
'auth',
'api',
'tagList',
'router',
'keyboard',
'topNavigationPresenter',
'messagePresenter'],
App.Presenters.TagPresenter);

View File

@ -77,11 +77,7 @@ App.Presenters.UserAccountSettingsPresenter = function(
}
function avatarContentChanged(files) {
if (files.length === 1) {
fileDropper.readAsDataURL(files[0], function(content) {
avatarContent = content;
});
}
avatarContent = files[0];
}
function accountSettingsFormSubmitted(e) {
@ -89,41 +85,45 @@ App.Presenters.UserAccountSettingsPresenter = function(
var $el = jQuery(target);
var $messages = jQuery(target).find('.messages');
messagePresenter.hideMessages($messages);
var formData = {};
var formData = new FormData();
if (privileges.canChangeAvatarStyle) {
formData.avatarStyle = $el.find('[name=avatar-style]:checked').val();
formData.append('avatarStyle', $el.find('[name=avatar-style]:checked').val());
if (avatarContent) {
formData.avatarContent = avatarContent;
formData.append('avatarContent', avatarContent);
}
}
if (privileges.canChangeName) {
formData.userName = $el.find('[name=userName]').val();
formData.append('userName', $el.find('[name=userName]').val());
}
if (privileges.canChangeEmailAddress) {
formData.email = $el.find('[name=email]').val();
formData.append('email', $el.find('[name=email]').val());
}
if (privileges.canChangePassword) {
formData.password = $el.find('[name=password]').val();
formData.passwordConfirmation = $el.find('[name=passwordConfirmation]').val();
var password = $el.find('[name=password]').val();
var passwordConfirmation = $el.find('[name=passwordConfirmation]').val();
if (password) {
if (password !== passwordConfirmation) {
messagePresenter.showError($messages, 'Passwords must be the same.');
return;
}
formData.append('password', password);
}
}
if (privileges.canChangeAccessRank) {
formData.accessRank = $el.find('[name=access-rank]:checked').val();
formData.append('accessRank', $el.find('[name=access-rank]:checked').val());
}
if (privileges.canBan) {
formData.banned = $el.find('[name=ban]').is(':checked') ? 1 : 0;
formData.append('banned', $el.find('[name=ban]').is(':checked') ? 1 : 0);
}
if (!validateAccountSettingsFormData(formData)) {
return;
}
if (!formData.password) {
delete formData.password;
delete formData.passwordConfirmation;
}
promise.wait(api.put('/users/' + user.name, formData))
promise.wait(api.post('/users/' + user.name, formData))
.then(function(response) {
editSuccess(response);
}).fail(function(response) {
@ -153,16 +153,6 @@ App.Presenters.UserAccountSettingsPresenter = function(
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
}
function validateAccountSettingsFormData(formData) {
var $messages = jQuery(target).find('.messages');
if (formData.password !== formData.passwordConfirmation) {
messagePresenter.showError($messages, 'Passwords must be the same.');
return false;
}
return true;
}
return {
init: init,
render: render,

View File

@ -13,11 +13,14 @@ App.Presenters.UserListPresenter = function(
var $el = jQuery('#content');
var templates = {};
var params;
var privileges = {};
function init(params, loaded) {
topNavigationPresenter.select('users');
topNavigationPresenter.changeTitle('Users');
privileges.canViewUsers = auth.hasPrivilege(auth.privileges.viewUsers);
promise.wait(
util.promiseTemplate('user-list'),
util.promiseTemplate('user-list-item'))
@ -60,7 +63,7 @@ App.Presenters.UserListPresenter = function(
}
function render() {
$el.html(templates.list());
$el.html(templates.list(privileges));
}
function updateActiveOrder(activeOrder) {
@ -71,10 +74,11 @@ App.Presenters.UserListPresenter = function(
function renderUsers($page, users) {
var $target = $page.find('.users');
_.each(users, function(user) {
var $item = jQuery('<li>' + templates.listItem({
var $item = jQuery('<li>' + templates.listItem(_.extend({
user: user,
formatRelativeTime: util.formatRelativeTime,
}) + '</li>');
formatAbsoluteTime: util.formatAbsoluteTime,
}, privileges)) + '</li>');
$target.append($item);
});
_.map(_.map($target.find('img'), jQuery), util.loadImagesNicely);

View File

@ -75,6 +75,7 @@ App.Presenters.UserPresenter = function(
user: user,
isLoggedIn: auth.isLoggedIn(user.name),
formatRelativeTime: util.formatRelativeTime,
formatAbsoluteTime: util.formatAbsoluteTime,
canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings,
canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()),
canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount}));

View File

@ -146,6 +146,11 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
return future ? 'in ' + text : text + ' ago';
}
function formatAbsoluteTime(timeString) {
var time = new Date(Date.parse(timeString));
return time.toString();
}
function formatUnits(number, base, suffixes, callback) {
if (!number && number !== 0) {
return NaN;
@ -205,6 +210,8 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>');
//spoilers
text = text.replace(/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '<span class="spoiler">$1</span>');
//[small]
text = text.replace(/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, '<small>$1</small>');
//strike-through
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
text = text.replace(/\\~/g, '~');
@ -233,6 +240,7 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
return {
promiseTemplate: promiseTemplate,
formatRelativeTime: formatRelativeTime,
formatAbsoluteTime: formatAbsoluteTime,
formatFileSize: formatFileSize,
formatMarkdown: formatMarkdown,
enableExitConfirmation: enableExitConfirmation,

View File

@ -1,6 +1,6 @@
<div class="comment">
<div class="avatar">
<% if (comment.user.name) { %>
<% if (comment.user.name && canViewUsers) { %>
<a href="#/user/<%= comment.user.name %>">
<% } %>
@ -8,7 +8,7 @@
src="/data/thumbnails/40x40/avatars/<%= comment.user.name || '!' %>"
alt="<%= comment.user.name || 'Anonymous user' %>"/>
<% if (comment.user.name) { %>
<% if (comment.user.name && canViewUsers) { %>
</a>
<% } %>
</div>
@ -16,18 +16,18 @@
<div class="body">
<div class="header">
<span class="nickname">
<% if (comment.user.name) { %>
<% if (comment.user.name && canViewUsers) { %>
<a href="#/user/<%= comment.user.name %>">
<% } %>
<%= comment.user.name || 'Anonymous user' %>
<% if (comment.user.name) { %>
<% if (comment.user.name && canViewUsers) { %>
</a>
<% } %>
</span>
<span class="date" title="<%= comment.creationTime %>">
<span class="date" title="<%= formatAbsoluteTime(comment.creationTime) %>">
<%= formatRelativeTime(comment.creationTime) %>
</span>

View File

@ -1,6 +1,6 @@
<div class="post-comment">
<div class="post">
<%= postTemplate({post: post, util: util}) %>
<%= postTemplate({post: post, util: util, canViewPosts: canViewPosts}) %>
</div>
<div class="post-comments-target">

View File

@ -5,13 +5,24 @@ var reprValue = function(value) {
}
return JSON.stringify(value);
};
var showDifference = function(className, difference) {
_.each(difference, function(value, key) {
if (!Array.isArray(value)) {
value = [value];
}
_.each(value, function(v) {
%><li class="<%= className %> difference-<%= key %>"><%= key + ':' + reprValue(v) %></li><%
});
});
};
%>
<table class="history">
<tbody>
<% _.each(history, function( historyEntry) { %>
<tr>
<td class="time">
<td class="time" title="<%= formatAbsoluteTime(historyEntry.time) %>">
<%= formatRelativeTime(historyEntry.time) %>
</td>
@ -59,17 +70,8 @@ var reprValue = function(value) {
<% if (historyEntry.dataDifference) { %>
<ul><!--
--><% _.each(historyEntry.dataDifference['+'], function (difference) { %><!--
--><li class="addition difference-<%= difference[0] %>"><!--
--><%= difference[0] + ':' + reprValue(difference[1]) %><!--
--></li><!--
--><% }) %><!--
--><% _.each(historyEntry.dataDifference['-'], function (difference) { %><!--
--><li class="removal difference-<%= difference[0] %>"><!--
--><%= difference[0] + ':' + reprValue(difference[1]) %><!--
--></li><!--
--><% }) %><!--
--><% showDifference('addition', historyEntry.dataDifference['+']) %><!--
--><% showDifference('removal', historyEntry.dataDifference['-']) %><!--
--></ul>
<% } %>
<% } %>

View File

@ -1,7 +1,7 @@
<div id="home">
<h1><%= title %></h1>
<p>
<small>Serving <%= globals.postCount || 0 %> posts (<%= formatFileSize(globals.postSize || 0) %>)</small>
<p class="subheader">
Serving <%= globals.postCount || 0 %> posts (<%= formatFileSize(globals.postSize || 0) %>)
</p>
<% if (post && post.id) { %>
@ -11,7 +11,7 @@
<div class="post-footer">
<small class="left">
<span class="left">
<% var showLink = canViewPosts %>
<% if (showLink) { %>
@ -26,9 +26,9 @@
uploaded
<%= formatRelativeTime(post.uploadTime) %>
</small>
</span>
<small class="right">
<span class="right">
featured
<%= formatRelativeTime(post.lastFeatureTime) %>
by
@ -48,7 +48,7 @@
<% if (showLink) { %>
</a>
<% } %>
</small>
</span>
</div>
</div>

View File

@ -1,7 +1,12 @@
<div class="post-small post-type-<%= post.contentType %> ">
<a class="link"
href="<%= util.appendComplexRouteParam('#/post/' + post.id, typeof(query) !== 'undefined' ? query : {}) %>"
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>">
<% if (canViewPosts) { %>
<a class="link"
href="<%= util.appendComplexRouteParam('#/post/' + post.id, typeof(query) !== 'undefined' ? query : {}) %>"
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>">
<% } else { %>
<span class="link">
<% } %>
<img width="160" height="160" class="thumb" src="/data/thumbnails/160x160/posts/<%= post.name %>" alt="<%= post.idMarkdown %>"/>
@ -31,7 +36,12 @@
</ul>
</div>
<% } %>
</a>
<% if (canViewPosts) { %>
</a>
<% } else { %>
</span>
<% } %>
<div class="action">
<button>Action</button>

View File

@ -10,7 +10,7 @@
</div>
<div class="search">
<a href="#/posts/query=<%= query.query %>;order=<%= query.order %>">
<a class="enabled" href="#/posts/query=<%= query.query %>;order=<%= query.order %>">
Current search: <%= query.query || '-' %>
</a>
</div>
@ -27,13 +27,15 @@
<div id="post-view-wrapper">
<div id="sidebar">
<ul class="essential">
<li>
<a class="download" href="<%= permaLink %>">
<i class="fa fa-download"></i>
<br/>
<%= post.contentExtension + ', ' + formatFileSize(post.originalFileSize) %>
</a>
</li>
<% if (post.contentType !== 'youtube') { %>
<li>
<a class="download" href="<%= permaLink %>">
<i class="fa fa-download"></i>
<br/>
<%= post.contentExtension + ', ' + formatFileSize(post.originalFileSize) %>
</a>
</li>
<% } %>
<% if (isLoggedIn) { %>
<li>
@ -107,7 +109,9 @@
<br/>
<span class="date"><%= formatRelativeTime(post.uploadTime) %></span>
<span class="date" title="<%= formatAbsoluteTime(post.uploadTime) %>">
<%= formatRelativeTime(post.uploadTime) %>
</span>
</div>
<ul class="other-info">
@ -136,7 +140,9 @@
<% if (post.lastEditTime !== post.uploadTime) { %>
<li>
Edited:
<%= formatRelativeTime(post.lastEditTime) %>
<span title="<%= formatAbsoluteTime(post.lastEditTime) %>">
<%= formatRelativeTime(post.lastEditTime) %>
</span>
</li>
<% } %>
@ -271,7 +277,8 @@
<h1>History</h1>
<%= historyTemplate({
history: postHistory,
formatRelativeTime: formatRelativeTime
formatRelativeTime: formatRelativeTime,
formatAbsoluteTime: formatAbsoluteTime,
}) %>
</div>
<% } %>

View File

@ -103,7 +103,8 @@
<h3>History</h3>
<%= historyTemplate({
history: tag.history,
formatRelativeTime: formatRelativeTime
formatRelativeTime: formatRelativeTime,
formatAbsoluteTime: formatAbsoluteTime,
}) %>
</div>
<% } %>

View File

@ -1,17 +1,28 @@
<div class="user">
<a href="#/user/<%= user.name %>">
<img width="80" height="80" src="/data/thumbnails/80x80/avatars/<%= user.name %>" alt="<%= user.name %>"/>
</a>
<div class="avatar">
<% if (canViewUsers) { %>
<a href="#/user/<%= user.name %>">
<% } %>
<img width="80" height="80" src="/data/thumbnails/80x80/avatars/<%= user.name %>" alt="<%= user.name %>"/>
<% if (canViewUsers) { %>
</a>
<% } %>
</div>
<div class="details">
<h1>
<a href="#/user/<%= user.name %>">
<% if (canViewUsers) { %>
<a href="#/user/<%= user.name %>">
<%= user.name %>
</a>
<% } else { %>
<%= user.name %>
</a>
<% } %>
</h1>
<div class="date-joined" title="<%= user.registrationTime %>">
<div class="date-joined" title="<%= formatAbsoluteTime(user.registrationTime) %>">
Joined: <%= formatRelativeTime(user.registrationTime) %>
</div>
<div class="date-seen">
<div class="date-seen" title="<%= formatAbsoluteTime(user.lastLoginTime) %>">
Last seen: <%= formatRelativeTime(user.lastLoginTime) %>
</div>
</div>

View File

@ -51,12 +51,16 @@
<table>
<tr>
<td>Registered:</td>
<td><%= formatRelativeTime(user.registrationTime) %></td>
<td title="<%= formatAbsoluteTime(user.registrationTime) %>">
<%= formatRelativeTime(user.registrationTime) %>
</td>
</tr>
<tr>
<td>Seen:</td>
<td><%= formatRelativeTime(user.lastLoginTime) %></td>
<td title="<%= formatAbsoluteTime(user.lastLoginTime) %>">
<%= formatRelativeTime(user.lastLoginTime) %>
</td>
</tr>
<% if (user.accessRank) { %>

View File

@ -25,7 +25,7 @@ if ($testMode)
$databaseConnection = \Szurubooru\Injector::get(\Szurubooru\DatabaseConnection::class);
$pdo = $databaseConnection->getPDO();
$pdo->exec('DROP DATABASE szuru_test');
$pdo->exec('DROP DATABASE IF EXISTS szuru_test');
$pdo->exec('CREATE DATABASE szuru_test');
$pdo->exec('USE szuru_test');
}

View File

@ -1,17 +0,0 @@
<?php
namespace Szurubooru;
class ControllerRepository
{
private $controllers = [];
public function __construct(array $controllers)
{
$this->controllers = $controllers;
}
public function getControllers()
{
return $this->controllers;
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Router;
abstract class AbstractController
{
abstract function registerRoutes(Router $router);
}

View File

@ -1,155 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\CommentViewProxy;
use Szurubooru\Controllers\ViewProxies\PostViewProxy;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\SearchServices\Filters\CommentFilter;
use Szurubooru\SearchServices\Filters\PostFilter;
use Szurubooru\SearchServices\Requirements\Requirement;
use Szurubooru\SearchServices\Requirements\RequirementRangedValue;
use Szurubooru\SearchServices\Requirements\RequirementSingleValue;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
final class CommentController extends AbstractController
{
private $privilegeService;
private $authService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function registerRoutes(Router $router)
{
$router->get('/api/comments', [$this, 'getComments']);
$router->get('/api/comments/:postNameOrId', [$this, 'getPostComments']);
$router->post('/api/comments/:postNameOrId', [$this, 'addComment']);
$router->put('/api/comments/:commentId', [$this, 'editComment']);
$router->delete('/api/comments/:commentId', [$this, 'deleteComment']);
}
public function getComments()
{
$this->privilegeService->assertPrivilege(Privilege::LIST_COMMENTS);
$filter = new PostFilter();
$filter->setPageSize(10);
$filter->setPageNumber($this->inputReader->page);
$filter->setOrder([
PostFilter::ORDER_LAST_COMMENT_TIME =>
PostFilter::ORDER_DESC]);
$this->postService->decorateFilterFromBrowsingSettings($filter);
$requirement = new Requirement();
$requirement->setValue(new RequirementRangedValue());
$requirement->getValue()->setMinValue(1);
$requirement->setType(PostFilter::REQUIREMENT_COMMENT_COUNT);
$filter->addRequirement($requirement);
$result = $this->postService->getFiltered($filter);
$posts = $result->getEntities();
$data = [];
foreach ($posts as $post)
{
$data[] = [
'post' => $this->postViewProxy->fromEntity($post),
'comments' => $this->commentViewProxy->fromArray(
array_reverse($this->commentService->getByPost($post)),
$this->getCommentsFetchConfig()),
];
}
return [
'data' => $data,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
public function getPostComments($postNameOrId)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_COMMENTS);
$post = $this->postService->getByNameOrId($postNameOrId);
$filter = new CommentFilter();
$filter->setOrder([
CommentFilter::ORDER_ID =>
CommentFilter::ORDER_ASC]);
$requirement = new Requirement();
$requirement->setValue(new RequirementSingleValue($post->getId()));
$requirement->setType(CommentFilter::REQUIREMENT_POST_ID);
$filter->addRequirement($requirement);
$result = $this->commentService->getFiltered($filter);
$entities = $this->commentViewProxy->fromArray($result->getEntities(), $this->getCommentsFetchConfig());
return ['data' => $entities];
}
public function addComment($postNameOrId)
{
$this->privilegeService->assertPrivilege(Privilege::ADD_COMMENTS);
$post = $this->postService->getByNameOrId($postNameOrId);
$comment = $this->commentService->createComment($post, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
public function editComment($commentId)
{
$comment = $this->commentService->getById($commentId);
$this->privilegeService->assertPrivilege(
($comment->getUser() && $this->privilegeService->isLoggedIn($comment->getUser()))
? Privilege::EDIT_OWN_COMMENTS
: Privilege::EDIT_ALL_COMMENTS);
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
public function deleteComment($commentId)
{
$comment = $this->commentService->getById($commentId);
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($comment->getUser())
? Privilege::DELETE_OWN_COMMENTS
: Privilege::DELETE_ALL_COMMENTS);
return $this->commentService->deleteComment($comment);
}
private function getCommentsFetchConfig()
{
return
[
CommentViewProxy::FETCH_OWN_SCORE => true,
];
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\UserViewProxy;
use Szurubooru\Router;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\FavoritesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
final class FavoritesController extends AbstractController
{
private $privilegeService;
private $authService;
private $postService;
private $favoritesService;
private $userViewProxy;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
FavoritesService $favoritesService,
UserViewProxy $userViewProxy)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->favoritesService = $favoritesService;
$this->userViewProxy = $userViewProxy;
}
public function registerRoutes(Router $router)
{
$router->get('/api/posts/:postNameOrId/favorites', [$this, 'getFavoriteUsers']);
$router->post('/api/posts/:postNameOrId/favorites', [$this, 'addFavorite']);
$router->delete('/api/posts/:postNameOrId/favorites', [$this, 'deleteFavorite']);
}
public function getFavoriteUsers($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$users = $this->favoritesService->getFavoriteUsers($post);
return ['data' => $this->userViewProxy->fromArray($users)];
}
public function addFavorite($postNameOrId)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($postNameOrId);
$this->favoritesService->addFavorite($user, $post);
return $this->getFavoriteUsers($postNameOrId);
}
public function deleteFavorite($postNameOrId)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($postNameOrId);
$this->favoritesService->deleteFavorite($user, $post);
return $this->getFavoriteUsers($postNameOrId);
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Dao\GlobalParamDao;
use Szurubooru\Router;
final class GlobalParamController extends AbstractController
{
private $globalParamDao;
public function __construct(GlobalParamDao $globalParamDao)
{
$this->globalParamDao = $globalParamDao;
}
public function registerRoutes(Router $router)
{
$router->get('/api/globals', [$this, 'getGlobals']);
}
public function getGlobals()
{
$globals = $this->globalParamDao->findAll();
$return = [];
foreach ($globals as $global)
{
$return[$global->getKey()] = $global->getValue();
}
return $return;
}
}

View File

@ -1,179 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Config;
use Szurubooru\Controllers\ViewProxies\PostViewProxy;
use Szurubooru\Controllers\ViewProxies\SnapshotViewProxy;
use Szurubooru\Controllers\ViewProxies\UserViewProxy;
use Szurubooru\Entities\Post;
use Szurubooru\FormData\PostEditFormData;
use Szurubooru\FormData\UploadFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\SearchServices\Parsers\PostSearchParser;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\PostFeatureService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
final class PostController extends AbstractController
{
private $config;
private $authService;
private $privilegeService;
private $postService;
private $postFeatureService;
private $postSearchParser;
private $inputReader;
private $postViewProxy;
private $snapshotViewProxy;
public function __construct(
Config $config,
AuthService $authService,
PrivilegeService $privilegeService,
PostService $postService,
PostFeatureService $postFeatureService,
PostSearchParser $postSearchParser,
InputReader $inputReader,
UserViewProxy $userViewProxy,
PostViewProxy $postViewProxy,
SnapshotViewProxy $snapshotViewProxy)
{
$this->config = $config;
$this->authService = $authService;
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->postFeatureService = $postFeatureService;
$this->postSearchParser = $postSearchParser;
$this->inputReader = $inputReader;
$this->userViewProxy = $userViewProxy;
$this->postViewProxy = $postViewProxy;
$this->snapshotViewProxy = $snapshotViewProxy;
}
public function registerRoutes(Router $router)
{
$router->post('/api/posts', [$this, 'createPost']);
$router->get('/api/posts', [$this, 'getFiltered']);
$router->get('/api/posts/featured', [$this, 'getFeatured']);
$router->get('/api/posts/:postNameOrId', [$this, 'getByNameOrId']);
$router->get('/api/posts/:postNameOrId/history', [$this, 'getHistory']);
$router->put('/api/posts/:postNameOrId', [$this, 'updatePost']);
$router->delete('/api/posts/:postNameOrId', [$this, 'deletePost']);
$router->post('/api/posts/:postNameOrId/feature', [$this, 'featurePost']);
$router->put('/api/posts/:postNameOrId/feature', [$this, 'featurePost']);
}
public function getFeatured()
{
$post = $this->postFeatureService->getFeaturedPost();
$user = $this->postFeatureService->getFeaturedPostUser();
return [
'user' => $this->userViewProxy->fromEntity($user),
'post' => $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig()),
];
}
public function getByNameOrId($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
public function getHistory($postNameOrId)
{
$this->privilegeService->assertPrivilege(Privilege::VIEW_HISTORY);
$post = $this->getByNameOrId($postNameOrId);
return ['data' => $this->snapshotViewProxy->fromArray($this->postService->getHistory($post))];
}
public function getFiltered()
{
$this->privilegeService->assertPrivilege(Privilege::LIST_POSTS);
$filter = $this->postSearchParser->createFilterFromInputReader($this->inputReader);
$filter->setPageSize($this->config->posts->postsPerPage);
$this->postService->decorateFilterFromBrowsingSettings($filter);
$result = $this->postService->getFiltered($filter);
$entities = $this->postViewProxy->fromArray($result->getEntities(), $this->getLightFetchConfig());
return [
'data' => $entities,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
public function createPost()
{
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS);
$formData = new UploadFormData($this->inputReader);
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS);
if ($formData->anonymous)
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS_ANONYMOUSLY);
$post = $this->postService->createPost($formData);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
public function updatePost($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$formData = new PostEditFormData($this->inputReader);
if ($formData->content !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_CONTENT);
if ($formData->thumbnail !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_THUMBNAIL);
if ($formData->safety !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_SAFETY);
if ($formData->source !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_SOURCE);
if ($formData->tags !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_TAGS);
$this->postService->updatePost($post, $formData);
$post = $this->postService->getByNameOrId($postNameOrId);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
public function deletePost($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$this->postService->deletePost($post);
}
public function featurePost($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$this->postFeatureService->featurePost($post);
}
private function getFullFetchConfig()
{
return
[
PostViewProxy::FETCH_RELATIONS => true,
PostViewProxy::FETCH_TAGS => true,
PostViewProxy::FETCH_USER => true,
PostViewProxy::FETCH_HISTORY => true,
PostViewProxy::FETCH_OWN_SCORE => true,
PostViewProxy::FETCH_FAVORITES => true,
PostViewProxy::FETCH_NOTES => true,
];
}
private function getLightFetchConfig()
{
return
[
PostViewProxy::FETCH_TAGS => true,
];
}
}

View File

@ -1,77 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\PostNoteViewProxy;
use Szurubooru\FormData\PostNoteFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\Services\PostNotesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
final class PostNotesController extends AbstractController
{
private $inputReader;
private $postService;
private $postNotesService;
private $privilegeService;
private $postNoteViewProxy;
public function __construct(
InputReader $inputReader,
PostService $postService,
PostNotesService $postNotesService,
PrivilegeService $privilegeService,
PostNoteViewProxy $postNoteViewProxy)
{
$this->inputReader = $inputReader;
$this->postService = $postService;
$this->postNotesService = $postNotesService;
$this->privilegeService = $privilegeService;
$this->postNoteViewProxy = $postNoteViewProxy;
}
public function registerRoutes(Router $router)
{
$router->get('/api/notes/:postNameOrId', [$this, 'getPostNotes']);
$router->post('/api/notes/:postNameOrId', [$this, 'addPostNote']);
$router->put('/api/notes/:postNoteId', [$this, 'editPostNote']);
$router->delete('/api/notes/:postNoteId', [$this, 'deletePostNote']);
}
public function getPostNotes($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$postNotes = $this->postNotesService->getByPost($post);
return $this->postNoteViewProxy->fromArray($postNotes);
}
public function addPostNote($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$this->privilegeService->assertPrivilege(Privilege::ADD_POST_NOTES);
$formData = new PostNoteFormData($this->inputReader);
$postNote = $this->postNotesService->createPostNote($post, $formData);
return $this->postNoteViewProxy->fromEntity($postNote);
}
public function editPostNote($postNoteId)
{
$postNote = $this->postNotesService->getById($postNoteId);
$this->privilegeService->assertPrivilege(Privilege::EDIT_POST_NOTES);
$formData = new PostNoteFormData($this->inputReader);
$postNote = $this->postNotesService->updatePostNote($postNote, $formData);
return $this->postNoteViewProxy->fromEntity($postNote);
}
public function deletePostNote($postNoteId)
{
$postNote = $this->postNotesService->getById($postNoteId);
$this->privilegeService->assertPrivilege(Privilege::DELETE_POST_NOTES);
return $this->postNotesService->deletePostNote($postNote);
}
}

View File

@ -1,89 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Entities\Entity;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Router;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
final class ScoreController extends AbstractController
{
private $privilegeService;
private $authService;
private $postService;
private $commentService;
private $scoreService;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
CommentService $commentService,
ScoreService $scoreService,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->scoreService = $scoreService;
$this->inputReader = $inputReader;
}
public function registerRoutes(Router $router)
{
$router->get('/api/posts/:postNameOrId/score', [$this, 'getPostScore']);
$router->post('/api/posts/:postNameOrId/score', [$this, 'setPostScore']);
$router->get('/api/comments/:commentId/score', [$this, 'getCommentScore']);
$router->post('/api/comments/:commentId/score', [$this, 'setCommentScore']);
}
public function getPostScore($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
return $this->getScore($post);
}
public function setPostScore($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
return $this->setScore($post);
}
public function getCommentScore($commentId)
{
$comment = $this->commentService->getById($commentId);
return $this->getScore($comment);
}
public function setCommentScore($commentId)
{
$comment = $this->commentService->getById($commentId);
return $this->setScore($comment);
}
private function setScore(Entity $entity)
{
$this->privilegeService->assertLoggedIn();
$score = intval($this->inputReader->score);
$user = $this->authService->getLoggedInUser();
$result = $this->scoreService->setUserScore($user, $entity, $score);
return [
'score' => $this->scoreService->getScoreValue($entity),
'ownScore' => $result->getScore(),
];
}
private function getScore(Entity $entity)
{
$user = $this->authService->getLoggedInUser();
return [
'score' => $this->scoreService->getScoreValue($entity),
'ownScore' => $this->scoreService->getUserScoreValue($user, $entity),
];
}
}

View File

@ -1,127 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\TagViewProxy;
use Szurubooru\FormData\TagEditFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\SearchServices\Parsers\TagSearchParser;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\TagService;
final class TagController extends AbstractController
{
private $privilegeService;
private $tagService;
private $tagViewProxy;
private $tagSearchParser;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
TagService $tagService,
TagViewProxy $tagViewProxy,
TagSearchParser $tagSearchParser,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->tagService = $tagService;
$this->tagViewProxy = $tagViewProxy;
$this->tagSearchParser = $tagSearchParser;
$this->inputReader = $inputReader;
}
public function registerRoutes(Router $router)
{
$router->get('/api/tags', [$this, 'getTags']);
$router->get('/api/tags/:tagName', [$this, 'getTag']);
$router->get('/api/tags/:tagName/siblings', [$this, 'getTagSiblings']);
$router->put('/api/tags/:tagName', [$this, 'updateTag']);
$router->put('/api/tags/:tagName/merge', [$this, 'mergeTag']);
$router->delete('/api/tags/:tagName', [$this, 'deleteTag']);
}
public function getTag($tagName)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
$tag = $this->tagService->getByName($tagName);
return $this->tagViewProxy->fromEntity($tag, $this->getFullFetchConfig());
}
public function getTags()
{
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
$filter = $this->tagSearchParser->createFilterFromInputReader($this->inputReader);
$filter->setPageSize(50);
$result = $this->tagService->getFiltered($filter);
$entities = $this->tagViewProxy->fromArray($result->getEntities(), $this->getFullFetchConfig());
return [
'data' => $entities,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
public function getTagSiblings($tagName)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
$tag = $this->tagService->getByName($tagName);
$result = $this->tagService->getSiblings($tagName);
$entities = $this->tagViewProxy->fromArray($result);
return [
'data' => $entities,
];
}
public function updateTag($tagName)
{
$tag = $this->tagService->getByName($tagName);
$formData = new TagEditFormData($this->inputReader);
if ($formData->name !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_TAG_NAME);
if ($formData->category !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_TAG_CATEGORY);
if ($formData->banned !== null)
$this->privilegeService->assertPrivilege(Privilege::BAN_TAGS);
if ($formData->implications !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_TAG_IMPLICATIONS);
if ($formData->suggestions !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_TAG_SUGGESTIONS);
$tag = $this->tagService->updateTag($tag, $formData);
return $this->tagViewProxy->fromEntity($tag, $this->getFullFetchConfig());
}
public function deleteTag($tagName)
{
$tag = $this->tagService->getByName($tagName);
$this->privilegeService->assertPrivilege(Privilege::DELETE_TAGS);
return $this->tagService->deleteTag($tag);
}
public function mergeTag($tagName)
{
$targetTagName = $this->inputReader->targetTag;
$sourceTag = $this->tagService->getByName($tagName);
$targetTag = $this->tagService->getByName($targetTagName);
$this->privilegeService->assertPrivilege(Privilege::MERGE_TAGS);
return $this->tagService->mergeTag($sourceTag, $targetTag);
}
private function getFullFetchConfig()
{
return
[
TagViewProxy::FETCH_IMPLICATIONS => true,
TagViewProxy::FETCH_SUGGESTIONS => true,
TagViewProxy::FETCH_HISTORY => true,
];
}
}

View File

@ -1,176 +0,0 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Config;
use Szurubooru\Controllers\ViewProxies\UserViewProxy;
use Szurubooru\FormData\RegistrationFormData;
use Szurubooru\FormData\UserEditFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\SearchServices\Parsers\UserSearchParser;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\TokenService;
use Szurubooru\Services\UserService;
final class UserController extends AbstractController
{
private $config;
private $privilegeService;
private $userService;
private $tokenService;
private $userSearchParser;
private $inputReader;
private $userViewProxy;
public function __construct(
Config $config,
PrivilegeService $privilegeService,
UserService $userService,
TokenService $tokenService,
UserSearchParser $userSearchParser,
InputReader $inputReader,
UserViewProxy $userViewProxy)
{
$this->config = $config;
$this->privilegeService = $privilegeService;
$this->userService = $userService;
$this->tokenService = $tokenService;
$this->userSearchParser = $userSearchParser;
$this->inputReader = $inputReader;
$this->userViewProxy = $userViewProxy;
}
public function registerRoutes(Router $router)
{
$router->post('/api/users', [$this, 'createUser']);
$router->get('/api/users', [$this, 'getFiltered']);
$router->get('/api/users/:userNameOrEmail', [$this, 'getByNameOrEmail']);
$router->put('/api/users/:userNameOrEmail', [$this, 'updateUser']);
$router->delete('/api/users/:userNameOrEmail', [$this, 'deleteUser']);
$router->post('/api/password-reset/:userNameOrEmail', [$this, 'passwordReset']);
$router->post('/api/finish-password-reset/:tokenName', [$this, 'finishPasswordReset']);
$router->post('/api/activation/:userNameOrEmail', [$this, 'activation']);
$router->post('/api/finish-activation/:tokenName', [$this, 'finishActivation']);
}
public function getByNameOrEmail($userNameOrEmail)
{
if (!$this->privilegeService->isLoggedIn($userNameOrEmail))
$this->privilegeService->assertPrivilege(Privilege::VIEW_USERS);
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
return $this->userViewProxy->fromEntity($user);
}
public function getFiltered()
{
$this->privilegeService->assertPrivilege(Privilege::LIST_USERS);
$filter = $this->userSearchParser->createFilterFromInputReader($this->inputReader);
$filter->setPageSize($this->config->users->usersPerPage);
$result = $this->userService->getFiltered($filter);
$entities = $this->userViewProxy->fromArray($result->getEntities());
return [
'data' => $entities,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
public function createUser()
{
$this->privilegeService->assertPrivilege(Privilege::REGISTER);
$formData = new RegistrationFormData($this->inputReader);
$user = $this->userService->createUser($formData);
return $this->userViewProxy->fromEntity($user);
}
public function updateUser($userNameOrEmail)
{
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
$formData = new UserEditFormData($this->inputReader);
if ($formData->avatarStyle !== null || $formData->avatarContent !== null)
{
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($userNameOrEmail)
? Privilege::CHANGE_OWN_AVATAR_STYLE
: Privilege::CHANGE_ALL_AVATAR_STYLES);
}
if ($formData->userName !== null)
{
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($userNameOrEmail)
? Privilege::CHANGE_OWN_NAME
: Privilege::CHANGE_ALL_NAMES);
}
if ($formData->password !== null)
{
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($userNameOrEmail)
? Privilege::CHANGE_OWN_PASSWORD
: Privilege::CHANGE_ALL_PASSWORDS);
}
if ($formData->email !== null)
{
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($userNameOrEmail)
? Privilege::CHANGE_OWN_EMAIL_ADDRESS
: Privilege::CHANGE_ALL_EMAIL_ADDRESSES);
}
if ($formData->accessRank)
{
$this->privilegeService->assertPrivilege(Privilege::CHANGE_ACCESS_RANK);
}
if ($formData->browsingSettings)
{
$this->privilegeService->assertLoggedIn($userNameOrEmail);
}
if ($formData->banned !== null)
{
$this->privilegeService->assertPrivilege(Privilege::BAN_USERS);
}
$user = $this->userService->updateUser($user, $formData);
return $this->userViewProxy->fromEntity($user);
}
public function deleteUser($userNameOrEmail)
{
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($userNameOrEmail)
? Privilege::DELETE_OWN_ACCOUNT
: Privilege::DELETE_ACCOUNTS);
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
return $this->userService->deleteUser($user);
}
public function passwordReset($userNameOrEmail)
{
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
return $this->userService->sendPasswordResetEmail($user);
}
public function activation($userNameOrEmail)
{
$user = $this->userService->getByNameOrEmail($userNameOrEmail, true);
return $this->userService->sendActivationEmail($user);
}
public function finishPasswordReset($tokenName)
{
$token = $this->tokenService->getByName($tokenName);
return ['newPassword' => $this->userService->finishPasswordReset($token)];
}
public function finishActivation($tokenName)
{
$token = $this->tokenService->getByName($tokenName);
$this->userService->finishActivation($token);
}
}

View File

@ -3,12 +3,12 @@ namespace Szurubooru\Dao;
use Szurubooru\Dao\EntityConverters\IEntityConverter;
use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Entity;
use Szurubooru\SearchServices\Filters\IFilter;
use Szurubooru\SearchServices\Requirements\Requirement;
use Szurubooru\SearchServices\Requirements\RequirementCompositeValue;
use Szurubooru\SearchServices\Requirements\RequirementRangedValue;
use Szurubooru\SearchServices\Requirements\RequirementSingleValue;
use Szurubooru\SearchServices\Result;
use Szurubooru\Search\Filters\IFilter;
use Szurubooru\Search\Requirements\Requirement;
use Szurubooru\Search\Requirements\RequirementCompositeValue;
use Szurubooru\Search\Requirements\RequirementRangedValue;
use Szurubooru\Search\Requirements\RequirementSingleValue;
use Szurubooru\Search\Result;
abstract class AbstractDao implements ICrudDao, IBatchDao
{
@ -137,7 +137,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao
$query->execute();
$lastUsedId = $this->pdo->query('SELECT @lastUsedId')->fetchColumn();
$entity->setId($lastUsedId);
$entity->setId(intval($lastUsedId));
$arrayEntity = $this->entityConverter->toArray($entity);
$this->pdo->insertInto($this->tableName)->values($arrayEntity)->execute();
return $entity;

View File

@ -42,6 +42,6 @@ abstract class AbstractEntityConverter implements IEntityConverter
protected function entityTimeToDbTime($time)
{
return $time;
return $time === null ? null : date('Y-m-d H:i:s', strtotime($time));
}
}

View File

@ -19,7 +19,7 @@ class CommentEntityConverter extends AbstractEntityConverter implements IEntityC
public function toBasicEntity(array $array)
{
$entity = new Comment($array['id']);
$entity = new Comment(intval($array['id']));
$entity->setUserId($array['userId']);
$entity->setPostId($array['postId']);
$entity->setText($array['text']);

View File

@ -17,7 +17,7 @@ class FavoriteEntityConverter extends AbstractEntityConverter implements IEntity
public function toBasicEntity(array $array)
{
$entity = new Favorite($array['id']);
$entity = new Favorite(intval($array['id']));
$entity->setUserId($array['userId']);
$entity->setPostId($array['postId']);
$entity->setTime($this->dbTimeToEntityTime($array['time']));

View File

@ -16,7 +16,7 @@ class GlobalParamEntityConverter extends AbstractEntityConverter implements IEnt
public function toBasicEntity(array $array)
{
$entity = new GlobalParam($array['id']);
$entity = new GlobalParam(intval($array['id']));
$entity->setKey($array['dataKey']);
$entity->setValue($array['dataValue']);
return $entity;

View File

@ -20,7 +20,7 @@ class PostNoteEntityConverter extends AbstractEntityConverter implements IEntity
public function toBasicEntity(array $array)
{
$entity = new PostNote($array['id']);
$entity = new PostNote(intval($array['id']));
$entity->setPostId($array['postId']);
$entity->setLeft(floatval($array['x']));
$entity->setTop(floatval($array['y']));

View File

@ -19,7 +19,7 @@ class ScoreEntityConverter extends AbstractEntityConverter implements IEntityCon
public function toBasicEntity(array $array)
{
$entity = new Score($array['id']);
$entity = new Score(intval($array['id']));
$entity->setUserId($array['userId']);
$entity->setPostId($array['postId']);
$entity->setCommentId($array['commentId']);

View File

@ -14,8 +14,8 @@ class SnapshotEntityConverter extends AbstractEntityConverter implements IEntity
'primaryKey' => $entity->getPrimaryKey(),
'userId' => $entity->getUserId(),
'operation' => $entity->getOperation(),
'data' => json_encode($entity->getData()),
'dataDifference' => json_encode($entity->getDataDifference()),
'data' => gzdeflate(json_encode($entity->getData())),
'dataDifference' => gzdeflate(json_encode($entity->getDataDifference())),
];
}
@ -25,10 +25,10 @@ class SnapshotEntityConverter extends AbstractEntityConverter implements IEntity
$entity->setTime($this->dbTimeToEntityTime($array['time']));
$entity->setType(intval($array['type']));
$entity->setPrimaryKey($array['primaryKey']);
$entity->setUserId($array['userId']);
$entity->setUserId(intval($array['userId']));
$entity->setOperation($array['operation']);
$entity->setData(json_decode($array['data'], true));
$entity->setDataDifference(json_decode($array['dataDifference'], true));
$entity->setData(json_decode(gzinflate($array['data']), true));
$entity->setDataDifference(json_decode(gzinflate($array['dataDifference']), true));
return $entity;
}
}

View File

@ -11,14 +11,14 @@ class TagEntityConverter extends AbstractEntityConverter implements IEntityConve
[
'name' => $entity->getName(),
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
'banned' => $entity->isBanned(),
'banned' => intval($entity->isBanned()),
'category' => $entity->getCategory(),
];
}
public function toBasicEntity(array $array)
{
$entity = new Tag($array['id']);
$entity = new Tag(intval($array['id']));
$entity->setName($array['name']);
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
$entity->setMeta(Tag::META_USAGES, intval($array['usages']));

View File

@ -19,8 +19,8 @@ class UserEntityConverter extends AbstractEntityConverter implements IEntityConv
'lastLoginTime' => $this->entityTimeToDbTime($entity->getLastLoginTime()),
'avatarStyle' => $entity->getAvatarStyle(),
'browsingSettings' => json_encode($entity->getBrowsingSettings()),
'accountConfirmed' => $entity->isAccountConfirmed(),
'banned' => $entity->isBanned(),
'accountConfirmed' => intval($entity->isAccountConfirmed()),
'banned' => intval($entity->isBanned()),
];
}

View File

@ -7,8 +7,8 @@ use Szurubooru\Dao\UserDao;
use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Entity;
use Szurubooru\Entities\Post;
use Szurubooru\SearchServices\Filters\PostFilter;
use Szurubooru\SearchServices\Requirements\Requirement;
use Szurubooru\Search\Filters\PostFilter;
use Szurubooru\Search\Requirements\Requirement;
use Szurubooru\Services\ThumbnailService;
class PostDao extends AbstractDao implements ICrudDao
@ -44,7 +44,7 @@ class PostDao extends AbstractDao implements ICrudDao
public function getTotalFileSize()
{
$query = $this->pdo->from($this->tableName)->select('SUM(originalFileSize) AS __sum');
return intval(iterator_to_array($query)[0]['__sum']);
return iterator_to_array($query)[0]['__sum'];
}
public function findByName($name)

View File

@ -22,13 +22,17 @@ class SnapshotDao extends AbstractDao
$this->userDao = $userDao;
}
public function findByTypeAndKey($type, $primaryKey)
public function findEarlierSnapshots(Snapshot $snapshot)
{
$query = $this->pdo
->from($this->tableName)
->where('type', $type)
->where('primaryKey', $primaryKey)
->where('type', $snapshot->getType())
->where('primaryKey', $snapshot->getPrimaryKey())
->orderBy('time DESC');
if ($snapshot->getId())
$query->where('id < ?', $snapshot->getId());
return $this->arrayToEntities(iterator_to_array($query));
}
@ -44,7 +48,7 @@ class SnapshotDao extends AbstractDao
private function getUser(Snapshot $snapshot)
{
$userId = $snapshot->getUserId();
$userId = $snapshot->getUserId();
return $this->userDao->findById($userId);
}
}

View File

@ -5,8 +5,8 @@ use Szurubooru\Dao\EntityConverters\TagEntityConverter;
use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Entity;
use Szurubooru\Entities\Tag;
use Szurubooru\SearchServices\Filters\TagFilter;
use Szurubooru\SearchServices\Requirements\Requirement;
use Szurubooru\Search\Filters\TagFilter;
use Szurubooru\Search\Requirements\Requirement;
class TagDao extends AbstractDao implements ICrudDao
{

View File

@ -2,7 +2,6 @@
namespace Szurubooru;
use Szurubooru\Bootstrap;
use Szurubooru\Config;
use Szurubooru\ControllerRepository;
use Szurubooru\DatabaseConnection;
use Szurubooru\Helpers\HttpHelper;
use Szurubooru\Router;
@ -24,7 +23,7 @@ final class Dispatcher
HttpHelper $httpHelper,
AuthService $authService,
TokenService $tokenService,
ControllerRepository $controllerRepository)
RouteRepository $routeRepository)
{
$this->router = $router;
$this->config = $config;
@ -36,8 +35,7 @@ final class Dispatcher
//if script fails prematurely, mark it as fail from advance
$this->httpHelper->setResponseCode(500);
foreach ($controllerRepository->getControllers() as $controller)
$controller->registerRoutes($router);
$routeRepository->injectRoutes($router);
}
public function run($requestMethod, $requestUri)

View File

@ -20,8 +20,8 @@ class PostEditFormData implements IValidatable
{
if ($inputReader !== null)
{
$this->content = $inputReader->decodeBase64($inputReader->content);
$this->thumbnail = $inputReader->decodeBase64($inputReader->thumbnail);
$this->content = $inputReader->readFile('content');
$this->thumbnail = $inputReader->readFile('thumbnail');
if ($inputReader->safety)
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety);
if ($inputReader->source !== null)
@ -31,7 +31,7 @@ class PostEditFormData implements IValidatable
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
$this->seenEditTime = $inputReader->seenEditTime;
$this->flags = new \StdClass;
$this->flags->loop = !empty($inputReader->flags['loop']);
$this->flags->loop = !empty($inputReader->loop);
}
}

View File

@ -19,7 +19,7 @@ class UploadFormData implements IValidatable
if ($inputReader !== null)
{
$this->contentFileName = $inputReader->contentFileName;
$this->content = $inputReader->decodeBase64($inputReader->content);
$this->content = $inputReader->readFile('content');
$this->url = $inputReader->url;
$this->anonymous = $inputReader->anonymous;
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety);

View File

@ -27,7 +27,7 @@ class UserEditFormData implements IValidatable
$this->accessRank = EnumHelper::accessRankFromString($inputReader->accessRank);
if ($inputReader->avatarStyle !== null)
$this->avatarStyle = EnumHelper::avatarStyleFromString($inputReader->avatarStyle);
$this->avatarContent = $inputReader->decodeBase64($inputReader->avatarContent);
$this->avatarContent = $inputReader->readFile('avatarContent');
$this->browsingSettings = json_decode($inputReader->browsingSettings);
if ($inputReader->banned !== null)
$this->banned = boolval($inputReader->banned);

View File

@ -25,13 +25,11 @@ final class InputReader extends \ArrayObject
return parent::offsetGet($index);
}
public function decodeBase64($base64string)
public function readFile($fileName)
{
if ($base64string === null)
if (!isset($_FILES[$fileName]))
return null;
$commaPosition = strpos($base64string, ',');
if ($commaPosition !== null)
$base64string = substr($base64string, $commaPosition + 1);
return base64_decode($base64string);
return file_get_contents($_FILES[$fileName]['tmp_name']);
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace Szurubooru;
final class Route
{
public $query;
public $route;
public function __construct($query, callable $route)
{
$this->query = $query;
$this->route = $route;
$this->regex = $this->getRegex();
}
public function handle($query, &$output)
{
$query = trim($query, '/');
if (!preg_match($this->regex, $query, $matches))
return false;
$routeArguments = $this->getRouteArguments($matches);
$func = $this->route;
$output = $func(...array_values($routeArguments));
return true;
}
private function getRegex()
{
$quotedQuery = preg_quote(trim($this->query, '/'), '/');
return '/^' . preg_replace('/\\\?\:([a-zA-Z_-]*)/', '(?P<\1>[^\/]+)', $quotedQuery) . '$/i';
}
private function getRouteArguments($matches)
{
$reflectionFunction = is_array($this->route)
? new \ReflectionMethod($this->route[0], $this->route[1])
: new \ReflectionFunction($this->route);
$arguments = [];
foreach ($reflectionFunction->getParameters() as $reflectionParameter)
{
$key = $reflectionParameter->name;
if (isset($matches[$key]))
$arguments[$key] = $matches[$key];
elseif ($reflectionParameter->isDefaultValueAvailable())
$arguments[$key] = $reflectionParameter->getDefaultValue();
else
$arguments[$key] = null;
}
return $arguments;
}
}

29
src/RouteRepository.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace Szurubooru;
class RouteRepository
{
private $routes = [];
public function __construct(array $routes)
{
$this->routes = $routes;
}
public function getRoutes()
{
return $this->routes;
}
public function injectRoutes(Router $router)
{
foreach ($this->routes as $route)
{
foreach ($route->getMethods() as $method)
{
$method = strtolower($method);
$router->$method($route->getUrl(), [$route, 'work']);
}
}
}
}

View File

@ -5,29 +5,24 @@ class Router
{
private $routes;
public function get($query, callable $route)
public function get($url, callable $function)
{
$this->route('GET', $query, $route);
$this->inject('GET', $url, $function);
}
public function put($query, callable $route)
public function post($url, callable $function)
{
$this->route('PUT', $query, $route);
$this->inject('POST', $url, $function);
}
public function delete($query, callable $route)
public function put($url, callable $function)
{
$this->route('DELETE', $query, $route);
$this->inject('PUT', $url, $function);
}
public function post($query, callable $route)
public function delete($url, callable $function)
{
$this->route('POST', $query, $route);
}
private function route($method, $query, callable $route)
{
$this->routes[$method][] = new Route($query, $route);
$this->inject('DELETE', $url, $function);
}
public function handle($method, $request)
@ -35,14 +30,28 @@ class Router
if (!isset($this->routes[$method]))
throw new \DomainException('Unhandled request method: ' . $method);
foreach ($this->routes[$method] as $route)
$request = trim($request, '/');
foreach ($this->routes[$method] as $url => $callback)
{
if ($route->handle($request, $output))
{
return $output;
}
if (!preg_match(self::getRegex($url), $request, $matches))
continue;
return $callback($matches);
}
throw new \DomainException('Unhandled request address: ' . $request);
}
private function inject($method, $url, callable $function)
{
if (!isset($this->routes[$method]))
$this->routes[$method] = [];
$this->routes[$method][$url] = $function;
}
private static function getRegex($url)
{
$quotedQuery = preg_quote(trim($url, '/'), '/');
return '/^' . preg_replace('/\\\?\:([a-zA-Z_-]*)/', '(?P<\1>[^\/]+)', $quotedQuery) . '$/i';
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Szurubooru\Routes;
abstract class AbstractRoute
{
public abstract function getMethods();
public abstract function getUrl();
public abstract function work($args);
}

View File

@ -0,0 +1,15 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\ViewProxies\CommentViewProxy;
abstract class AbstractCommentRoute extends AbstractRoute
{
protected function getCommentsFetchConfig()
{
return
[
CommentViewProxy::FETCH_OWN_SCORE => true,
];
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\CommentViewProxy;
use Szurubooru\ViewProxies\PostViewProxy;
class AddComment extends AbstractCommentRoute
{
private $privilegeService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function getMethods()
{
return ['POST'];
}
public function getUrl()
{
return '/api/comments/:postNameOrId';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::ADD_COMMENTS);
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$comment = $this->commentService->createComment($post, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\CommentViewProxy;
use Szurubooru\ViewProxies\PostViewProxy;
class DeleteComment extends AbstractCommentRoute
{
private $privilegeService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function getMethods()
{
return ['DELETE'];
}
public function getUrl()
{
return '/api/comments/:commentId';
}
public function work($args)
{
$comment = $this->commentService->getById($args['commentId']);
$this->privilegeService->assertPrivilege(
$this->privilegeService->isLoggedIn($comment->getUser())
? Privilege::DELETE_OWN_COMMENTS
: Privilege::DELETE_ALL_COMMENTS);
return $this->commentService->deleteComment($comment);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\CommentViewProxy;
use Szurubooru\ViewProxies\PostViewProxy;
class EditComment extends AbstractCommentRoute
{
private $privilegeService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function getMethods()
{
return ['PUT'];
}
public function getUrl()
{
return '/api/comments/:commentId';
}
public function work($args)
{
$comment = $this->commentService->getById($args['commentId']);
$this->privilegeService->assertPrivilege(
($comment->getUser() && $this->privilegeService->isLoggedIn($comment->getUser()))
? Privilege::EDIT_OWN_COMMENTS
: Privilege::EDIT_ALL_COMMENTS);
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Search\Filters\PostFilter;
use Szurubooru\Search\Requirements\Requirement;
use Szurubooru\Search\Requirements\RequirementRangedValue;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\CommentViewProxy;
use Szurubooru\ViewProxies\PostViewProxy;
class GetComments extends AbstractCommentRoute
{
private $privilegeService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/comments';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_COMMENTS);
$filter = new PostFilter();
$filter->setPageSize(10);
$filter->setPageNumber($this->inputReader->page);
$filter->setOrder([
PostFilter::ORDER_LAST_COMMENT_TIME =>
PostFilter::ORDER_DESC]);
$this->postService->decorateFilterFromBrowsingSettings($filter);
$requirement = new Requirement();
$requirement->setValue(new RequirementRangedValue());
$requirement->getValue()->setMinValue(1);
$requirement->setType(PostFilter::REQUIREMENT_COMMENT_COUNT);
$filter->addRequirement($requirement);
$result = $this->postService->getFiltered($filter);
$posts = $result->getEntities();
$data = [];
foreach ($posts as $post)
{
$data[] = [
'post' => $this->postViewProxy->fromEntity($post),
'comments' => $this->commentViewProxy->fromArray(
array_reverse($this->commentService->getByPost($post)),
$this->getCommentsFetchConfig()),
];
}
return [
'data' => $data,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Szurubooru\Routes\Comments;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Search\Filters\CommentFilter;
use Szurubooru\Search\Requirements\Requirement;
use Szurubooru\Search\Requirements\RequirementSingleValue;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\CommentViewProxy;
use Szurubooru\ViewProxies\PostViewProxy;
class GetPostComments extends AbstractCommentRoute
{
private $privilegeService;
private $postService;
private $commentService;
private $commentViewProxy;
private $postViewProxy;
private $inputReader;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
CommentService $commentService,
CommentViewProxy $commentViewProxy,
PostViewProxy $postViewProxy,
InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->commentViewProxy = $commentViewProxy;
$this->postViewProxy = $postViewProxy;
$this->inputReader = $inputReader;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/comments/:postNameOrId';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_COMMENTS);
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$filter = new CommentFilter();
$filter->setOrder([
CommentFilter::ORDER_ID =>
CommentFilter::ORDER_ASC]);
$requirement = new Requirement();
$requirement->setValue(new RequirementSingleValue($post->getId()));
$requirement->setType(CommentFilter::REQUIREMENT_POST_ID);
$filter->addRequirement($requirement);
$result = $this->commentService->getFiltered($filter);
$entities = $this->commentViewProxy->fromArray($result->getEntities(), $this->getCommentsFetchConfig());
return ['data' => $entities];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Szurubooru\Routes\Favorites;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\FavoritesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\UserViewProxy;
class AddToFavorites extends AbstractRoute
{
private $privilegeService;
private $authService;
private $postService;
private $favoritesService;
private $userViewProxy;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
FavoritesService $favoritesService,
UserViewProxy $userViewProxy)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->favoritesService = $favoritesService;
$this->userViewProxy = $userViewProxy;
}
public function getMethods()
{
return ['POST', 'PUT'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/favorites';
}
public function work($args)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$this->favoritesService->addFavorite($user, $post);
$users = $this->favoritesService->getFavoriteUsers($post);
return ['data' => $this->userViewProxy->fromArray($users)];
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Szurubooru\Routes\Favorites;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\FavoritesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\UserViewProxy;
class GetFavoriteUsers extends AbstractRoute
{
private $privilegeService;
private $authService;
private $postService;
private $favoritesService;
private $userViewProxy;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
FavoritesService $favoritesService,
UserViewProxy $userViewProxy)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->favoritesService = $favoritesService;
$this->userViewProxy = $userViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/favorites';
}
public function work($args)
{
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$users = $this->favoritesService->getFavoriteUsers($post);
return ['data' => $this->userViewProxy->fromArray($users)];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Szurubooru\Routes\Favorites;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\FavoritesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\UserViewProxy;
class RemoveFromFavorites extends AbstractRoute
{
private $privilegeService;
private $authService;
private $postService;
private $favoritesService;
private $userViewProxy;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
FavoritesService $favoritesService,
UserViewProxy $userViewProxy)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->favoritesService = $favoritesService;
$this->userViewProxy = $userViewProxy;
}
public function getMethods()
{
return ['DELETE'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/favorites';
}
public function work($args)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$this->favoritesService->deleteFavorite($user, $post);
$users = $this->favoritesService->getFavoriteUsers($post);
return ['data' => $this->userViewProxy->fromArray($users)];
}
}

34
src/Routes/GetGlobals.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace Szurubooru\Routes;
use Szurubooru\Dao\GlobalParamDao;
class GetGlobals extends AbstractRoute
{
private $globalParamDao;
public function __construct(GlobalParamDao $globalParamDao)
{
$this->globalParamDao = $globalParamDao;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/globals';
}
public function work($args)
{
$globals = $this->globalParamDao->findAll();
$result = [];
foreach ($globals as $global)
{
$result[$global->getKey()] = $global->getValue();
}
return $result;
}
}

View File

@ -1,14 +1,14 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\SnapshotViewProxy;
namespace Szurubooru\Routes;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Router;
use Szurubooru\SearchServices\Parsers\SnapshotSearchParser;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\Search\Parsers\SnapshotSearchParser;
use Szurubooru\Services\HistoryService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\SnapshotViewProxy;
final class HistoryController extends AbstractController
class GetHistory extends AbstractRoute
{
private $historyService;
private $privilegeService;
@ -30,12 +30,17 @@ final class HistoryController extends AbstractController
$this->snapshotViewProxy = $snapshotViewProxy;
}
public function registerRoutes(Router $router)
public function getMethods()
{
$router->get('/api/history', [$this, 'getFiltered']);
return ['GET'];
}
public function getFiltered()
public function getUrl()
{
return '/api/history';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::VIEW_HISTORY);

View File

@ -1,7 +1,5 @@
<?php
namespace Szurubooru\Controllers;
use Szurubooru\Controllers\ViewProxies\TokenViewProxy;
use Szurubooru\Controllers\ViewProxies\UserViewProxy;
namespace Szurubooru\Routes;
use Szurubooru\FormData\LoginFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Router;
@ -9,8 +7,10 @@ use Szurubooru\Services\AuthService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\TokenService;
use Szurubooru\Services\UserService;
use Szurubooru\ViewProxies\TokenViewProxy;
use Szurubooru\ViewProxies\UserViewProxy;
final class AuthController extends AbstractController
class Login extends AbstractRoute
{
private $authService;
private $userService;
@ -38,13 +38,17 @@ final class AuthController extends AbstractController
$this->tokenViewProxy = $tokenViewProxy;
}
public function registerRoutes(Router $router)
public function getMethods()
{
$router->post('/api/login', [$this, 'login']);
$router->put('/api/login', [$this, 'login']);
return ['POST', 'PUT'];
}
public function login()
public function getUrl()
{
return '/api/login';
}
public function work($args)
{
if (isset($this->inputReader->userNameOrEmail) && isset($this->inputReader->password))
{

View File

@ -0,0 +1,29 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\ViewProxies\PostViewProxy;
abstract class AbstractPostRoute extends AbstractRoute
{
protected function getFullFetchConfig()
{
return
[
PostViewProxy::FETCH_RELATIONS => true,
PostViewProxy::FETCH_TAGS => true,
PostViewProxy::FETCH_USER => true,
PostViewProxy::FETCH_HISTORY => true,
PostViewProxy::FETCH_OWN_SCORE => true,
PostViewProxy::FETCH_FAVORITES => true,
PostViewProxy::FETCH_NOTES => true,
];
}
protected function getLightFetchConfig()
{
return
[
PostViewProxy::FETCH_TAGS => true,
];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\FormData\UploadFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostViewProxy;
class CreatePost extends AbstractPostRoute
{
private $privilegeService;
private $postService;
private $inputReader;
private $postViewProxy;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
InputReader $inputReader,
PostViewProxy $postViewProxy)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->inputReader = $inputReader;
$this->postViewProxy = $postViewProxy;
}
public function getMethods()
{
return ['POST'];
}
public function getUrl()
{
return '/api/posts';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS);
$formData = new UploadFormData($this->inputReader);
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS);
if ($formData->anonymous)
$this->privilegeService->assertPrivilege(Privilege::UPLOAD_POSTS_ANONYMOUSLY);
$post = $this->postService->createPost($formData);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Privilege;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
class DeletePost extends AbstractPostRoute
{
private $privilegeService;
private $postService;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
}
public function getMethods()
{
return ['DELETE'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::DELETE_POSTS);
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$this->postService->deletePost($post);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Privilege;
use Szurubooru\Services\PostFeatureService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
class FeaturePost extends AbstractPostRoute
{
private $privilegeService;
private $postService;
private $postFeatureService;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
PostFeatureService $postFeatureService)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->postFeatureService = $postFeatureService;
}
public function getMethods()
{
return ['POST', 'PUT'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/feature';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::FEATURE_POSTS);
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$this->postFeatureService->featurePost($post);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Entities\Post;
use Szurubooru\Services\PostFeatureService;
use Szurubooru\ViewProxies\PostViewProxy;
use Szurubooru\ViewProxies\UserViewProxy;
class GetFeaturedPost extends AbstractPostRoute
{
private $postFeatureService;
private $postViewProxy;
public function __construct(
PostFeatureService $postFeatureService,
UserViewProxy $userViewProxy,
PostViewProxy $postViewProxy)
{
$this->postFeatureService = $postFeatureService;
$this->userViewProxy = $userViewProxy;
$this->postViewProxy = $postViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts/featured';
}
public function work($args)
{
$post = $this->postFeatureService->getFeaturedPost();
$user = $this->postFeatureService->getFeaturedPostUser();
return [
'user' => $this->userViewProxy->fromEntity($user),
'post' => $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig()),
];
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Privilege;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostViewProxy;
class GetPost extends AbstractPostRoute
{
private $privilegeService;
private $postService;
private $postViewProxy;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
PostViewProxy $postViewProxy)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->postViewProxy = $postViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::VIEW_POSTS);
$post = $this->postService->getByNameOrId($args['postNameOrId']);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Config;
use Szurubooru\Dao\PublicFileDao;
use Szurubooru\Entities\Post;
use Szurubooru\Helpers\MimeHelper;
use Szurubooru\Services\NetworkingService;
use Szurubooru\Services\PostService;
class GetPostContent extends AbstractPostRoute
{
private $config;
private $fileDao;
private $postService;
private $networkingService;
public function __construct(
Config $config,
PublicFileDao $fileDao,
PostService $postService,
NetworkingService $networkingService)
{
$this->config = $config;
$this->fileDao = $fileDao;
$this->postService = $postService;
$this->networkingService = $networkingService;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts/:postName/content';
}
public function work($args)
{
$post = $this->postService->getByName($args['postName']);
$customFileName = sprintf('%s_%s.%s',
$this->config->basic->serviceName,
$post->getName(),
strtolower(MimeHelper::getExtension($post->getContentMimeType())));
if ($post->getContentType() === Post::POST_TYPE_YOUTUBE)
{
$this->networkingService->nonCachedRedirect($post->getOriginalFileName());
return;
}
$this->networkingService->serveFile($this->fileDao->getFullPath($post->getContentPath()), $customFileName);
}
}

View File

@ -1,14 +1,13 @@
<?php
namespace Szurubooru\Controllers;
namespace Szurubooru\Routes\Posts;
use Szurubooru\Config;
use Szurubooru\Dao\PublicFileDao;
use Szurubooru\Helpers\MimeHelper;
use Szurubooru\Router;
use Szurubooru\Entities\Post;
use Szurubooru\Services\NetworkingService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PostThumbnailService;
final class PostContentController extends AbstractController
class GetPostThumbnail extends AbstractPostRoute
{
private $config;
private $fileDao;
@ -30,27 +29,20 @@ final class PostContentController extends AbstractController
$this->postThumbnailService = $postThumbnailService;
}
public function registerRoutes(Router $router)
public function getMethods()
{
$router->get('/api/posts/:postName/content', [$this, 'getPostContent']);
$router->get('/api/posts/:postName/thumbnail/:size', [$this, 'getPostThumbnail']);
return ['GET'];
}
public function getPostContent($postName)
public function getUrl()
{
$post = $this->postService->getByName($postName);
$customFileName = sprintf('%s_%s.%s',
$this->config->basic->serviceName,
$post->getName(),
strtolower(MimeHelper::getExtension($post->getContentMimeType())));
$this->networkingService->serveFile($this->fileDao->getFullPath($post->getContentPath()), $customFileName);
return '/api/posts/:postName/thumbnail/:size';
}
public function getPostThumbnail($postName, $size)
public function work($args)
{
$post = $this->postService->getByName($postName);
$size = $args['size'];
$post = $this->postService->getByName($args['postName']);
$thumbnailName = $this->postThumbnailService->generateIfNeeded($post, $size, $size);
$this->networkingService->serveFile($this->fileDao->getFullPath($thumbnailName));
}

View File

@ -0,0 +1,61 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\Config;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Search\Parsers\PostSearchParser;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostViewProxy;
class GetPosts extends AbstractPostRoute
{
private $config;
private $privilegeService;
private $postService;
private $postSearchParser;
private $inputReader;
private $postViewProxy;
public function __construct(
Config $config,
PrivilegeService $privilegeService,
PostService $postService,
PostSearchParser $postSearchParser,
InputReader $inputReader,
PostViewProxy $postViewProxy)
{
$this->config = $config;
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->postSearchParser = $postSearchParser;
$this->inputReader = $inputReader;
$this->postViewProxy = $postViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_POSTS);
$filter = $this->postSearchParser->createFilterFromInputReader($this->inputReader);
$filter->setPageSize($this->config->posts->postsPerPage);
$this->postService->decorateFilterFromBrowsingSettings($filter);
$result = $this->postService->getFiltered($filter);
$entities = $this->postViewProxy->fromArray($result->getEntities(), $this->getLightFetchConfig());
return [
'data' => $entities,
'pageSize' => $result->getPageSize(),
'totalRecords' => $result->getTotalRecords()];
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Szurubooru\Routes\Posts\Notes;
use Szurubooru\FormData\PostNoteFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Routes\Posts\AbstractPostRoute;
use Szurubooru\Services\PostNotesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostNoteViewProxy;
class AddPostNote extends AbstractPostRoute
{
private $inputReader;
private $postService;
private $postNotesService;
private $privilegeService;
private $postNoteViewProxy;
public function __construct(
InputReader $inputReader,
PostService $postService,
PostNotesService $postNotesService,
PrivilegeService $privilegeService,
PostNoteViewProxy $postNoteViewProxy)
{
$this->inputReader = $inputReader;
$this->postService = $postService;
$this->postNotesService = $postNotesService;
$this->privilegeService = $privilegeService;
$this->postNoteViewProxy = $postNoteViewProxy;
}
public function getMethods()
{
return ['POST'];
}
public function getUrl()
{
return '/api/notes/:postNameOrId';
}
public function work($args)
{
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$this->privilegeService->assertPrivilege(Privilege::ADD_POST_NOTES);
$formData = new PostNoteFormData($this->inputReader);
$postNote = $this->postNotesService->createPostNote($post, $formData);
return $this->postNoteViewProxy->fromEntity($postNote);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Szurubooru\Routes\Posts\Notes;
use Szurubooru\Privilege;
use Szurubooru\Routes\Posts\AbstractPostRoute;
use Szurubooru\Services\PostNotesService;
use Szurubooru\Services\PrivilegeService;
class DeletePostNote extends AbstractPostRoute
{
private $postNotesService;
private $privilegeService;
public function __construct(
PostNotesService $postNotesService,
PrivilegeService $privilegeService)
{
$this->postNotesService = $postNotesService;
$this->privilegeService = $privilegeService;
}
public function getMethods()
{
return ['DELETE'];
}
public function getUrl()
{
return '/api/notes/:postNoteId';
}
public function work($args)
{
$postNote = $this->postNotesService->getById($args['postNoteId']);
$this->privilegeService->assertPrivilege(Privilege::DELETE_POST_NOTES);
return $this->postNotesService->deletePostNote($postNote);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Szurubooru\Routes\Posts\Notes;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Routes\Posts\AbstractPostRoute;
use Szurubooru\Services\PostNotesService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostNoteViewProxy;
class GetPostNotes extends AbstractPostRoute
{
private $inputReader;
private $postService;
private $postNotesService;
private $privilegeService;
private $postNoteViewProxy;
public function __construct(
InputReader $inputReader,
PostService $postService,
PostNotesService $postNotesService,
PrivilegeService $privilegeService,
PostNoteViewProxy $postNoteViewProxy)
{
$this->inputReader = $inputReader;
$this->postService = $postService;
$this->postNotesService = $postNotesService;
$this->privilegeService = $privilegeService;
$this->postNoteViewProxy = $postNoteViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/notes/:postNameOrId';
}
public function work($args)
{
$post = $this->postService->getByNameOrId($args['postNameOrId']);
$postNotes = $this->postNotesService->getByPost($post);
return $this->postNoteViewProxy->fromArray($postNotes);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Szurubooru\Routes\Posts\Notes;
use Szurubooru\FormData\PostNoteFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Routes\Posts\AbstractPostRoute;
use Szurubooru\Services\PostNotesService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostNoteViewProxy;
class UpdatePostNote extends AbstractPostRoute
{
private $inputReader;
private $postNotesService;
private $privilegeService;
private $postNoteViewProxy;
public function __construct(
InputReader $inputReader,
PostNotesService $postNotesService,
PrivilegeService $privilegeService,
PostNoteViewProxy $postNoteViewProxy)
{
$this->inputReader = $inputReader;
$this->postNotesService = $postNotesService;
$this->privilegeService = $privilegeService;
$this->postNoteViewProxy = $postNoteViewProxy;
}
public function getMethods()
{
return ['PUT'];
}
public function getUrl()
{
return '/api/notes/:postNoteId';
}
public function work($args)
{
$postNote = $this->postNotesService->getById($args['postNoteId']);
$this->privilegeService->assertPrivilege(Privilege::EDIT_POST_NOTES);
$formData = new PostNoteFormData($this->inputReader);
$postNote = $this->postNotesService->updatePostNote($postNote, $formData);
return $this->postNoteViewProxy->fromEntity($postNote);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Szurubooru\Routes\Posts;
use Szurubooru\FormData\PostEditFormData;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Privilege;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\ViewProxies\PostViewProxy;
class UpdatePost extends AbstractPostRoute
{
private $privilegeService;
private $postService;
private $inputReader;
private $postViewProxy;
public function __construct(
PrivilegeService $privilegeService,
PostService $postService,
InputReader $inputReader,
PostViewProxy $postViewProxy)
{
$this->privilegeService = $privilegeService;
$this->postService = $postService;
$this->inputReader = $inputReader;
$this->postViewProxy = $postViewProxy;
}
public function getMethods()
{
return ['POST'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId';
}
public function work($args)
{
$postNameOrId = $args['postNameOrId'];
$post = $this->postService->getByNameOrId($postNameOrId);
$formData = new PostEditFormData($this->inputReader);
if ($formData->content !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_CONTENT);
if ($formData->thumbnail !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_THUMBNAIL);
if ($formData->safety !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_SAFETY);
if ($formData->source !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_SOURCE);
if ($formData->tags !== null)
$this->privilegeService->assertPrivilege(Privilege::CHANGE_POST_TAGS);
$this->postService->updatePost($post, $formData);
$post = $this->postService->getByNameOrId($postNameOrId);
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Szurubooru\Routes\Scores;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Entities\Entity;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
use Szurubooru\Routes\AbstractRoute;
abstract class AbstractScoreRoute extends AbstractRoute
{
private $privilegeService;
private $scoreService;
private $authService;
public function __construct(
AuthService $authService,
InputReader $inputReader,
PrivilegeService $privilegeService,
ScoreService $scoreService)
{
$this->authService = $authService;
$this->inputReader = $inputReader;
$this->privilegeService = $privilegeService;
$this->scoreService = $scoreService;
}
protected function getScore(Entity $entity)
{
$user = $this->authService->getLoggedInUser();
return [
'score' => $this->scoreService->getScoreValue($entity),
'ownScore' => $this->scoreService->getUserScoreValue($user, $entity),
];
}
protected function setScore(Entity $entity)
{
$this->privilegeService->assertLoggedIn();
$score = intval($this->inputReader->score);
$user = $this->authService->getLoggedInUser();
$result = $this->scoreService->setUserScore($user, $entity, $score);
return [
'score' => $this->scoreService->getScoreValue($entity),
'ownScore' => $result->getScore(),
];
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Szurubooru\Routes\Scores;
use Szurubooru\Entities\Entity;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
class GetCommentScore extends AbstractScoreRoute
{
private $commentService;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
CommentService $commentService,
ScoreService $scoreService,
InputReader $inputReader)
{
parent::__construct(
$authService,
$inputReader,
$privilegeService,
$scoreService);
$this->commentService = $commentService;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/comments/:commentId/score';
}
public function work($args)
{
$comment = $this->commentService->getById($args['commentId']);
return $this->getScore($comment);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Szurubooru\Routes\Scores;
use Szurubooru\Entities\Entity;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
class GetPostScore extends AbstractScoreRoute
{
private $postService;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
ScoreService $scoreService,
InputReader $inputReader)
{
parent::__construct(
$authService,
$inputReader,
$privilegeService,
$scoreService);
$this->postService = $postService;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/score';
}
public function work($args)
{
$post = $this->postService->getByNameOrId($args['postNameOrId']);
return $this->getScore($post);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Szurubooru\Routes\Scores;
use Szurubooru\Entities\Entity;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\CommentService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
class SetCommentScore extends AbstractScoreRoute
{
private $commentService;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
CommentService $commentService,
ScoreService $scoreService,
InputReader $inputReader)
{
parent::__construct(
$authService,
$inputReader,
$privilegeService,
$scoreService);
$this->commentService = $commentService;
}
public function getMethods()
{
return ['POST', 'PUT'];
}
public function getUrl()
{
return '/api/comments/:commentId/score';
}
public function work($args)
{
$comment = $this->commentService->getById($args['commentId']);
return $this->setScore($comment);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Szurubooru\Routes\Scores;
use Szurubooru\Entities\Entity;
use Szurubooru\Helpers\InputReader;
use Szurubooru\Services\AuthService;
use Szurubooru\Services\PostService;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\ScoreService;
class SetPostScore extends AbstractScoreRoute
{
private $postService;
public function __construct(
PrivilegeService $privilegeService,
AuthService $authService,
PostService $postService,
ScoreService $scoreService,
InputReader $inputReader)
{
parent::__construct(
$authService,
$inputReader,
$privilegeService,
$scoreService);
$this->postService = $postService;
}
public function getMethods()
{
return ['POST', 'PUT'];
}
public function getUrl()
{
return '/api/posts/:postNameOrId/score';
}
public function work($args)
{
$post = $this->postService->getByNameOrId($args['postNameOrId']);
return $this->setScore($post);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Szurubooru\Routes\Tags;
use Szurubooru\Routes\AbstractRoute;
use Szurubooru\ViewProxies\TagViewProxy;
abstract class AbstractTagRoute extends AbstractRoute
{
protected function getFullFetchConfig()
{
return
[
TagViewProxy::FETCH_IMPLICATIONS => true,
TagViewProxy::FETCH_SUGGESTIONS => true,
TagViewProxy::FETCH_HISTORY => true,
];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Szurubooru\Routes\Tags;
use Szurubooru\Privilege;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\TagService;
class DeleteTag extends AbstractTagRoute
{
private $privilegeService;
private $tagService;
public function __construct(
PrivilegeService $privilegeService,
TagService $tagService)
{
$this->privilegeService = $privilegeService;
$this->tagService = $tagService;
}
public function getMethods()
{
return ['DELETE'];
}
public function getUrl()
{
return '/api/tags/:tagName';
}
public function work($args)
{
$tag = $this->tagService->getByName($args['tagName']);
$this->privilegeService->assertPrivilege(Privilege::DELETE_TAGS);
return $this->tagService->deleteTag($tag);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Szurubooru\Routes\Tags;
use Szurubooru\Privilege;
use Szurubooru\Services\PrivilegeService;
use Szurubooru\Services\TagService;
use Szurubooru\ViewProxies\TagViewProxy;
class GetTag extends AbstractTagRoute
{
private $privilegeService;
private $tagService;
private $tagViewProxy;
public function __construct(
PrivilegeService $privilegeService,
TagService $tagService,
TagViewProxy $tagViewProxy)
{
$this->privilegeService = $privilegeService;
$this->tagService = $tagService;
$this->tagViewProxy = $tagViewProxy;
}
public function getMethods()
{
return ['GET'];
}
public function getUrl()
{
return '/api/tags/:tagName';
}
public function work($args)
{
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
$tag = $this->tagService->getByName($args['tagName']);
return $this->tagViewProxy->fromEntity($tag, $this->getFullFetchConfig());
}
}

Some files were not shown because too many files have changed in this diff Show More