From 84eaa9460b55390c40332834817844afdbeb9362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Banno-Cloutier?= <leo.banno-cloutier@savoirfairelinux.com> Date: Fri, 9 Jun 2023 15:54:25 -0400 Subject: [PATCH] jams-server: create the file handler api and edit form Change-Id: I1c0d618a05b013de75e25ec7285564d85fad6226 --- jams-server/doc/index.html | 532 +++++++++++++++--- .../java/net/jami/jams/server/Server.java | 73 ++- .../api/admin/contacts/ContactServlet.java | 3 +- .../api/admin/group/PolicyServlet.java | 12 +- .../api/auth/contacts/ContactServlet.java | 3 +- .../api/image/FileHandlerServlet.java | 165 ++++++ .../server/servlets/filters/CorsFilter.java | 15 +- .../server/servlets/filters/FilterUtils.java | 2 + 8 files changed, 686 insertions(+), 119 deletions(-) create mode 100644 jams-server/src/main/java/net/jami/jams/server/servlets/api/image/FileHandlerServlet.java diff --git a/jams-server/doc/index.html b/jams-server/doc/index.html index 0ea9d9f6..cb0fba68 100644 --- a/jams-server/doc/index.html +++ b/jams-server/doc/index.html @@ -1,22 +1,36 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="X-UA-Compatible" content="IE=edge" /> - <title>Loading...</title> + <title>Acme project</title> + <meta name="description" content="REST Api"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> - <link href="vendor/bootstrap.min.css" rel="stylesheet" media="screen"> - <link href="vendor/prettify.css" rel="stylesheet" media="screen"> - <link href="css/style.css" rel="stylesheet" media="screen, print"> - <link href="img/favicon.ico" rel="icon" type="image/x-icon"> - <script src="vendor/polyfill.js"></script> + <link href="assets/bootstrap.min.css?v=1686163607729" rel="stylesheet" media="screen"> + <link href="assets/prism.css?v=1686163607729" rel="stylesheet" /> + <link href="assets/prism-toolbar.css?v=1686163607729" rel="stylesheet" /> + <link href="assets/prism-diff-highlight.css?v=1686163607729" rel="stylesheet" /> + <link href="assets/main.css?v=1686163607729" rel="stylesheet" media="screen, print"> + <link href="assets/favicon.ico?v=1686163607729" rel="icon" type="image/x-icon"> + <link href="assets/apple-touch-icon.png?v=1686163607729" rel="apple-touch-icon" sizes="180x180"> + <link href="assets/favicon-32x32.png?v=1686163607729" rel="icon" type="image/png" sizes="32x32"> + <link href="assets/favicon-16x16.png?v=1686163607729" rel="icon" type="image/png" sizes="16x16"> </head> + <body class="container-fluid"> +<!-- SIDENAV --> <script id="template-sidenav" type="text/x-handlebars-template"> -<nav id="scrollingNav"> +<nav id="scrollingNav" class="col-sm-3 col-lg-2 sidebar-offcanvas"> + <div class="nav-toggle visible-xs"> + <button type="button" class="btn btn-link" data-toggle="offcanvas"> + <span class="sr-only">{{__ "Toggle navigation"}}</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + </div> <div class="sidenav-search"> - <input class="form-control search" type="text" placeholder="{{__ "Filter..."}}"> + <input class="form-control search" data-action='filter-search' type="text" placeholder="{{__ "Filter..."}}"> <span class="search-reset">x</span> </div> <ul class="sidenav nav nav-list list"> @@ -39,6 +53,7 @@ </nav> </script> +<!-- PROJECT --> <script id="template-project" type="text/x-handlebars-template"> <div class="pull-left"> <h1>{{name}}</h1> @@ -70,7 +85,7 @@ <script id="template-header" type="text/x-handlebars-template"> {{#if content}} - <div id="api-_" class="show-api-article show-api-_-article">{{{content}}}</div> + <div id="api-_header" class="show-api-article show-api-_-article">{{{content}}}</div> {{/if}} </script> @@ -83,8 +98,10 @@ <script id="template-generator" type="text/x-handlebars-template"> {{#if template.withGenerator}} {{#if generator}} - <div class="content"> - {{__ "Generated with"}} <a href="{{{generator.url}}}">{{{generator.name}}}</a> {{{generator.version}}} - {{{generator.time}}} + <div> + <p class="text-muted"> + {{__ "Generated with"}} <a href="{{{generator.url}}}">{{{generator.name}}}</a> {{{generator.version}}} - {{{generator.time}}} + </p> </div> {{/if}} {{/if}} @@ -92,7 +109,7 @@ <script id="template-sections" type="text/x-handlebars-template"> <section id="api-{{group}}" class="show-api-group show-api-{{group}}-group {{#if aloneDisplay}} hide{{/if}}"> - <h1>{{underscoreToSpace title}}</h1> + <h1 class="color-primary font-weight-bold">{{underscoreToSpace title}}</h1> {{#if description}} <p>{{{nl2br description}}}</p> {{/if}} @@ -107,7 +124,7 @@ <script id="template-article" type="text/x-handlebars-template"> <article id="api-{{article.group}}-{{article.name}}-{{article.version}}" {{#if hidden}}class="hide"{{/if}} data-group="{{article.group}}" data-name="{{article.name}}" data-version="{{article.version}}"> <div class="pull-left"> - <h1>{{underscoreToSpace article.groupTitle}}{{#if article.title}} - {{article.title}}{{/if}}</h1> + <h1><span class="color-primary">{{underscoreToSpace article.groupTitle}}</span>{{#if article.title}} <span class="text-muted">|</span> {{article.title}}{{/if}}</h1> </div> {{#if template.withCompare}} <div class="pull-right"> @@ -137,8 +154,13 @@ {{#if article.description}} <p>{{{nl2br article.description}}}</p> {{/if}} - <span class="type type__{{toLowerCase article.type}}">{{toLowerCase article.type}}</span> - <pre class="prettyprint language-html" data-type="{{toLowerCase article.type}}"><code>{{article.url}}</code></pre> + <span class="method meth-{{toLowerCase article.type}}">{{article.type}}</span> + <pre + data-type="{{toLowerCase article.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-http">{{article.url}}</code></pre> {{#if article.permission}} <p> @@ -154,26 +176,34 @@ </p> {{/if}} - {{#if_gt article.examples.length compare=0}} - <ul class="nav nav-tabs nav-tabs-examples"> + {{!-- CODE EXAMPLES IN TABS --}} + {{#ifCond article.examples.length '>' 0}} + <ul class="nav nav-tabs nav-tabs-examples" role="tablist"> {{#each article.examples}} - <li{{#if_eq @index compare=0}} class="active"{{/if_eq}}> - <a href="#examples-{{../id}}-{{@index}}">{{title}}</a> + <li{{#ifCond @index '==' 0}} class="active"{{/ifCond}}> + <a href="#examples-{{../id}}-{{@index}}" role="tab" data-toggle="tab">{{title}}</a> </li> {{/each}} </ul> <div class="tab-content"> {{#each article.examples}} - <div class="tab-pane{{#if_eq @index compare=0}} active{{/if_eq}}" id="examples-{{../id}}-{{@index}}"> - <pre class="prettyprint language-{{type}}" data-type="{{type}}"><code>{{content}}</code></pre> + <div class="tab-pane{{#ifCond @index '==' 0}} active{{/ifCond}}" id="examples-{{../id}}-{{@index}}"> + <pre + data-type="{{type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{type}}">{{content}}</code></pre> </div> {{/each}} </div> - {{/if_gt}} + {{/ifCond}} {{subTemplate "article-param-block" params=article.header _hasType=_hasTypeInHeaderFields section="header"}} {{subTemplate "article-param-block" params=article.parameter _hasType=_hasTypeInParameterFields section="parameter"}} + {{subTemplate "article-query-block" params=article.query _hasType=_hasTypeInParameterFields section="query"}} + {{subTemplate "article-body-block" params=article.body _hasType=_hasTypeInParameterFields section="body"}} {{subTemplate "article-param-block" params=article.success _hasType=_hasTypeInSuccessFields section="success"}} {{subTemplate "article-param-block" params=article.error _col1="Name" _hasType=_hasTypeInErrorFields section="error"}} @@ -181,6 +211,104 @@ </article> </script> +<script id="template-article-query-block" type="text/x-handlebars-template"> + {{#if article.query}} + <h2>{{__ "Query Parameter(s)"}}</h2> + <table> + <thead> + <tr> + <th style="width: 30%">{{#if ../_col1}}{{__ ../_col1}}{{else}}{{__ "Field"}}{{/if}}</th> + {{#unless this.Type compare=null}} + <th style="width: 10%">{{__ "Type"}}</th> + {{/unless}} + <th style="width: {{#if ../_hasType}}60%{{else}}70%{{/if}}">{{__ "Description"}}</th> + </tr> + </thead> + <tbody> + {{#each params}} + <tr> + <td class="code"> + {{{nestObject this}}} + {{#if this.optional}} + <span class="label optional">{{__ "optional"}}</span> + {{else}} + {{#if ../template.showRequiredLabels}} + <span class="label required">{{__ "required"}}</span> + {{/if}} + {{/if}} + </td> + {{#unless this.Type compare=null}} + <td class="code">{{this.type}}</td> + {{/unless}} + <td>{{{nl2br this.description}}} + {{#if defaultValue}}<p class="default-value">{{__ "Default value:"}} <code>{{{defaultValue}}}</code></p>{{/if}} + {{#if size}}<p class="type-size">{{__ "Size range:"}} <code>{{{size}}}</code></p>{{/if}} + {{#if allowedValues}}<p class="type-size">{{__ "Allowed values:"}} + {{#each allowedValues}} + <code>{{{this}}}</code>{{#unless @last}}, {{/unless}} + {{/each}} + </p> + {{/if}} + </td> + </tr> + {{/each}} + </tbody> + </table> + {{/if}} +</script> + +<script id="template-article-body-block" type="text/x-handlebars-template"> + {{#if article.body}} + <h2>{{__ "Request Body"}}</h2> + <table> + <thead> + <tr> + <th style="width: 30%">{{#if ../_col1}}{{__ ../_col1}}{{else}}{{__ "Field"}}{{/if}}</th> + {{#unless this.Type compare=null}} + <th style="width: 10%">{{__ "Type"}}</th> + {{/unless}} + <th style="width: {{#if ../_hasType}}60%{{else}}70%{{/if}}">{{__ "Description"}}</th> + </tr> + </thead> + <tbody> + {{#each params}} + <tr> + <td class="code"> + {{{nestObject this}}} + {{#if this.optional}} + <span class="label optional">{{__ "optional"}}</span> + {{else}} + {{#if ../template.showRequiredLabels}} + <span class="label required">{{__ "required"}}</span> + {{/if}} + {{/if}} + </td> + {{#unless this.Type compare=null}} + <td class="code">{{this.type}}</td> + {{/unless}} + <td> + {{{nl2br this.description}}} + {{#if defaultValue}} + <p class="default-value">{{__ "Default value:"}} <code>{{{defaultValue}}}</code></p> + {{/if}} + {{#if size}} + <p class="type-size">{{__ "Size range:"}} <code>{{{size}}}</code></p> + {{/if}} + {{#if allowedValues}} + <p class="type-size">{{__ "Allowed values:"}} + {{#each allowedValues}} + <code>{{{this}}}</code>{{#unless @last}}, {{/unless}} + {{/each}} + </p> + {{/if}} + </td> + </tr> + {{/each}} + </tbody> + </table> + {{/if}} +</script> + <script id="template-article-param-block" type="text/x-handlebars-template"> {{#if params}} {{#each params.fields}} @@ -196,9 +324,17 @@ <tbody> {{#each this}} <tr> - <td class="code">{{{splitFill field "." " "}}}{{#if optional}} <span class="label label-optional">{{__ "optional"}}</span>{{/if}}</td> + <td class="code"> + {{{nestObject this}}} + {{#if optional}} + <span class="label optional">{{__ "optional"}}</span> + {{else}} + {{#if ../../template.showRequiredLabels}} + <span class="label required">{{__ "required"}}</span> + {{/if}} + {{/if}}</td> {{#if ../../_hasType}} - <td> + <td class="code"> {{{type}}} </td> {{/if}} @@ -218,51 +354,62 @@ </tbody> </table> {{/each}} - {{#if_gt params.examples.length compare=0}} - <ul class="nav nav-tabs nav-tabs-examples"> + {{#ifCond params.examples.length '>' 0}} + <ul class="nav nav-tabs nav-tabs-examples" role="tablist"> {{#each params.examples}} - <li{{#if_eq @index compare=0}} class="active"{{/if_eq}}> - <a href="#{{../section}}-examples-{{../id}}-{{@index}}">{{title}}</a> + <li{{#ifCond @index '==' 0}} class="active"{{/ifCond}}> + <a href="#{{../section}}-examples-{{../id}}-{{@index}}" role="tab" data-toggle="tab">{{title}}</a> </li> {{/each}} </ul> <div class="tab-content"> {{#each params.examples}} - <div class="tab-pane{{#if_eq @index compare=0}} active{{/if_eq}}" id="{{../section}}-examples-{{../id}}-{{@index}}"> - <pre class="prettyprint language-{{type}}" data-type="{{type}}"><code>{{reformat content type}}</code></pre> + <div class="tab-pane{{#ifCond @index '==' 0}} active{{/ifCond}}" id="{{../section}}-examples-{{../id}}-{{@index}}"> + <pre + data-type="{{type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{type}}">{{reformat content type}}</code></pre> </div> {{/each}} </div> - {{/if_gt}} + {{/ifCond}} {{/if}} </script> <script id="template-article-sample-request" type="text/x-handlebars-template"> - {{#if article.sampleRequest}} - <h2>{{__ "Send a Sample Request"}}</h2> + {{#if article.sampleRequest}} + <div class="well"> + <h3>{{__ "Send a Sample Request"}}</h3> <form class="form-horizontal"> <fieldset> - <div class="form-group"> - <label class="col-md-3 control-label" for="{{../id}}-sample-request-url"></label> - <div class="input-group"> - <input id="{{../id}}-sample-request-url" type="text" class="form-control sample-request-url" value="{{article.sampleRequest.0.url}}" /> - <span class="input-group-addon">{{__ "url"}}</span> - </div> + <div class="form-group"> + <label class="col-md-3 control-label" for="{{../id}}-sample-request-url">URL</label> + <div class="input-group"> + <span class="input-group-addon">{{__ "url"}}</span> + <input id="{{../id}}-sample-request-url" type="url" class="form-control sample-request-url" value="{{article.sampleRequest.0.url}}" /> </div> + </div> {{#if article.header}} {{#if article.header.fields}} <h3>{{__ "Headers"}}</h3> {{#each article.header.fields}} - <h4><input type="checkbox" data-sample-request-header-group-id="sample-request-header-{{@index}}" name="{{../id}}-sample-request-header" value="{{@index}}" class="sample-request-header sample-request-switch" checked />{{__ @key}}</h4> <div class="{{../id}}-sample-request-header-fields"> {{#each this}} <div class="form-group"> <label class="col-md-3 control-label" for="sample-request-header-field-{{field}}">{{field}}</label> <div class="input-group"> - <input type="text" placeholder="{{field}}" value="{{defaultValue}}" id="sample-request-header-field-{{field}}" class="form-control sample-request-header" data-sample-request-header-name="{{field}}" data-sample-request-header-group="sample-request-header-{{@../index}}"> <span class="input-group-addon">{{{type}}}</span> + <input type="text" id="sample-request-header-field-{{field}}" + class="form-control sample-request-input" + value="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + placeholder="{{#if defaultValue}}{{ defaultValue }}{{else}}{{field}}{{/if}}" + data-family="header" + data-name="{{field}}" + data-group="{{@../index}}"> </div> </div> {{/each}} @@ -275,29 +422,54 @@ {{#if article.parameter.fields}} <h3>{{__ "Parameters"}}</h3> {{#each article.parameter.fields}} - <h4><input type="checkbox" data-sample-request-param-group-id="sample-request-param-{{@index}}" name="{{../id}}-sample-request-param" value="{{@index}}" class="sample-request-param sample-request-switch" checked/>{{__ @key}} + <div class="col-md-3"> <select name="{{../id}}-sample-header-content-type" class="{{../id}}-sample-request-param-select sample-header-content-type sample-header-content-type-switch"> - <option value="undefined" selected>ajax-auto</option> - <option value="body-json" >body/json</option> - <option value="body-form-data" >body/form-data</option> + <option value="auto" selected>ajax-auto</option> + <option value="json" >json</option> + <option value="form-data" >form-data</option> </select> - </h4> + </div> + <div class="{{../id}}-sample-request-param-body {{../id}}-sample-header-content-type-body hide"> <div class="form-group"> <div class="input-group"> - <textarea id="sample-request-body-json" class="form-control sample-request-body" data-sample-request-body-group="sample-request-param-{{@./index}}" rows="6" style="OVERFLOW: visible" {{#if optional}}data-sample-request-param-optional="true"{{/if}}></textarea> <div class="input-group-addon">json</div> + <textarea id="sample-request-body-json" class="form-control sample-request-body" data-sample-request-body-group="sample-request-param-{{@./index}}" rows="6" style="OVERFLOW: visible" {{#if optional}}data-sample-request-param-optional="true"{{/if}}></textarea> </div> </div> </div> <div class="{{../id}}-sample-request-param-fields {{../id}}-sample-header-content-type-fields"> {{#each this}} <div class="form-group"> + {{#ifNotObject type}} <label class="col-md-3 control-label" for="sample-request-param-field-{{field}}">{{field}}</label> <div class="input-group"> - <input id="sample-request-param-field-{{field}}" type="{{setInputType type}}" placeholder="{{field}}" class="form-control sample-request-param" data-sample-request-param-name="{{field}}" data-sample-request-param-group="sample-request-param-{{@../index}}" {{#if optional}}data-sample-request-param-optional="true"{{/if}}> <div class="input-group-addon">{{{type}}}</div> + {{#if allowedValues}} + <div class="input-group-addon sample-request-select"> + <select class="form-control" data-name="{{dot2bracket this}}" data-family="query" data-group="{{@../index}}" {{#if optional}}data-optional="true"{{/if}}> + <option value="" class="empty"><{{__ "No value"}}></option> + {{#each allowedValues}} + <option {{#ifCond ../defaultValue '===' this}} selected {{/ifCond}}value="{{{removeDblQuotes this}}}">{{{removeDblQuotes this}}}</option> + {{/each}} + </select> + </div> + <input class="invisible"> + {{else}} + <div class="sample-request-input-{{type}}-container"><div> + <input id="sample-request-param-field-{{field}}" + class="{{#ifCond type '!==' 'Boolean'}}form-control{{/ifCond}} sample-request-param" + type="{{setInputType type}}" + value="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + placeholder="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + data-name="{{dot2bracket this}}" + data-family="query" + data-group="{{@../index}}" + {{#if optional}}data-optional="true"{{/if}}> + </div></div> + {{/if}} </div> + {{/ifNotObject}} </div> {{/each}} </div> @@ -305,27 +477,135 @@ {{/if}} {{/if}} + {{#if article.query}} + <h3>{{__ "Query Parameters"}}</h3> + <div class="{{../id}}-sample-request-param-fields {{../id}}-sample-header-content-type-fields"> + {{#each article.query}} + <div class="form-group"> + {{#ifNotObject type}} + <label class="col-md-3 control-label" for="sample-request-param-field-{{field}}">{{field}}{{#if optional}} ({{__ "optional"}}){{/if}}</label> + <div class="input-group col-md-6"> + <div class="input-group-addon">{{{type}}}</div> + {{#if allowedValues}} + <div class="input-group-addon sample-request-select"> + <select class="form-control" data-name="{{dot2bracket this}}" data-family="query" data-group="{{@../index}}" {{#if optional}}data-optional="true"{{/if}}> + <option value="" class="empty"><{{__ "No value"}}></option> + {{#each allowedValues}} + <option {{#ifCond ../defaultValue '===' this}} selected {{/ifCond}}value="{{{removeDblQuotes this}}}">{{{removeDblQuotes this}}}</option> + {{/each}} + </select> + </div> + <input class="invisible"> + {{else}} + <div class="sample-request-input-{{type}}-container"><div> + <input id="sample-request-param-field-{{field}}" + class="{{#ifCond type '!==' 'Boolean'}}form-control{{/ifCond}} sample-request-input" + type="{{setInputType type}}" + value="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + placeholder="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + data-name="{{dot2bracket this}}" + data-family="query" + data-group="{{@../index}}" + {{#if optional}}data-optional="true"{{/if}}> + </div></div> + {{/if}} + </div> + {{/ifNotObject}} + </div> + {{/each}} + </div> + {{/if}} + + {{#if article.body}} + <h3>{{__ "Body"}}</h3> + + <div class="col-md-3"> + <label for="body-content-type-{{this.id}}">{{__ "Content-Type"}}</label> + <select id="body-content-type-{{this.id}}" data-id="{{this.id}}" class="sample-request-content-type-switch form-control"> + <option value="body-json" selected>json</option> + <option value="body-form-data">form-data</option> + </select> + </div> + + <div class="col-md-9" id="sample-request-body-json-input-{{this.id}}"> + <div class="form-group"> + <div class="input-group"> + <div class="input-group-addon">json</div> + <textarea class="form-control sample-request-input" rows="6" + data-family="body-json" + data-name={{"body"}} + data-content-type="json" + {{#if optional}}data-optional="true"{{/if}}>{{body2json article.body}}</textarea> + </div> + </div> + </div> + + <div hidden class="col-md-9" id="sample-request-body-form-input-{{this.id}}"> + {{#each article.body}} + <div class="form-group"> + {{#ifNotObject type}} + <label class="col-md-3 control-label" for="sample-request-param-field-{{field}}">{{field}}</label> + <div class="input-group"> + <div class="input-group-addon">{{{type}}}</div> + {{#if allowedValues}} + <div class="input-group-addon sample-request-select"> + <select class="form-control" data-name="{{dot2bracket this}}" data-family="body" data-group="{{@../index}}" {{#if optional}}data-optional="true"{{/if}}> + <option value="" class="empty"><{{__ "No value"}}></option> + {{#each allowedValues}} + <option {{#ifCond ../defaultValue '===' this}} selected {{/ifCond}}value="{{{removeDblQuotes this}}}">{{{removeDblQuotes this}}}</option> + {{/each}} + </select> + </div> + <input class="invisible"> + {{else}} + <div class="sample-request-input-{{type}}-container"><div> + <input id="sample-request-param-field-{{field}}" + class="{{#ifCond type '!==' 'Boolean'}}form-control{{/ifCond}} sample-request-input" + type="{{setInputType type}}" + value="{{#ifCond type '!==' 'Boolean'}}{{#if defaultValue}}{{ defaultValue }}{{/if}}{{/ifCond}}" + {{#if checked}}checked{{/if}} + placeholder="{{#if defaultValue}}{{ defaultValue }}{{/if}}" + data-family="body" + data-name="{{dot2bracket this}}" + data-content-type="form" + {{#if optional}}data-optional="true"{{/if}}> + </div></div> + {{/if}} + </div> + {{/ifNotObject}} + </div> + {{/each}} + </div> + {{/if}} + <div class="form-group"> <div class="controls pull-right"> - <button class="btn btn-primary sample-request-send" data-sample-request-type="{{article.type}}">{{__ "Send"}}</button> + <button class="btn btn-primary bg-primary sample-request-send" data-type="{{article.type}}">{{__ "Send"}}</button> + <button class="btn btn-danger bg-red sample-request-clear" data-type="{{article.type}}">{{__ "Reset"}}</button> </div> </div> - <div class="form-group sample-request-response" style="display: none;"> + <div class="form-group sample-request-response" hidden> <h3> {{__ "Response"}} <button class="btn btn-default btn-xs pull-right sample-request-clear">X</button> </h3> - <pre class="prettyprint language-json" data-type="json"><code class="sample-request-response-json"></code></pre> + <pre + data-type="json" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-json sample-request-response-json"></code></pre> </div> </fieldset> </form> - {{/if}} + </div> + {{/if}} </script> <script id="template-compare-article" type="text/x-handlebars-template"> <article id="api-{{article.group}}-{{article.name}}-{{article.version}}" {{#if hidden}}class="hide"{{/if}} data-group="{{article.group}}" data-name="{{article.name}}" data-version="{{article.version}}" data-compare-version="{{compare.version}}"> <div class="pull-left"> - <h1>{{underscoreToSpace article.group}} - {{{showDiff article.title compare.title}}}</h1> + <h1>{{underscoreToSpace article.groupTitle}} | {{{showDiff article.title compare.title}}}</h1> </div> <div class="pull-right"> @@ -355,27 +635,34 @@ {{/if}} {{/if}} - <pre class="prettyprint language-html" data-type="{{toLowerCase article.type}}"><code>{{{showDiff article.url compare.url}}}</code></pre> + <span class="method meth-{{toLowerCase compare.type}}">{{compare.type}}</span> + <pre + data-type="{{toLowerCase article.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + class="language-html" + >{{{showDiff article.url compare.url}}}</pre> {{subTemplate "article-compare-permission" article=article compare=compare}} - <ul class="nav nav-tabs nav-tabs-examples"> + <ul class="nav nav-tabs nav-tabs-examples" role="tablist"> {{#each_compare_title article.examples compare.examples}} {{#if typeSame}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#compare-examples-{{../../article.id}}-{{index}}">{{{showDiff source.title compare.title}}}</a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#compare-examples-{{../article.id}}-{{index}}" role="tab" data-toggle="tab">{{{showDiff source.title compare.title}}}</a> </li> {{/if}} {{#if typeIns}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#compare-examples-{{../../article.id}}-{{index}}"><ins>{{{source.title}}}</ins></a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#compare-examples-{{../article.id}}-{{index}}"><ins>{{{source.title}}}</ins></a> </li> {{/if}} {{#if typeDel}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#compare-examples-{{../../article.id}}-{{index}}"><del>{{{compare.title}}}</del></a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#compare-examples-{{../article.id}}-{{index}}"><del>{{{compare.title}}}</del></a> </li> {{/if}} {{/each_compare_title}} @@ -385,27 +672,45 @@ {{#each_compare_title article.examples compare.examples}} {{#if typeSame}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{source.type}}"><code>{{{showDiff source.content compare.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{source.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-diff-{{source.type}} diff-highlight">{{{showDiff source.content compare.content "code"}}}</code></pre> </div> {{/if}} {{#if typeIns}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{source.type}}"><code>{{{source.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{source.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{source.type}}">{{{source.content}}}</code></pre> </div> {{/if}} {{#if typeDel}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{compare.type}}"><code>{{{compare.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{compare.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{source.type}}">{{{compare.content}}}</code></pre> </div> {{/if}} {{/each_compare_title}} </div> + {{subTemplate "article-compare-param-block" source=article.header compare=compare.header _hasType=_hasTypeInHeaderFields section="header"}} {{subTemplate "article-compare-param-block" source=article.parameter compare=compare.parameter _hasType=_hasTypeInParameterFields section="parameter"}} + {{subTemplate "article-compare-query-block" source=article.query compare=compare.query _hasType=_hasTypeInParameterFields section="query"}} + {{subTemplate "article-compare-body-block" source=article.body compare=compare.body _hasType=_hasTypeInParameterFields section="body"}} {{subTemplate "article-compare-param-block" source=article.success compare=compare.success _hasType=_hasTypeInSuccessFields section="success"}} {{subTemplate "article-compare-param-block" source=article.error compare=compare.error _col1="Name" _hasType=_hasTypeInErrorFields section="error"}} @@ -530,23 +835,23 @@ {{/each_compare_keys}} {{#if source.examples}} - <ul class="nav nav-tabs nav-tabs-examples"> + <ul class="nav nav-tabs nav-tabs-examples" role="tablist"> {{#each_compare_title source.examples compare.examples}} {{#if typeSame}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#{{../../section}}-compare-examples-{{../../article.id}}-{{index}}">{{{showDiff source.title compare.title}}}</a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#{{../section}}-compare-examples-{{../article.id}}-{{index}}" role="tab" data-toggle="tab">{{{showDiff source.title compare.title}}}</a> </li> {{/if}} {{#if typeIns}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#{{../../section}}-compare-examples-{{../../article.id}}-{{index}}"><ins>{{{source.title}}}</ins></a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#{{../section}}-compare-examples-{{../article.id}}-{{index}}"><ins>{{{source.title}}}</ins></a> </li> {{/if}} {{#if typeDel}} - <li{{#if_eq index compare=0}} class="active"{{/if_eq}}> - <a href="#{{../../section}}-compare-examples-{{../../article.id}}-{{index}}"><del>{{{compare.title}}}</del></a> + <li{{#ifCond index '==' 0}} class="active"{{/ifCond}}> + <a href="#{{../section}}-compare-examples-{{../article.id}}-{{index}}"><del>{{{compare.title}}}</del></a> </li> {{/if}} {{/each_compare_title}} @@ -556,20 +861,35 @@ {{#each_compare_title source.examples compare.examples}} {{#if typeSame}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="{{../../section}}-compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{source.type}}"><code>{{{showDiff source.content compare.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="{{../section}}-compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{source.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-diff-{{source.type}} diff-highlight">{{{showDiff source.content compare.content "code"}}}</code></pre> </div> {{/if}} {{#if typeIns}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="{{../../section}}-compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{source.type}}"><code>{{{source.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="{{../section}}-compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{source.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{source.type}}">{{{source.content}}}</code></pre> </div> {{/if}} {{#if typeDel}} - <div class="tab-pane{{#if_eq index compare=0}} active{{/if_eq}}" id="{{../../section}}-compare-examples-{{../../article.id}}-{{index}}"> - <pre class="prettyprint language-{{source.type}}" data-type="{{compare.type}}"><code>{{{compare.content}}}</code></pre> + <div class="tab-pane{{#ifCond index '==' 0}} active{{/ifCond}}" id="{{../section}}-compare-examples-{{../article.id}}-{{index}}"> + <pre + data-type="{{compare.type}}" + data-prismjs-copy="{{__ "Copy"}}" + data-prismjs-copy-error="{{__ "Press Ctrl+C to copy"}}" + data-prismjs-copy-success="{{__ "copied!"}}" + ><code class="language-{{source.type}}">{{{compare.content}}}</code></pre> </div> {{/if}} {{/each_compare_title}} @@ -578,13 +898,49 @@ {{/if}} </script> +<script id="template-article-compare-query-block" type="text/x-handlebars-template"> + {{#if article.query}} + <h2>{{__ "Query Parameter(s)"}}</h2> + <table class="table table-hover"> + <thead> + <tr> + <th style="width: 30%">{{#if ../_col1}}{{__ ../_col1}}{{else}}{{__ "Field"}}{{/if}}</th> + {{#unless this.Type compare=null}} + <th style="width: 10%">{{__ "Type"}}</th> + {{/unless}} + <th style="width: {{#if ../_hasType}}60%{{else}}70%{{/if}}">{{__ "Description"}}</th> + </tr> + </thead> + {{subTemplate "article-compare-param-block-body" source=source compare=compare _hasType=this.type}} + </table> + {{/if}} +</script> + +<script id="template-article-compare-body-block" type="text/x-handlebars-template"> + {{#if article.body}} + <h2>{{__ "Request Body"}}</h2> + <table class="table table-hover"> + <thead> + <tr> + <th style="width: 30%">{{#if ../_col1}}{{__ ../_col1}}{{else}}{{__ "Field"}}{{/if}}</th> + {{#unless this.Type compare=null}} + <th style="width: 10%">{{__ "Type"}}</th> + {{/unless}} + <th style="width: {{#if ../_hasType}}60%{{else}}70%{{/if}}">{{__ "Description"}}</th> + </tr> + </thead> + {{subTemplate "article-compare-param-block-body" source=source compare=compare _hasType=this.type}} + </table> + {{/if}} +</script> + <script id="template-article-compare-param-block-body" type="text/x-handlebars-template"> <tbody> {{#each_compare_field source compare}} {{#if typeSame}} <tr> <td class="code"> - {{{splitFill source.field "." " "}}} + {{{nestObject source}}} {{#if source.optional}} {{#if compare.optional}} <span class="label label-optional">{{__ "optional"}}</span> {{else}} <span class="label label-optional label-ins">{{__ "optional"}}</span> @@ -617,7 +973,7 @@ {{#if typeIns}} <tr class="ins"> <td class="code"> - {{{splitFill source.field "." " "}}} + {{{nestObject source}}} {{#if source.optional}} <span class="label label-optional label-ins">{{__ "optional"}}</span>{{/if}} </td> @@ -637,7 +993,7 @@ {{#if typeDel}} <tr class="del"> <td class="code"> - {{{splitFill compare.field "." " "}}} + {{{nestObject compare}}} {{#if compare.optional}} <span class="label label-optional label-del">{{__ "optional"}}</span>{{/if}} </td> @@ -659,9 +1015,9 @@ </script> <div class="container-fluid"> - <div class="row"> - <div id="sidenav" class="span2"></div> - <div id="content"> + <div class="row row-offcanvas row-offcanvas-left"> + <div id="sidenav" class="col-sm-3 col-lg-2"></div> + <div id="content" class="col-sm-9 col-lg-10"> <div id="project"></div> <div id="header"></div> <div id="sections"></div> @@ -686,6 +1042,6 @@ </div> </div> -<script data-main="main.js" src="vendor/require.min.js"></script> +<script src="assets/main.bundle.js?v=1686163607729"></script> </body> </html> diff --git a/jams-server/src/main/java/net/jami/jams/server/Server.java b/jams-server/src/main/java/net/jami/jams/server/Server.java index 32c29703..7b9341d6 100644 --- a/jams-server/src/main/java/net/jami/jams/server/Server.java +++ b/jams-server/src/main/java/net/jami/jams/server/Server.java @@ -51,14 +51,15 @@ import java.net.URI; import java.util.concurrent.atomic.AtomicBoolean; @Slf4j -//In order to make this "stoppable" to simply, I turned the server itself into a thread. +// In order to make this "stoppable" to simply, I turned the server itself into +// a thread. public class Server { public final static AtomicBoolean isInstalled = new AtomicBoolean(false); public final static AtomicBoolean activated = new AtomicBoolean(false); public static DataStore dataStore; - //This one gets loaded via JAR, to make it more flexible. + // This one gets loaded via JAR, to make it more flexible. public static CertificateAuthority certificateAuthority; public static AuthenticationModule userAuthenticationModule; public static NameServer nameServer; @@ -67,12 +68,12 @@ public class Server { public static final UpdateInterface updateInterface = new UpdateInterface(); public static JAMSUpdater appUpdater = new JAMSUpdater(new AtomicBoolean(false)); - public static void main(String[] args) { - //This is a fix to drop old cached stuff from the tomcat classloader. + // This is a fix to drop old cached stuff from the tomcat classloader. ClassPool.getDefault().clearImportedPackages(); ScopedServletAnnotationScanner scanner = new ScopedServletAnnotationScanner(); - scanner.processClasses(new java.io.File(Server.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getName()); + scanner.processClasses( + new java.io.File(Server.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getName()); switch (args.length) { case 1: tomcatLauncher = new TomcatLauncher(Integer.parseInt(args[0])); @@ -86,34 +87,52 @@ public class Server { break; } - //Pre-load the libraries we should pre-load. + // Pre-load the libraries we should pre-load. LibraryLoader.loadlibs("libs", Server.class); - //Step 1: Create the data store. + // Step 1: Create the data store. dataStore = new DataStore("jdbc:derby:jams;create=true"); + // Create image folder if it doesn't exist. + File imagesFolder = new File("." + File.separator + "images"); + if (!imagesFolder.exists()) { + boolean created = imagesFolder.mkdirs(); + if (created) { + log.info("Created images folder"); + } else { + log.error("Failed to create images folder"); + System.exit(-1); + } + } + isInstalled.set(new File(System.getProperty("user.dir") + File.separator + "config.json").exists()); log.info("Server is already installed: " + isInstalled.get()); ServerSettings serverSettings = null; if (isInstalled.get()) { try { - InputStream path = new FileInputStream(new File(System.getProperty("user.dir") + File.separator + "config.json")); + InputStream path = new FileInputStream( + new File(System.getProperty("user.dir") + File.separator + "config.json")); serverSettings = JsonIterator.deserialize(path.readAllBytes(), ServerSettings.class); - certificateAuthority = CryptoEngineLoader.loadCertificateAuthority(serverSettings.getCaConfiguration(), dataStore); + certificateAuthority = CryptoEngineLoader.loadCertificateAuthority(serverSettings.getCaConfiguration(), + dataStore); userAuthenticationModule = AuthModuleLoader.loadAuthenticationModule(dataStore, certificateAuthority); if (serverSettings.getLdapConfiguration() != null) userAuthenticationModule.attachAuthSource(AuthenticationSourceType.LDAP, - serverSettings.getLdapConfiguration() - ); + serverSettings.getLdapConfiguration()); if (serverSettings.getActiveDirectoryConfiguration() != null) { userAuthenticationModule.attachAuthSource(AuthenticationSourceType.AD, serverSettings.getActiveDirectoryConfiguration()); } if (serverSettings.getLocalDirectoryConfiguration() != null) { - LocalAuthSettings settings = JsonIterator.deserialize(serverSettings.getLocalDirectoryConfiguration(), LocalAuthSettings.class); - if(settings.getPublicNames()) nameServer = new PublicNameServer(settings.getPublicNameServer()); - else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI()); - } - else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI()); + LocalAuthSettings settings = JsonIterator + .deserialize(serverSettings.getLocalDirectoryConfiguration(), LocalAuthSettings.class); + if (settings.getPublicNames()) + nameServer = new PublicNameServer(settings.getPublicNameServer()); + else + nameServer = new LocalNameServer(dataStore, userAuthenticationModule, + serverSettings.getServerPublicURI()); + } else + nameServer = new LocalNameServer(dataStore, userAuthenticationModule, + serverSettings.getServerPublicURI()); licenseService.loadLicense(); log.info("All services are UP and ready for use..."); @@ -125,27 +144,29 @@ public class Server { userAuthenticationModule = AuthModuleLoader.loadAuthenticationModule(dataStore, certificateAuthority); log.info("Started server with empty modules waiting for setup..."); } - //Now pop-up the GUI - //Here we need to do a small spin-wait because keys may be slow to generate. + // Now pop-up the GUI + // Here we need to do a small spin-wait because keys may be slow to generate. try { startGUI(); + log.info("Server is now running!"); + } catch (Exception e) { + log.error("Could not start GUI with error {}", e.getMessage()); } - catch (Exception e){ - log.error("Could not start GUI with error {}",e.getMessage()); - } - } - public static void startGUI() throws Exception{ + public static void startGUI() throws Exception { boolean ready = false; - while(!ready){ - if(userAuthenticationModule.getAuthModulePubKey() != null){ + while (!ready) { + log.info("Waiting for authentication module to be ready..."); + if (userAuthenticationModule.getAuthModulePubKey() != null) { ready = true; } } + log.info("Authentication module is ready!"); if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(new URI(tomcatLauncher.getTomcat().getConnector().getScheme() + "://localhost:" + tomcatLauncher.getTomcat().getService().findConnectors()[0].getPort())); + Desktop.getDesktop().browse(new URI(tomcatLauncher.getTomcat().getConnector().getScheme() + "://localhost:" + + tomcatLauncher.getTomcat().getService().findConnectors()[0].getPort())); } else { log.info("There is no graphical interface on this system - please connect remotely!"); } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/contacts/ContactServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/contacts/ContactServlet.java index 9d729168..52a2ffa3 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/contacts/ContactServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/contacts/ContactServlet.java @@ -78,7 +78,8 @@ public class ContactServlet extends HttpServlet { * @apiGroup Contacts * * @apiParam {body} Contact JSON representation of the contact object - * @apiParamExample {json} Request-Example: { "uri": "jami://7e3ab29383" } + * @apiParamExample {json} Request-Example: + * { "uri": "jami://7e3ab29383" } * * @apiSuccess (200) {null} null successfully added contact * @apiError (500) {null} null contact could not be successfully added diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/PolicyServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/PolicyServlet.java index 703b06ed..9893eae4 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/PolicyServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/PolicyServlet.java @@ -6,6 +6,7 @@ import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import net.jami.jams.common.annotations.JsonContent; import net.jami.jams.common.annotations.ScopedServletMethod; import net.jami.jams.common.dao.StatementElement; @@ -20,6 +21,7 @@ import java.util.Scanner; import static net.jami.jams.server.Server.dataStore; +@Slf4j @WebServlet("/api/admin/policy") public class PolicyServlet extends HttpServlet { @@ -37,8 +39,14 @@ public class PolicyServlet extends HttpServlet { StatementList statementList = new StatementList(); StatementElement st = new StatementElement("name", "=", name, ""); statementList.addStatement(st); - resp.getOutputStream().write(JsonStream.serialize(dataStore.getPolicyDao().getObjects(statementList).get(0)).getBytes()); - resp.setStatus(200); + policies = dataStore.getPolicyDao().getObjects(statementList); + if (!policies.isEmpty()) { + resp.getOutputStream().write(JsonStream.serialize(policies.get(0)).getBytes()); + resp.setStatus(200); + } else { + resp.setStatus(404); // 404 status code for Not Found + resp.getWriter().write("{\"message\": \"No policy found.\"}"); + } } else { policies = dataStore.getPolicyDao().getObjects(null); resp.getOutputStream().write(JsonStream.serialize(policies).getBytes()); diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java index a1a3a947..739b1d9d 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java @@ -73,7 +73,8 @@ public class ContactServlet extends HttpServlet { * @apiGroup Contacts * * @apiParam {body} Contact JSON representation of the contact object - * @apiParamExample {json} Request-Example: { "uri": "jami://7e3ab29383" } + * @apiParamExample {json} Request-Example: + * { "uri": "jami://7e3ab29383" } * * @apiSuccess (200) {null} null successfully added contact * @apiError (500) {null} null contact could not be successfully added diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/image/FileHandlerServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/image/FileHandlerServlet.java new file mode 100644 index 00000000..325d6dbe --- /dev/null +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/image/FileHandlerServlet.java @@ -0,0 +1,165 @@ +package net.jami.jams.server.servlets.api.image; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.MultipartConfig; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Part; +import lombok.extern.slf4j.Slf4j; + +@MultipartConfig +@Slf4j +@WebServlet("/api/image/filehandler/*") +public class FileHandlerServlet extends HttpServlet { + + private static final String IMAGES_DIR = "images"; + private static final String SAVE_DIR = ".." + File.separator + ".." + File.separator + ".." + File.separator + ".." + + File.separator + ".." + File.separator + IMAGES_DIR; + + /** + * @apiVersion 1.0.0 + * @api {post} /api/image/filehandler/<blueprintName>/<imageType> Upload an + * image + * @apiName postImage + * @apiGroup Images + * + * @apiParam {param} blueprintName The name of the blueprint + * @apiParam {param} imageType 'logo' or 'background' + * @apiParam {body} image The new logo image or background image + * + * @apiSuccess (200) {null} null successfully uploaded image + * @apiError (400) {null} null missing path info + * @apiError (500) {null} null contact could not be successfully added + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + String pathInfo = request.getPathInfo(); + + if (pathInfo == null || pathInfo.equals("/")) { + // Handle missing path info + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String[] pathParts = pathInfo.split("/"); + + if (pathParts.length != 3) { + // Handle wrong number of path elements + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String blueprintName = pathParts[1]; + String imageType = pathParts[2]; + + File imagesFolder = new File(IMAGES_DIR + File.separator + blueprintName); + if (!imagesFolder.exists()) { + + imagesFolder.mkdirs(); + + File imagesFolderLogo = new File(IMAGES_DIR + File.separator + blueprintName + File.separator + "logo"); + File imagesFolderBackground = new File( + IMAGES_DIR + File.separator + blueprintName + File.separator + "background"); + + imagesFolderLogo.mkdirs(); + imagesFolderBackground.mkdirs(); + } + + Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file"> + String fileName = filePart.getSubmittedFileName(); + + for (Part part : request.getParts()) { + part.write(SAVE_DIR + File.separator + blueprintName + File.separator + imageType + File.separator + + fileName); + } + + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("File upload was successful."); + } catch (Exception e) { + log.error("Error during file upload: " + e.getMessage()); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("Error during file upload: " + e.getMessage()); + } + } + + /** + * @apiVersion 1.0.0 + * @api {get} /api/image/filehandler/<blueprintName>/<imageType>/<fileName> Get + * an image + * @apiName getImage + * @apiGroup Images + * + * @apiParam {param} blueprintName The name of the blueprint + * @apiParam {param} imageType 'logo' or 'background' + * @apiParam {param} fileName The name of the file + * + * @apiSuccess (200) {null} null successfully got image + * @apiError (400) {null} null missing path info + * @apiError (404) {null} null image not found + */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + String pathInfo = request.getPathInfo(); + + if (pathInfo == null || pathInfo.equals("/")) { + // Handle missing path info + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String[] pathParts = pathInfo.split("/"); + + if (pathParts.length != 4) { + // Handle wrong number of path elements + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String blueprintName = pathParts[1]; + String imageType = pathParts[2]; + String fileName = pathParts[3]; + + File file = new File(IMAGES_DIR + File.separator + blueprintName + File.separator + imageType + + File.separator + fileName); + + String mimeType = Files.probeContentType(Paths.get(file.getAbsolutePath())); + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + + response.setContentType(mimeType); + response.addHeader("Content-Disposition", "attachment; filename=" + fileName); + + OutputStream out = response.getOutputStream(); + FileInputStream in = new FileInputStream(file); + byte[] buffer = new byte[4096]; + int length; + + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + in.close(); + out.flush(); + } catch (FileNotFoundException e) { + log.error("FileHandlerServlet: Error while processing request", e); + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } catch (IOException e) { + log.error("FileHandlerServlet: Error while processing request", e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/CorsFilter.java b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/CorsFilter.java index 9325a9a0..56d5af3a 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/CorsFilter.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/CorsFilter.java @@ -1,8 +1,21 @@ package net.jami.jams.server.servlets.filters; +import java.io.IOException; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.annotation.WebInitParam; +import jakarta.servlet.http.HttpServletResponse; -@WebFilter(urlPatterns = {"*"}, initParams={@WebInitParam(name = "cors.allowed.origins",value = "*")}) +// @WebFilter(urlPatterns = { "*" }, initParams = { @WebInitParam(name = "cors.allowed.origins", value = "*") }) +@WebFilter(urlPatterns = { "*" }, initParams = { + @WebInitParam(name = "cors.allowed.origins", value = "*"), + @WebInitParam(name = "cors.allowed.methods", value = "PUT, POST, GET, OPTIONS, DELETE"), + @WebInitParam(name = "cors.allowed.headers", value = "Content-Type, Authorization"), + @WebInitParam(name = "cors.preflight.maxage", value = "3600") +}) public class CorsFilter extends org.apache.catalina.filters.CorsFilter { } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/FilterUtils.java b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/FilterUtils.java index 6f3b4dcd..abc69c17 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/FilterUtils.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/FilterUtils.java @@ -87,7 +87,9 @@ public class FilterUtils { StatementList statementList = new StatementList(); StatementElement statementElement = new StatementElement("username", "=", token.getJWTClaimsSet().getSubject(), ""); statementList.addStatement(statementElement); + log.info("Getting user from database"); User user = dataStore.getUserDao().getObjects(statementList).get(0); + log.info("User retrieved from database: {}", user); if(!user.getAccessLevelName().equals("ADMIN") && certificateAuthority.getLatestCRL().get() != null) { if(certificateAuthority.getLatestCRL().get().getRevokedCertificate(user.getCertificate().getSerialNumber()) != null) return false; -- GitLab