Started documenting the API we now have swagger under ~/apidocs

#222 #205
pull/226/head
tidusjar 8 years ago
parent 7266f20927
commit 9b4ae2c486

@ -156,6 +156,8 @@ namespace PlexRequests.UI
nancyConventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content")
);
nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui");
}
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
@ -185,5 +187,18 @@ namespace PlexRequests.UI
notificationService.Subscribe(new PushoverNotification(container.Resolve<IPushoverApi>(), pushoverService));
}
}
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
//CORS Enable
pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) =>
{
ctx.Response.WithHeader("Access-Control-Allow-Origin", "*")
.WithHeader("Access-Control-Allow-Methods", "POST,GET")
.WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type");
});
base.RequestStartup(container, pipelines, context);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);

@ -0,0 +1 @@
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);

@ -0,0 +1,8 @@
/*
jQuery Wiggle
Author: WonderGroup, Jordan Thomas
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
*/
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,125 @@
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,250 @@
.swagger-section #header a#logo {
font-size: 1.5em;
font-weight: bold;
text-decoration: none;
background: transparent url(../images/logo.png) no-repeat left center;
padding: 20px 0 20px 40px;
}
#text-head {
font-size: 80px;
font-family: 'Roboto', sans-serif;
color: #ffffff;
float: right;
margin-right: 20%;
}
.navbar-fixed-top .navbar-nav {
height: auto;
}
.navbar-fixed-top .navbar-brand {
height: auto;
}
.navbar-header {
height: auto;
}
.navbar-inverse {
background-color: #000;
border-color: #000;
}
#navbar-brand {
margin-left: 20%;
}
.navtext {
font-size: 10px;
}
.h1,
h1 {
font-size: 60px;
}
.navbar-default .navbar-header .navbar-brand {
color: #a2dfee;
}
/* tag titles */
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {
color: #393939;
font-family: 'Arvo', serif;
font-size: 1.5em;
}
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {
color: black;
}
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {
color: #525252;
padding-left: 0px;
display: block;
clear: none;
float: left;
font-family: 'Arvo', serif;
font-weight: bold;
}
.navbar-default .navbar-collapse,
.navbar-default .navbar-form {
border-color: #0A0A0A;
}
.container1 {
width: 1500px;
margin: auto;
margin-top: 0;
background-image: url('../images/shield.png');
background-repeat: no-repeat;
background-position: -40px -20px;
margin-bottom: 210px;
}
.container-inner {
width: 1200px;
margin: auto;
background-color: rgba(223, 227, 228, 0.75);
padding-bottom: 40px;
padding-top: 40px;
border-radius: 15px;
}
.header-content {
padding: 0;
width: 1000px;
}
.title1 {
font-size: 80px;
font-family: 'Vollkorn', serif;
color: #404040;
text-align: center;
padding-top: 40px;
padding-bottom: 100px;
}
#icon {
margin-top: -18px;
}
.subtext {
font-size: 25px;
font-style: italic;
color: #08b;
text-align: right;
padding-right: 250px;
}
.bg-primary {
background-color: #00468b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:focus {
color: #08b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:hover {
color: #08b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:focus:hover {
color: #08b;
}
.text-faded {
font-size: 25px;
font-family: 'Vollkorn', serif;
}
.section-heading {
font-family: 'Vollkorn', serif;
font-size: 45px;
padding-bottom: 10px;
}
hr {
border-color: #00468b;
padding-bottom: 10px;
}
.description {
margin-top: 20px;
padding-bottom: 200px;
}
.description li {
font-family: 'Vollkorn', serif;
font-size: 25px;
color: #525252;
margin-left: 28%;
padding-top: 5px;
}
.gap {
margin-top: 200px;
}
.troubleshootingtext {
color: rgba(255, 255, 255, 0.7);
padding-left: 30%;
}
.troubleshootingtext li {
list-style-type: circle;
font-size: 25px;
padding-bottom: 5px;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.block.response_body.json:hover {
cursor: pointer;
}
.backdrop {
color: blue;
}
#myModal {
height: 100%;
}
.modal-backdrop {
bottom: 0;
position: fixed;
}
.curl {
padding: 10px;
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
font-size: 0.9em;
max-height: 400px;
margin-top: 5px;
overflow-y: auto;
background-color: #fcf6db;
border: 1px solid #e5e0c6;
border-radius: 4px;
}
.curl_title {
font-size: 1.1em;
margin: 0;
padding: 15px 0 5px;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
font-weight: 500;
line-height: 1.1;
}
.footer {
display: none;
}
.swagger-section .swagger-ui-wrap h2 {
padding: 0;
}
h2 {
margin: 0;
margin-bottom: 5px;
}
.markdown p {
font-size: 15px;
font-family: 'Arvo', serif;
}
.swagger-section .swagger-ui-wrap .code {
font-size: 15px;
font-family: 'Arvo', serif;
}
.swagger-section .swagger-ui-wrap b {
font-family: 'Arvo', serif;
}
#signin:hover {
cursor: pointer;
}
.dropdown-menu {
padding: 15px;
}
.navbar-right .dropdown-menu {
left: 0;
right: auto;
}
#signinbutton {
width: 100%;
height: 32px;
font-size: 13px;
font-weight: bold;
color: #08b;
}
.navbar-default .nav > li .details {
color: #000000;
text-transform: none;
font-size: 15px;
font-weight: normal;
font-family: 'Open Sans', sans-serif;
font-style: italic;
line-height: 20px;
top: -2px;
}
.navbar-default .nav > li .details:hover {
color: black;
}
#signout {
width: 100%;
height: 32px;
font-size: 13px;
font-weight: bold;
color: #08b;
}

@ -0,0 +1,338 @@
var appName;
var popupMask;
var popupDialog;
var clientId;
var realm;
var redirect_uri;
var clientSecret;
var scopeSeparator;
var additionalQueryStringParams;
function handleLogin() {
var scopes = [];
var auths = window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions;
if(auths) {
var key;
var defs = auths;
for(key in defs) {
var auth = defs[key];
if(auth.type === 'oauth2' && auth.scopes) {
var scope;
if(Array.isArray(auth.scopes)) {
// 1.2 support
var i;
for(i = 0; i < auth.scopes.length; i++) {
scopes.push(auth.scopes[i]);
}
}
else {
// 2.0 support
for(scope in auth.scopes) {
scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key});
}
}
}
}
}
if(window.swaggerUi.api
&& window.swaggerUi.api.info) {
appName = window.swaggerUi.api.info.title;
}
$('.api-popup-dialog').remove();
popupDialog = $(
[
'<div class="api-popup-dialog">',
'<div class="api-popup-title">Select OAuth2.0 Scopes</div>',
'<div class="api-popup-content">',
'<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
'<a href="#">Learn how to use</a>',
'</p>',
'<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',
'<ul class="api-popup-scopes">',
'</ul>',
'<p class="error-msg"></p>',
'<div class="api-popup-actions"><button class="api-popup-authbtn api-button green" type="button">Authorize</button><button class="api-popup-cancel api-button gray" type="button">Cancel</button></div>',
'</div>',
'</div>'].join(''));
$(document.body).append(popupDialog);
//TODO: only display applicable scopes (will need to pass them into handleLogin)
popup = popupDialog.find('ul.api-popup-scopes').empty();
for (i = 0; i < scopes.length; i ++) {
scope = scopes[i];
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"' +'" oauthtype="' + scope.OAuthSchemeKey +'"/>' + '<label for="scope_' + i + '">' + scope.scope ;
if (scope.description) {
if ($.map(auths, function(n, i) { return i; }).length > 1) //if we have more than one scheme, display schemes
str += '<br/><span class="api-scope-desc">' + scope.description + ' ('+ scope.OAuthSchemeKey+')' +'</span>';
else
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
}
str += '</label></li>';
popup.append(str);
}
var $win = $(window),
dw = $win.width(),
dh = $win.height(),
st = $win.scrollTop(),
dlgWd = popupDialog.outerWidth(),
dlgHt = popupDialog.outerHeight(),
top = (dh -dlgHt)/2 + st,
left = (dw - dlgWd)/2;
popupDialog.css({
top: (top < 0? 0 : top) + 'px',
left: (left < 0? 0 : left) + 'px'
});
popupDialog.find('button.api-popup-cancel').click(function() {
popupMask.hide();
popupDialog.hide();
popupDialog.empty();
popupDialog = [];
});
$('button.api-popup-authbtn').unbind();
popupDialog.find('button.api-popup-authbtn').click(function() {
popupMask.hide();
popupDialog.hide();
var authSchemes = window.swaggerUi.api.authSchemes;
var host = window.location;
var pathname = location.pathname.substring(0, location.pathname.lastIndexOf("/"));
var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';
var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;
var url = null;
var scopes = []
var o = popup.find('input:checked');
var OAuthSchemeKeys = [];
var state;
for(k =0; k < o.length; k++) {
var scope = $(o[k]).attr('scope');
if (scopes.indexOf(scope) === -1)
scopes.push(scope);
var OAuthSchemeKey = $(o[k]).attr('oauthtype');
if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1)
OAuthSchemeKeys.push(OAuthSchemeKey);
}
//TODO: merge not replace if scheme is different from any existing
//(needs to be aware of schemes to do so correctly)
window.enabledScopes=scopes;
for (var key in authSchemes) {
if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope.
var flow = authSchemes[key].flow;
if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) {
var dets = authSchemes[key];
url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code');
window.swaggerUi.tokenName = dets.tokenName || 'access_token';
window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null);
state = key;
}
else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) {
var dets = authSchemes[key];
window.swaggerUi.tokenName = dets.tokenName || 'access_token';
clientCredentialsFlow(scopes, dets.tokenUrl, key);
return;
}
else if(authSchemes[key].grantTypes) {
// 1.2 support
var o = authSchemes[key].grantTypes;
for(var t in o) {
if(o.hasOwnProperty(t) && t === 'implicit') {
var dets = o[t];
var ep = dets.loginEndpoint.url;
url = dets.loginEndpoint.url + '?response_type=token';
window.swaggerUi.tokenName = dets.tokenName;
}
else if (o.hasOwnProperty(t) && t === 'accessCode') {
var dets = o[t];
var ep = dets.tokenRequestEndpoint.url;
url = dets.tokenRequestEndpoint.url + '?response_type=code';
window.swaggerUi.tokenName = dets.tokenName;
}
}
}
}
}
redirect_uri = redirectUrl;
url += '&redirect_uri=' + encodeURIComponent(redirectUrl);
url += '&realm=' + encodeURIComponent(realm);
url += '&client_id=' + encodeURIComponent(clientId);
url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator));
url += '&state=' + encodeURIComponent(state);
for (var key in additionalQueryStringParams) {
url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]);
}
window.open(url);
});
popupMask.show();
popupDialog.show();
return;
}
function handleLogout() {
for(key in window.swaggerUi.api.clientAuthorizations.authz){
window.swaggerUi.api.clientAuthorizations.remove(key)
}
window.enabledScopes = null;
$('.api-ic.ic-on').addClass('ic-off');
$('.api-ic.ic-on').removeClass('ic-on');
// set the info box
$('.api-ic.ic-warning').addClass('ic-error');
$('.api-ic.ic-warning').removeClass('ic-warning');
}
function initOAuth(opts) {
var o = (opts||{});
var errors = [];
appName = (o.appName||errors.push('missing appName'));
popupMask = (o.popupMask||$('#api-common-mask'));
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
clientId = (o.clientId||errors.push('missing client id'));
clientSecret = (o.clientSecret||null);
realm = (o.realm||errors.push('missing realm'));
scopeSeparator = (o.scopeSeparator||' ');
additionalQueryStringParams = (o.additionalQueryStringParams||{});
if(errors.length > 0){
log('auth unable initialize oauth: ' + errors);
return;
}
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
$('.api-ic').unbind();
$('.api-ic').click(function(s) {
if($(s.target).hasClass('ic-off'))
handleLogin();
else {
handleLogout();
}
false;
});
}
function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) {
var params = {
'client_id': clientId,
'client_secret': clientSecret,
'scope': scopes.join(' '),
'grant_type': 'client_credentials'
}
$.ajax(
{
url : tokenUrl,
type: "POST",
data: params,
success:function(data, textStatus, jqXHR)
{
onOAuthComplete(data,OAuthSchemeKey);
},
error: function(jqXHR, textStatus, errorThrown)
{
onOAuthComplete("");
}
});
}
window.processOAuthCode = function processOAuthCode(data) {
var OAuthSchemeKey = data.state;
var params = {
'client_id': clientId,
'code': data.code,
'grant_type': 'authorization_code',
'redirect_uri': redirect_uri
};
if (clientSecret) {
params.client_secret = clientSecret;
}
$.ajax(
{
url : window.swaggerUi.tokenUrl,
type: "POST",
data: params,
success:function(data, textStatus, jqXHR)
{
onOAuthComplete(data, OAuthSchemeKey);
},
error: function(jqXHR, textStatus, errorThrown)
{
onOAuthComplete("");
}
});
};
window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) {
if(token) {
if(token.error) {
var checkbox = $('input[type=checkbox],.secured')
checkbox.each(function(pos){
checkbox[pos].checked = false;
});
alert(token.error);
}
else {
var b = token[window.swaggerUi.tokenName];
if (!OAuthSchemeKey){
OAuthSchemeKey = token.state;
}
if(b){
// if all roles are satisfied
var o = null;
$.each($('.auth .api-ic .api_information_panel'), function(k, v) {
var children = v;
if(children && children.childNodes) {
var requiredScopes = [];
$.each((children.childNodes), function (k1, v1){
var inner = v1.innerHTML;
if(inner)
requiredScopes.push(inner);
});
var diff = [];
for(var i=0; i < requiredScopes.length; i++) {
var s = requiredScopes[i];
if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {
diff.push(s);
}
}
if(diff.length > 0){
o = v.parentNode.parentNode;
$(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');
$(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');
// sorry, not all scopes are satisfied
$(o).find('.api-ic').addClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
else {
o = v.parentNode.parentNode;
$(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');
$(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');
// all scopes are satisfied
$(o).find('.api-ic').addClass('ic-info');
$(o).find('.api-ic').removeClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
}
});
window.swaggerUi.api.clientAuthorizations.add(OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header'));
}
}
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,14 @@
/* Google Font's Droid Sans */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype');
}
/* Google Font's Droid Sans Bold */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype');
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,67 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RequestedModelDataProvider.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Nancy.Swagger;
using Nancy.Swagger.Services;
using PlexRequests.Store;
namespace PlexRequests.UI.ModelDataProviders
{
public class RequestedModelDataProvider : ISwaggerModelDataProvider
{
public SwaggerModelData GetModelData()
{
return SwaggerModelData.ForType<RequestedModel>(with =>
{
with.Property(x => x.Title)
.Description("The requests title e.g. Star Wars Episode III")
.Required(true);
with.Property(x => x.AdminNote).Description("A note left by the administrator");
with.Property(x => x.Approved).Description("true or false if the request is approved").Required(true).Default(false);
with.Property(x => x.ArtistId).Description("The artist ID (if this request is for Headphones then it is required)");
with.Property(x => x.ArtistName).Description("The artist name (if this request is for Headphones then it is required)");
with.Property(x => x.Available).Description("If the request is available on Plex").Default(false);
with.Property(x => x.CanApprove).Description("Ignore");
with.Property(x => x.ImdbId).Description("The IMDB id of the request").Required(true);
with.Property(x => x.Issues)
.Description(
"The issue type, None = 99, WrongAudio = 0, NoSubtitles = 1, WrongContent = 2, PlaybackIssues = 3, Other = 4. Use Other(4) when leaving an issue note");
with.Property(x => x.MusicBrainzId).Description("The MusicBrainz ID of the album request (if this request is for Headphones then it is required)");
with.Property(x => x.OtherMessage)
.Description("The issue message left by the user. The Issues property needs to be set to Other (4) for this to work correctly");
with.Property(x => x.PosterPath).Description("The poster path for the request").Required(true);
with.Property(x => x.ProviderId).Description("The TVMaze/TheMovieDB Id for the request depending if it's a movie request or Tv request").Required(true);
with.Property(x => x.ReleaseDate).Description("The release date of the request").Required(true);
with.Property(x => x.RequestedDate).Description("The date if the request, if this is not set, the request date will be set at the time of the Api call");
with.Property(x => x.RequestedUsers).Description("A collection of the requested users").Required(true);
with.Property(x => x.Type).Description("The type of request: Movie = 0, TvShow = 1, Album = 2").Required(true);
with.Property(x => x.UsersToNotify).Description("A list of Plex users to notify");
});
}
}
}

@ -0,0 +1,43 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiDocsModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using Nancy.Responses.Negotiation;
namespace PlexRequests.UI.Modules
{
public class ApiDocsModule : BaseModule
{
public ApiDocsModule() : base("apidocs")
{
Get["/"] = x => Documentation();
}
public Negotiator Documentation()
{
return View["Index"];
}
}
}

@ -0,0 +1,82 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiMetadataModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using Nancy.Metadata.Modules;
using Nancy.Swagger;
using PlexRequests.Store;
namespace PlexRequests.UI.Modules
{
public class ApiMetadataModule: MetadataModule<SwaggerRouteData>
{
public ApiMetadataModule()
{
Describe["GetRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("The list of requests");
with.Notes("This returns a list of users from our awesome app");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Model<ApiModel<List<RequestedModel>>>();
});
Describe["PostRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("Create a new request");
with.Model<ApiModel<bool>>();
with.BodyParam<RequestedModel>("The request", true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Creates a new request");
});
Describe["PutRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("Updates an existing request");
with.Model<ApiModel<bool>>();
with.BodyParam<RequestedModel>("The request", true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Updates an existing request e.g. Add a issue to the request");
});
Describe["DeleteRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("Deletes an existing request");
with.Model<ApiModel<bool>>();
with.BodyParam<RequestedModel>("The request", true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Deletes an existing request. If the request doesn't exist we will return an error.");
});
}
}
}

@ -29,6 +29,7 @@ using System.Collections.Generic;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Validation;
using PlexRequests.Core;
@ -38,12 +39,12 @@ namespace PlexRequests.UI.Modules
{
public class ApiModule : BaseApiModule
{
public ApiModule(IRequestService service) : base("api/v1/")
public ApiModule(IRequestService service) : base("api")
{
Get["/requests"] = x => GetRequests();
Post["/requests"] = x => CreateRequest();
Put["/requests"] = x => UpdateRequest();
Delete["/requests"] = x => DeleteRequest();
Get["GetRequests","/requests"] = x => GetRequests();
Post["PostRequests", "/requests"] = x => CreateRequest();
Put["PutRequests", "/requests"] = x => UpdateRequest();
Delete["DeleteRequests", "/requests"] = x => DeleteRequest();
RequestService = service;
}
@ -133,5 +134,7 @@ namespace PlexRequests.UI.Modules
return ReturnReponse(apiModel);
}
}
}
}

@ -53,10 +53,26 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy.Metadata.Modules, Version=1.4.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.Metadata.Modules.1.4.1\lib\net40\Nancy.Metadata.Modules.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy.Swagger, Version=0.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.Swagger.0.1.0-alpha3\lib\net40\Nancy.Swagger.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Swagger.ObjectModel, Version=0.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Swagger.ObjectModel.0.1.0-alpha3\lib\net40\Swagger.ObjectModel.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
@ -101,9 +117,6 @@
<Reference Include="Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756">
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
</Reference>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
</Reference>
<Reference Include="Nancy.Authentication.Basic, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Nancy.Authentication.Basic.1.4.1\lib\net40\Nancy.Authentication.Basic.dll</HintPath>
</Reference>
@ -151,6 +164,7 @@
<Compile Include="Helpers\ValidationHelper.cs" />
<Compile Include="Jobs\CustomJobFactory.cs" />
<Compile Include="Jobs\Scheduler.cs" />
<Compile Include="ModelDataProviders\RequestedModelDataProvider.cs" />
<Compile Include="Models\DatatablesModel.cs" />
<Compile Include="Models\JsonUpdateAvailableModel.cs" />
<Compile Include="Models\MovieSearchType.cs" />
@ -158,6 +172,8 @@
<Compile Include="Models\SearchViewModel.cs" />
<Compile Include="Models\SearchMusicViewModel.cs" />
<Compile Include="Models\SearchMovieViewModel.cs" />
<Compile Include="Modules\ApiDocsModule.cs" />
<Compile Include="Modules\ApiMetadataModule.cs" />
<Compile Include="Modules\BaseApiModule.cs" />
<Compile Include="Modules\BaseModule.cs" />
<Compile Include="Modules\UpdateCheckerModule.cs" />
@ -196,6 +212,66 @@
<Compile Include="Startup.cs" />
<Compile Include="Validators\PlexRequestsValidator.cs" />
<Compile Include="Modules\UserManagementModule.cs" />
<Content Include="Content\swagger\backbone-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\handlebars-2.0.0.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\highlight.7.3.pack.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\jquery-1.8.0.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\jquery.ba-bbq.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\jquery.slideto.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\jquery.wiggle.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\jsoneditor.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\marked.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\print.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\reset.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\screen.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\swagger-oauth.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\swagger-ui.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\swagger-ui.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\typography.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\underscore-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\ApiDocs\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\ApiDocs\o2c.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\UserManagement\Index.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -295,6 +371,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\pace.scss" />
<None Include="Content\swagger\underscore-min.map">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="job_scheduling_data_2_0.xsd">
<SubType>Designer</SubType>
</None>

@ -0,0 +1,124 @@

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
<link href='Content/swagger/typography.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='Content/swagger/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='Content/swagger/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='Content/swagger/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='Content/swagger/print.css' media='print' rel='stylesheet' type='text/css'/>
<script src='Content/swagger/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='Content/swagger/jquery.slideto.min.js' type='text/javascript'></script>
<script src='Content/swagger/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='Content/swagger/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='Content/swagger/handlebars-2.0.0.js' type='text/javascript'></script>
<script src='Content/swagger/underscore-min.js' type='text/javascript'></script>
<script src='Content/swagger/backbone-min.js' type='text/javascript'></script>
<script src='Content/swagger/swagger-ui.js' type='text/javascript'></script>
<script src='Content/swagger/highlight.7.3.pack.js' type='text/javascript'></script>
<script src='Content/swagger/jsoneditor.min.js' type='text/javascript'></script>
<script src='Content/swagger/marked.js' type='text/javascript'></script>
<script src='Content/swagger/swagger-oauth.js' type='text/javascript'></script>
<!-- Some basic translations -->
<!-- <script src='lang/translator.js' type='text/javascript'></script> -->
<!-- <script src='lang/ru.js' type='text/javascript'></script> -->
<!-- <script src='lang/en.js' type='text/javascript'></script> -->
<script type="text/javascript">
$(function () {
var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
} else {
url = "http://localhost:3579/api-docs";
}
// Pre load translate...
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
window.swaggerUi = new SwaggerUi({
url: url,
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
onComplete: function(swaggerApi, swaggerUi){
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: ",",
additionalQueryStringParams: {}
});
}
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
addApiKeyAuthorization();
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none",
jsonEditor: false,
apisSorter: "alpha",
defaultModelRendering: 'schema',
showRequestHeaders: false
});
function addApiKeyAuthorization(){
var key = encodeURIComponent($('#input_apiKey')[0].value);
if(key && key.trim() != "") {
var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query");
window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth);
log("added key " + key);
}
}
$('#input_apiKey').change(addApiKeyAuthorization);
// if you have an apiKey you would like to pre-populate on the page for demonstration purposes...
/*
var apiKey = "myApiKeyXXXX123456789";
$('#input_apiKey').val(apiKey);
*/
window.swaggerUi.load();
function log() {
if ('console' in window) {
console.log.apply(console, arguments);
}
}
});
</script>
</head>
<body class="swagger-section">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.io">swagger</a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div class='input'><input placeholder="api_key" id="input_apiKey" name="apiKey" type="text"/></div>
<div class='input'><a id="explore" href="#" data-sw-translate>Explore</a></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

@ -0,0 +1,20 @@
<script>
var qp = null;
if(window.location.hash) {
qp = location.hash.substring(1);
}
else {
qp = location.search.substring(1);
}
qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) {
return key===""?value:decodeURIComponent(value) }
):{}
if (window.opener.swaggerUi.tokenUrl)
window.opener.processOAuthCode(qp);
else
window.opener.onOAuthComplete(qp);
window.close();
</script>

@ -56,7 +56,7 @@
<li><a href="@url/admin"><i class="fa fa-cog"></i> Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> Change password</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-heart"></i> Donate!</a></li>
<li><a href="https://www.paypal.me/PlexRequestsNet"><i class="fa fa-heart"></i> Donate!</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> Logout</a></li>
</ul>

@ -16,7 +16,10 @@
<package id="Nancy.Authentication.Basic" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Hosting.Self" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Metadata.Modules" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Owin" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Swagger" version="0.1.0-alpha3" targetFramework="net45" />
<package id="Nancy.Swagger.Annotations" version="0.1.0-alpha3" targetFramework="net45" />
<package id="Nancy.Validation.FluentValidation" version="1.4.1" targetFramework="net45" />
<package id="Nancy.Viewengines.Razor" version="1.4.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
@ -26,6 +29,7 @@
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="Quartz" version="2.3.3" targetFramework="net45" />
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
<package id="Swagger.ObjectModel" version="0.1.0-alpha3" targetFramework="net45" />
<package id="System.Collections" version="4.0.0" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.99.0" targetFramework="net45" />
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net45" />

Loading…
Cancel
Save