implemented more /sample methods
This commit is contained in:
parent
20f57acd2a
commit
63a5f5ebd1
11
.idea/dataSources.xml
Normal file
11
.idea/dataSources.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="@localhost" uuid="46f112fc-d60d-4217-873f-f5ffea06180c">
|
||||||
|
<driver-ref>mongo</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>com.dbschema.MongoJdbcDriver</jdbc-driver>
|
||||||
|
<jdbc-url>mongodb://localhost:27017</jdbc-url>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
458
.idea/dbnavigator.xml
Normal file
458
.idea/dbnavigator.xml
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DBNavigator.Project.DataEditorManager">
|
||||||
|
<record-view-column-sorting-type value="BY_INDEX" />
|
||||||
|
<value-preview-text-wrapping value="true" />
|
||||||
|
<value-preview-pinned value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.DataExportManager">
|
||||||
|
<export-instructions>
|
||||||
|
<create-header value="true" />
|
||||||
|
<quote-values-containing-separator value="true" />
|
||||||
|
<quote-all-values value="false" />
|
||||||
|
<value-separator value="" />
|
||||||
|
<file-name value="" />
|
||||||
|
<file-location value="" />
|
||||||
|
<scope value="GLOBAL" />
|
||||||
|
<destination value="FILE" />
|
||||||
|
<format value="EXCEL" />
|
||||||
|
<charset value="windows-1252" />
|
||||||
|
</export-instructions>
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.DatabaseBrowserManager">
|
||||||
|
<autoscroll-to-editor value="false" />
|
||||||
|
<autoscroll-from-editor value="true" />
|
||||||
|
<show-object-properties value="true" />
|
||||||
|
<loaded-nodes />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.DatabaseFileManager">
|
||||||
|
<open-files />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.EditorStateManager">
|
||||||
|
<last-used-providers />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.MethodExecutionManager">
|
||||||
|
<method-browser />
|
||||||
|
<execution-history>
|
||||||
|
<group-entries value="true" />
|
||||||
|
<execution-inputs />
|
||||||
|
</execution-history>
|
||||||
|
<argument-values-cache />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.ObjectDependencyManager">
|
||||||
|
<last-used-dependency-type value="INCOMING" />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.ObjectQuickFilterManager">
|
||||||
|
<last-used-operator value="EQUAL" />
|
||||||
|
<filters />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
|
||||||
|
<recently-used-interfaces />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.Settings">
|
||||||
|
<connections />
|
||||||
|
<browser-settings>
|
||||||
|
<general>
|
||||||
|
<display-mode value="TABBED" />
|
||||||
|
<navigation-history-size value="100" />
|
||||||
|
<show-object-details value="false" />
|
||||||
|
</general>
|
||||||
|
<filters>
|
||||||
|
<object-type-filter>
|
||||||
|
<object-type name="SCHEMA" enabled="true" />
|
||||||
|
<object-type name="USER" enabled="true" />
|
||||||
|
<object-type name="ROLE" enabled="true" />
|
||||||
|
<object-type name="PRIVILEGE" enabled="true" />
|
||||||
|
<object-type name="CHARSET" enabled="true" />
|
||||||
|
<object-type name="TABLE" enabled="true" />
|
||||||
|
<object-type name="VIEW" enabled="true" />
|
||||||
|
<object-type name="MATERIALIZED_VIEW" enabled="true" />
|
||||||
|
<object-type name="NESTED_TABLE" enabled="true" />
|
||||||
|
<object-type name="COLUMN" enabled="true" />
|
||||||
|
<object-type name="INDEX" enabled="true" />
|
||||||
|
<object-type name="CONSTRAINT" enabled="true" />
|
||||||
|
<object-type name="DATASET_TRIGGER" enabled="true" />
|
||||||
|
<object-type name="DATABASE_TRIGGER" enabled="true" />
|
||||||
|
<object-type name="SYNONYM" enabled="true" />
|
||||||
|
<object-type name="SEQUENCE" enabled="true" />
|
||||||
|
<object-type name="PROCEDURE" enabled="true" />
|
||||||
|
<object-type name="FUNCTION" enabled="true" />
|
||||||
|
<object-type name="PACKAGE" enabled="true" />
|
||||||
|
<object-type name="TYPE" enabled="true" />
|
||||||
|
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
|
||||||
|
<object-type name="ARGUMENT" enabled="true" />
|
||||||
|
<object-type name="DIMENSION" enabled="true" />
|
||||||
|
<object-type name="CLUSTER" enabled="true" />
|
||||||
|
<object-type name="DBLINK" enabled="true" />
|
||||||
|
</object-type-filter>
|
||||||
|
</filters>
|
||||||
|
<sorting>
|
||||||
|
<object-type name="COLUMN" sorting-type="NAME" />
|
||||||
|
<object-type name="FUNCTION" sorting-type="NAME" />
|
||||||
|
<object-type name="PROCEDURE" sorting-type="NAME" />
|
||||||
|
<object-type name="ARGUMENT" sorting-type="POSITION" />
|
||||||
|
</sorting>
|
||||||
|
<default-editors>
|
||||||
|
<object-type name="VIEW" editor-type="SELECTION" />
|
||||||
|
<object-type name="PACKAGE" editor-type="SELECTION" />
|
||||||
|
<object-type name="TYPE" editor-type="SELECTION" />
|
||||||
|
</default-editors>
|
||||||
|
</browser-settings>
|
||||||
|
<navigation-settings>
|
||||||
|
<lookup-filters>
|
||||||
|
<lookup-objects>
|
||||||
|
<object-type name="SCHEMA" enabled="true" />
|
||||||
|
<object-type name="USER" enabled="false" />
|
||||||
|
<object-type name="ROLE" enabled="false" />
|
||||||
|
<object-type name="PRIVILEGE" enabled="false" />
|
||||||
|
<object-type name="CHARSET" enabled="false" />
|
||||||
|
<object-type name="TABLE" enabled="true" />
|
||||||
|
<object-type name="VIEW" enabled="true" />
|
||||||
|
<object-type name="MATERIALIZED VIEW" enabled="true" />
|
||||||
|
<object-type name="NESTED TABLE" enabled="false" />
|
||||||
|
<object-type name="COLUMN" enabled="false" />
|
||||||
|
<object-type name="INDEX" enabled="true" />
|
||||||
|
<object-type name="CONSTRAINT" enabled="true" />
|
||||||
|
<object-type name="DATASET TRIGGER" enabled="true" />
|
||||||
|
<object-type name="DATABASE TRIGGER" enabled="true" />
|
||||||
|
<object-type name="SYNONYM" enabled="false" />
|
||||||
|
<object-type name="SEQUENCE" enabled="true" />
|
||||||
|
<object-type name="PROCEDURE" enabled="true" />
|
||||||
|
<object-type name="FUNCTION" enabled="true" />
|
||||||
|
<object-type name="PACKAGE" enabled="true" />
|
||||||
|
<object-type name="TYPE" enabled="true" />
|
||||||
|
<object-type name="TYPE ATTRIBUTE" enabled="false" />
|
||||||
|
<object-type name="ARGUMENT" enabled="false" />
|
||||||
|
<object-type name="DIMENSION" enabled="false" />
|
||||||
|
<object-type name="CLUSTER" enabled="false" />
|
||||||
|
<object-type name="DBLINK" enabled="true" />
|
||||||
|
</lookup-objects>
|
||||||
|
<force-database-load value="false" />
|
||||||
|
<prompt-connection-selection value="true" />
|
||||||
|
<prompt-schema-selection value="true" />
|
||||||
|
</lookup-filters>
|
||||||
|
</navigation-settings>
|
||||||
|
<dataset-grid-settings>
|
||||||
|
<general>
|
||||||
|
<enable-zooming value="true" />
|
||||||
|
<enable-column-tooltip value="true" />
|
||||||
|
</general>
|
||||||
|
<sorting>
|
||||||
|
<nulls-first value="true" />
|
||||||
|
<max-sorting-columns value="4" />
|
||||||
|
</sorting>
|
||||||
|
<tracking-columns>
|
||||||
|
<columnNames value="" />
|
||||||
|
<visible value="true" />
|
||||||
|
<editable value="false" />
|
||||||
|
</tracking-columns>
|
||||||
|
</dataset-grid-settings>
|
||||||
|
<dataset-editor-settings>
|
||||||
|
<text-editor-popup>
|
||||||
|
<active value="false" />
|
||||||
|
<active-if-empty value="false" />
|
||||||
|
<data-length-threshold value="100" />
|
||||||
|
<popup-delay value="1000" />
|
||||||
|
</text-editor-popup>
|
||||||
|
<values-actions-popup>
|
||||||
|
<show-popup-button value="true" />
|
||||||
|
<element-count-threshold value="1000" />
|
||||||
|
<data-length-threshold value="250" />
|
||||||
|
</values-actions-popup>
|
||||||
|
<general>
|
||||||
|
<fetch-block-size value="100" />
|
||||||
|
<fetch-timeout value="30" />
|
||||||
|
<trim-whitespaces value="true" />
|
||||||
|
<convert-empty-strings-to-null value="true" />
|
||||||
|
<select-content-on-cell-edit value="true" />
|
||||||
|
<large-value-preview-active value="true" />
|
||||||
|
</general>
|
||||||
|
<filters>
|
||||||
|
<prompt-filter-dialog value="true" />
|
||||||
|
<default-filter-type value="BASIC" />
|
||||||
|
</filters>
|
||||||
|
<qualified-text-editor text-length-threshold="300">
|
||||||
|
<content-types>
|
||||||
|
<content-type name="Text" enabled="true" />
|
||||||
|
<content-type name="XML" enabled="true" />
|
||||||
|
<content-type name="DTD" enabled="true" />
|
||||||
|
<content-type name="HTML" enabled="true" />
|
||||||
|
<content-type name="XHTML" enabled="true" />
|
||||||
|
<content-type name="CSS" enabled="true" />
|
||||||
|
<content-type name="SQL" enabled="true" />
|
||||||
|
<content-type name="PL/SQL" enabled="true" />
|
||||||
|
<content-type name="JavaScript" enabled="true" />
|
||||||
|
<content-type name="JSON" enabled="true" />
|
||||||
|
<content-type name="JSON5" enabled="true" />
|
||||||
|
<content-type name="JSP" enabled="true" />
|
||||||
|
<content-type name="JSPx" enabled="true" />
|
||||||
|
<content-type name="ASP" enabled="true" />
|
||||||
|
<content-type name="YAML" enabled="true" />
|
||||||
|
</content-types>
|
||||||
|
</qualified-text-editor>
|
||||||
|
<record-navigation>
|
||||||
|
<navigation-target value="VIEWER" />
|
||||||
|
</record-navigation>
|
||||||
|
</dataset-editor-settings>
|
||||||
|
<code-editor-settings>
|
||||||
|
<general>
|
||||||
|
<show-object-navigation-gutter value="false" />
|
||||||
|
<show-spec-declaration-navigation-gutter value="true" />
|
||||||
|
<enable-spellchecking value="true" />
|
||||||
|
<enable-reference-spellchecking value="false" />
|
||||||
|
</general>
|
||||||
|
<confirmations>
|
||||||
|
<save-changes value="false" />
|
||||||
|
<revert-changes value="true" />
|
||||||
|
</confirmations>
|
||||||
|
</code-editor-settings>
|
||||||
|
<code-completion-settings>
|
||||||
|
<filters>
|
||||||
|
<basic-filter>
|
||||||
|
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="role" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="user" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||||
|
<user-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||||
|
</user-schema>
|
||||||
|
<public-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="false" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="false" />
|
||||||
|
</public-schema>
|
||||||
|
<any-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||||
|
</any-schema>
|
||||||
|
</basic-filter>
|
||||||
|
<extended-filter>
|
||||||
|
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||||
|
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="user" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="role" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||||
|
<user-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||||
|
</user-schema>
|
||||||
|
<public-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||||
|
</public-schema>
|
||||||
|
<any-schema>
|
||||||
|
<filter-element type="OBJECT" id="table" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="index" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="function" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="package" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="type" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||||
|
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||||
|
</any-schema>
|
||||||
|
</extended-filter>
|
||||||
|
</filters>
|
||||||
|
<sorting enabled="true">
|
||||||
|
<sorting-element type="RESERVED_WORD" id="keyword" />
|
||||||
|
<sorting-element type="RESERVED_WORD" id="datatype" />
|
||||||
|
<sorting-element type="OBJECT" id="column" />
|
||||||
|
<sorting-element type="OBJECT" id="table" />
|
||||||
|
<sorting-element type="OBJECT" id="view" />
|
||||||
|
<sorting-element type="OBJECT" id="materialized view" />
|
||||||
|
<sorting-element type="OBJECT" id="index" />
|
||||||
|
<sorting-element type="OBJECT" id="constraint" />
|
||||||
|
<sorting-element type="OBJECT" id="trigger" />
|
||||||
|
<sorting-element type="OBJECT" id="synonym" />
|
||||||
|
<sorting-element type="OBJECT" id="sequence" />
|
||||||
|
<sorting-element type="OBJECT" id="procedure" />
|
||||||
|
<sorting-element type="OBJECT" id="function" />
|
||||||
|
<sorting-element type="OBJECT" id="package" />
|
||||||
|
<sorting-element type="OBJECT" id="type" />
|
||||||
|
<sorting-element type="OBJECT" id="dimension" />
|
||||||
|
<sorting-element type="OBJECT" id="cluster" />
|
||||||
|
<sorting-element type="OBJECT" id="dblink" />
|
||||||
|
<sorting-element type="OBJECT" id="schema" />
|
||||||
|
<sorting-element type="OBJECT" id="role" />
|
||||||
|
<sorting-element type="OBJECT" id="user" />
|
||||||
|
<sorting-element type="RESERVED_WORD" id="function" />
|
||||||
|
<sorting-element type="RESERVED_WORD" id="parameter" />
|
||||||
|
</sorting>
|
||||||
|
<format>
|
||||||
|
<enforce-code-style-case value="true" />
|
||||||
|
</format>
|
||||||
|
</code-completion-settings>
|
||||||
|
<execution-engine-settings>
|
||||||
|
<statement-execution>
|
||||||
|
<fetch-block-size value="100" />
|
||||||
|
<execution-timeout value="20" />
|
||||||
|
<debug-execution-timeout value="600" />
|
||||||
|
<focus-result value="false" />
|
||||||
|
<prompt-execution value="false" />
|
||||||
|
</statement-execution>
|
||||||
|
<script-execution>
|
||||||
|
<command-line-interfaces />
|
||||||
|
<execution-timeout value="300" />
|
||||||
|
</script-execution>
|
||||||
|
<method-execution>
|
||||||
|
<execution-timeout value="30" />
|
||||||
|
<debug-execution-timeout value="600" />
|
||||||
|
<parameter-history-size value="10" />
|
||||||
|
</method-execution>
|
||||||
|
</execution-engine-settings>
|
||||||
|
<operation-settings>
|
||||||
|
<transactions>
|
||||||
|
<uncommitted-changes>
|
||||||
|
<on-project-close value="ASK" />
|
||||||
|
<on-disconnect value="ASK" />
|
||||||
|
<on-autocommit-toggle value="ASK" />
|
||||||
|
</uncommitted-changes>
|
||||||
|
<multiple-uncommitted-changes>
|
||||||
|
<on-commit value="ASK" />
|
||||||
|
<on-rollback value="ASK" />
|
||||||
|
</multiple-uncommitted-changes>
|
||||||
|
</transactions>
|
||||||
|
<session-browser>
|
||||||
|
<disconnect-session value="ASK" />
|
||||||
|
<kill-session value="ASK" />
|
||||||
|
<reload-on-filter-change value="false" />
|
||||||
|
</session-browser>
|
||||||
|
<compiler>
|
||||||
|
<compile-type value="KEEP" />
|
||||||
|
<compile-dependencies value="ASK" />
|
||||||
|
<always-show-controls value="false" />
|
||||||
|
</compiler>
|
||||||
|
<debugger>
|
||||||
|
<debugger-type value="JDBC" />
|
||||||
|
<use-generic-runners value="true" />
|
||||||
|
</debugger>
|
||||||
|
</operation-settings>
|
||||||
|
<ddl-file-settings>
|
||||||
|
<extensions>
|
||||||
|
<mapping file-type-id="VIEW" extensions="vw" />
|
||||||
|
<mapping file-type-id="TRIGGER" extensions="trg" />
|
||||||
|
<mapping file-type-id="PROCEDURE" extensions="prc" />
|
||||||
|
<mapping file-type-id="FUNCTION" extensions="fnc" />
|
||||||
|
<mapping file-type-id="PACKAGE" extensions="pkg" />
|
||||||
|
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
|
||||||
|
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
|
||||||
|
<mapping file-type-id="TYPE" extensions="tpe" />
|
||||||
|
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
|
||||||
|
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
|
||||||
|
</extensions>
|
||||||
|
<general>
|
||||||
|
<lookup-ddl-files value="true" />
|
||||||
|
<create-ddl-files value="false" />
|
||||||
|
<synchronize-ddl-files value="true" />
|
||||||
|
<use-qualified-names value="false" />
|
||||||
|
<make-scripts-rerunnable value="true" />
|
||||||
|
</general>
|
||||||
|
</ddl-file-settings>
|
||||||
|
<general-settings>
|
||||||
|
<regional-settings>
|
||||||
|
<date-format value="MEDIUM" />
|
||||||
|
<number-format value="UNGROUPED" />
|
||||||
|
<locale value="SYSTEM_DEFAULT" />
|
||||||
|
<use-custom-formats value="false" />
|
||||||
|
</regional-settings>
|
||||||
|
<environment>
|
||||||
|
<environment-types>
|
||||||
|
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
|
||||||
|
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
|
||||||
|
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
|
||||||
|
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
|
||||||
|
</environment-types>
|
||||||
|
<visibility-settings>
|
||||||
|
<connection-tabs value="true" />
|
||||||
|
<dialog-headers value="true" />
|
||||||
|
<object-editor-tabs value="true" />
|
||||||
|
<script-editor-tabs value="false" />
|
||||||
|
<execution-result-tabs value="true" />
|
||||||
|
</visibility-settings>
|
||||||
|
</environment>
|
||||||
|
</general-settings>
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.StatementExecutionManager">
|
||||||
|
<execution-variables />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -41,8 +41,8 @@
|
|||||||
500:
|
500:
|
||||||
$ref: 'api.yaml#/components/responses/500'
|
$ref: 'api.yaml#/components/responses/500'
|
||||||
put:
|
put:
|
||||||
summary: TODO change sample
|
summary: change sample
|
||||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
description: 'Auth: basic, levels: write, maintain, dev, admin, only maintain and admin are allowed to edit samples created by another user'
|
||||||
tags:
|
tags:
|
||||||
- /sample
|
- /sample
|
||||||
security:
|
security:
|
||||||
@ -59,7 +59,7 @@
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: 'api.yaml#/components/schemas/SampleDetail'
|
$ref: 'api.yaml#/components/schemas/SampleRefs'
|
||||||
400:
|
400:
|
||||||
$ref: 'api.yaml#/components/responses/400'
|
$ref: 'api.yaml#/components/responses/400'
|
||||||
401:
|
401:
|
||||||
@ -71,8 +71,8 @@
|
|||||||
500:
|
500:
|
||||||
$ref: 'api.yaml#/components/responses/500'
|
$ref: 'api.yaml#/components/responses/500'
|
||||||
delete:
|
delete:
|
||||||
summary: TODO delete sample
|
summary: delete sample
|
||||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
description: 'Auth: basic, levels: write, maintain, dev, admin, only maintain and admin are allowed to edit samples created by another user'
|
||||||
tags:
|
tags:
|
||||||
- /sample
|
- /sample
|
||||||
security:
|
security:
|
||||||
|
26
src/db.ts
26
src/db.ts
@ -42,19 +42,19 @@ export default class db {
|
|||||||
});
|
});
|
||||||
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
|
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
|
||||||
mongoose.connection.on('disconnected', () => { // reset state on disconnect
|
mongoose.connection.on('disconnected', () => { // reset state on disconnect
|
||||||
console.log('Database disconnected');
|
console.info('Database disconnected');
|
||||||
this.state.db = 0;
|
this.state.db = 0;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
process.on('SIGINT', () => { // close connection when app is terminated
|
process.on('SIGINT', () => { // close connection when app is terminated
|
||||||
mongoose.connection.close(() => {
|
mongoose.connection.close(() => {
|
||||||
console.log('Mongoose default connection disconnected through app termination');
|
console.info('Mongoose default connection disconnected through app termination');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
mongoose.connection.once('open', () => {
|
mongoose.connection.once('open', () => {
|
||||||
mongoose.set('useFindAndModify', false);
|
mongoose.set('useFindAndModify', false);
|
||||||
console.log(process.env.NODE_ENV === 'test' ? '' : `Connected to ${connectionString}`);
|
console.info(process.env.NODE_ENV === 'test' ? '' : `Connected to ${connectionString}`);
|
||||||
this.state.db = mongoose.connection;
|
this.state.db = mongoose.connection;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -90,13 +90,7 @@ export default class db {
|
|||||||
|
|
||||||
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
||||||
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
||||||
for(let i in json.collections[collectionName]) { // convert $oid fields to actual ObjectIds
|
json.collections[collectionName] = this.oidResolve(json.collections[collectionName]);
|
||||||
Object.keys(json.collections[collectionName][i]).forEach(key => {
|
|
||||||
if (json.collections[collectionName][i][key] !== null && json.collections[collectionName][i][key].hasOwnProperty('$oid')) {
|
|
||||||
json.collections[collectionName][i][key] = mongoose.Types.ObjectId(json.collections[collectionName][i][key].$oid);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.state.db.createCollection(collectionName, (err, collection) => {
|
this.state.db.createCollection(collectionName, (err, collection) => {
|
||||||
collection.insertMany(json.collections[collectionName], () => { // insert JSON data
|
collection.insertMany(json.collections[collectionName], () => { // insert JSON data
|
||||||
if (++ loadCounter >= Object.keys(json.collections).length) { // all collections loaded
|
if (++ loadCounter >= Object.keys(json.collections).length) { // all collections loaded
|
||||||
@ -106,4 +100,16 @@ export default class db {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static oidResolve (object: any) { // resolve $oid fields to actual ObjectIds recursively
|
||||||
|
Object.keys(object).forEach(key => {
|
||||||
|
if (object[key] !== null && object[key].hasOwnProperty('$oid')) {
|
||||||
|
object[key] = mongoose.Types.ObjectId(object[key].$oid);
|
||||||
|
}
|
||||||
|
else if (typeof object[key] === 'object' && object[key] !== null) {
|
||||||
|
object[key] = this.oidResolve(object[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
}
|
||||||
};
|
};
|
@ -30,7 +30,7 @@ export default (mailAddress, subject, content, f) => { // callback, executed em
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (process.env.NODE_ENV === 'test') {
|
else if (process.env.NODE_ENV === 'test') {
|
||||||
console.log('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
else { // dev
|
else { // dev
|
||||||
|
@ -8,7 +8,7 @@ import db from './db';
|
|||||||
|
|
||||||
|
|
||||||
// tell if server is running in debug or production environment
|
// tell if server is running in debug or production environment
|
||||||
console.log(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
||||||
|
|
||||||
|
|
||||||
// mongodb connection
|
// mongodb connection
|
||||||
@ -75,7 +75,7 @@ app.use((err, req, res, ignore) => { // internal server error handling
|
|||||||
|
|
||||||
// hook up server to port
|
// hook up server to port
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
console.log(process.env.NODE_ENV === 'test' ? '' : `Listening on http://localhost:${port}`);
|
console.info(process.env.NODE_ENV === 'test' ? '' : `Listening on http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = server;
|
module.exports = server;
|
@ -171,14 +171,54 @@ describe('/material', () => {
|
|||||||
res: {status: 'Material name already taken'}
|
res: {status: 'Material name already taken'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects wrong material properties', done => {
|
it('rejects a wrong mineral property', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: '/material/100000000000000000000001',
|
url: '/material/100000000000000000000001',
|
||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
req: {mineral: 'x'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"mineral" must be a number'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a wrong glass_fiber property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/material/100000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {glass_fiber: 'x'},
|
||||||
|
res: {status: 'Invalid body format', details: '"glass_fiber" must be a number'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a wrong carbon_fiber property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/material/100000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {carbon_fiber: 'x'},
|
||||||
|
res: {status: 'Invalid body format', details: '"carbon_fiber" must be a number'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a wrong color name property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/material/100000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {numbers: [{colorxx: 'black', number: 55}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a wrong color number property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/material/100000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {numbers: [{color: 'black', number: 'xxx'}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"numbers[0].number" must be a number'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid id', done => {
|
it('rejects an invalid id', done => {
|
||||||
@ -347,24 +387,94 @@ describe('/material', () => {
|
|||||||
res: {status: 'Material name already taken'}
|
res: {status: 'Material name already taken'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects wrong material properties', done => {
|
it('rejects a missing name', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/material/new',
|
url: '/material/new',
|
||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
req: {supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"name" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects incomplete material properties', done => {
|
it('rejects a missing supplier', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/material/new',
|
url: '/material/new',
|
||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {name: 'Crastin CE 2510'},
|
req: {name: 'Crastin CE 2510', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"supplier" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing group', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"group" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing mineral property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"mineral" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing glass_fiber property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"glass_fiber" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing carbon_fiber property', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, numbers: [{color: 'black', number: 5515798402}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"carbon_fiber" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing numbers array', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0},
|
||||||
|
res: {status: 'Invalid body format', details: '"numbers" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing color name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{number: 5515798402}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing color number', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'post',
|
||||||
|
url: '/material/new',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black'}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"numbers[0].number" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an API key', done => {
|
it('rejects an API key', done => {
|
||||||
|
@ -3,6 +3,7 @@ import express from 'express';
|
|||||||
import MaterialValidate from './validate/material';
|
import MaterialValidate from './validate/material';
|
||||||
import MaterialModel from '../models/material'
|
import MaterialModel from '../models/material'
|
||||||
import IdValidate from './validate/id';
|
import IdValidate from './validate/id';
|
||||||
|
import res400 from './validate/res400';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -34,10 +35,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
|||||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
const {error, value: material} = MaterialValidate.input(req.body, 'change');
|
const {error, value: material} = MaterialValidate.input(req.body, 'change');
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
res.status(400).json({status: 'Invalid body format'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (material.hasOwnProperty('name')) {
|
if (material.hasOwnProperty('name')) {
|
||||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||||
@ -87,10 +85,7 @@ router.post('/material/new', (req, res, next) => {
|
|||||||
|
|
||||||
// validate input
|
// validate input
|
||||||
const {error, value: material} = MaterialValidate.input(req.body, 'new');
|
const {error, value: material} = MaterialValidate.input(req.body, 'new');
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
res.status(400).json({status: 'Invalid body format'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
@ -69,6 +69,387 @@ describe('/sample', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PUT /sample/{id}', () => {
|
||||||
|
it('returns the right sample', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {},
|
||||||
|
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('keeps unchanged properties', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}},
|
||||||
|
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('changes the given properties', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||||
|
}).end(err => {
|
||||||
|
if (err) return done (err);
|
||||||
|
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'validated', 'material_id', 'note_id', 'user_id', '__v');
|
||||||
|
should(data).have.property('_id');
|
||||||
|
should(data).have.property('number', '10');
|
||||||
|
should(data).have.property('color', 'signalviolet');
|
||||||
|
should(data).have.property('type', 'part');
|
||||||
|
should(data).have.property('batch', '114531');
|
||||||
|
should(data).have.property('validated').be.type('boolean');
|
||||||
|
should(data.material_id.toString()).be.eql('100000000000000000000002');
|
||||||
|
should(data.user_id.toString()).be.eql('000000000000000000000002');
|
||||||
|
should(data).have.property('note_id');
|
||||||
|
NoteModel.findById(data.note_id).lean().exec((err, data: any) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
should(data).have.property('_id');
|
||||||
|
should(data).have.property('comment', 'Testcomment');
|
||||||
|
should(data).have.property('sample_references');
|
||||||
|
should(data.sample_references).have.lengthOf(1);
|
||||||
|
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
|
||||||
|
should(data.sample_references[0]).have.property('relation', 'part to this sample');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('adjusts the note_fields correctly', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000003',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {notes: {comment: 'Testcomment', sample_references: [], custom_fields: {field1: 'value 1'}}}
|
||||||
|
}).end(err => {
|
||||||
|
if (err) return done(err);
|
||||||
|
NoteFieldModel.findOne({name: 'not allowed for new applications'}).lean().exec((err, data) => {
|
||||||
|
console.log(data);
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).have.property('qty', 1);
|
||||||
|
NoteFieldModel.findOne({name: 'field1'}).lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
console.log(data);
|
||||||
|
should(data).have.property('qty', 1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('deletes old note_fields', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000004',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {notes: {comment: 'Testcomment', sample_references: []}}
|
||||||
|
}).end(err => {
|
||||||
|
if (err) return done (err);
|
||||||
|
NoteFieldModel.findOne({name: 'another_field'}).lean().exec((err, data) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('keeps untouched notes', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000002',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {number: '111'}
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
NoteModel.findById(res.body.note_id).lean().exec((err, data) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
console.log(data);
|
||||||
|
should(data).not.be.null();
|
||||||
|
should(data).have.property('comment', 'Stoff gesperrt');
|
||||||
|
should(data).have.property('sample_references').have.lengthOf(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('deletes old notes', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000004',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {notes: {comment: 'Testcomment', sample_references: []}}
|
||||||
|
}).end(err => {
|
||||||
|
if (err) return done (err);
|
||||||
|
NoteModel.findById('500000000000000000000003').lean().exec((err, data) => {
|
||||||
|
if (err) return done (err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a color not defined for the material', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
res: {status: 'Color not available for material'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an unknown material id', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '000000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
res: {status: 'Material not available'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a sample number in use', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {number: '21', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
res: {status: 'Sample number already taken'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an invalid sample reference', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
res: {status: 'Sample reference not available'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an invalid material id', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an invalid id', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/10000000000h000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 404,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an API key', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {key: 'janedoe'},
|
||||||
|
httpStatus: 401,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects changes for samples from another user for a write user', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000003',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 403,
|
||||||
|
req: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('accepts changes for samples from another user for a maintain/admin user', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200,
|
||||||
|
req: {},
|
||||||
|
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects requests from a read user', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'user'},
|
||||||
|
httpStatus: 403,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns 404 for an unknown sample', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/000000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 404,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
it('rejects unauthorized requests', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
httpStatus: 401,
|
||||||
|
req: {number: '10', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /sample/{id}', () => {
|
||||||
|
it('deletes the sample', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.body).be.eql({status: 'OK'});
|
||||||
|
SampleModel.findById('400000000000000000000001').lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('deletes the notes of the sample', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000002',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.body).be.eql({status: 'OK'});
|
||||||
|
NoteModel.findById('500000000000000000000001').lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('adjusts the note_fields correctly', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000004',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.body).be.eql({status: 'OK'});
|
||||||
|
NoteFieldModel.findOne({name: 'not allowed for new applications'}).lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).have.property('qty', 1);
|
||||||
|
NoteFieldModel.findOne({name: 'another_field'}).lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('resets references to this sample', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000003',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.body).be.eql({status: 'OK'});
|
||||||
|
setTimeout(() => { // background action takes some time before we can check
|
||||||
|
NoteModel.findById('500000000000000000000003').lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
console.log(data);
|
||||||
|
should(data).have.property('sample_references').with.lengthOf(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('lets admin/maintain users delete samples of other users', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.body).be.eql({status: 'OK'});
|
||||||
|
SampleModel.findById('400000000000000000000001').lean().exec((err, data) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(data).be.null();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects deleting samples of other users for write users', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000004',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 403
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an invalid id', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000h00000000004',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 404
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects requests from a read user', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000004',
|
||||||
|
auth: {basic: 'user'},
|
||||||
|
httpStatus: 403
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns 404 for an unknown id', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/000000000000000000000004',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 404
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an API key', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
auth: {key: 'janedoe'},
|
||||||
|
httpStatus: 401
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects unauthorized requests', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/sample/400000000000000000000001',
|
||||||
|
httpStatus: 401
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('POST /sample/new', () => {
|
describe('POST /sample/new', () => {
|
||||||
it('returns the right sample', done => {
|
it('returns the right sample', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
@ -209,7 +590,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {number: 'Rng172', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {number: 'Rng172', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"color" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects a missing sample number', done => {
|
it('rejects a missing sample number', done => {
|
||||||
@ -219,7 +600,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"number" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects a missing type', done => {
|
it('rejects a missing type', done => {
|
||||||
@ -229,7 +610,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {number: 'Rng172', color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {number: 'Rng172', color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"type" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects a missing batch', done => {
|
it('rejects a missing batch', done => {
|
||||||
@ -239,7 +620,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {number: 'Rng172', color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {number: 'Rng172', color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"batch" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects a missing material id', done => {
|
it('rejects a missing material id', done => {
|
||||||
@ -249,7 +630,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"material_id" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid material id', done => {
|
it('rejects an invalid material id', done => {
|
||||||
@ -259,7 +640,7 @@ describe('/sample', () => {
|
|||||||
auth: {basic: 'janedoe'},
|
auth: {basic: 'janedoe'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an API key', done => {
|
it('rejects an API key', done => {
|
||||||
|
@ -2,10 +2,12 @@ import express from 'express';
|
|||||||
|
|
||||||
import SampleValidate from './validate/sample';
|
import SampleValidate from './validate/sample';
|
||||||
import NoteFieldValidate from './validate/note_field';
|
import NoteFieldValidate from './validate/note_field';
|
||||||
|
import res400 from './validate/res400';
|
||||||
import SampleModel from '../models/sample'
|
import SampleModel from '../models/sample'
|
||||||
import MaterialModel from '../models/material';
|
import MaterialModel from '../models/material';
|
||||||
import NoteModel from '../models/note';
|
import NoteModel from '../models/note';
|
||||||
import NoteFieldModel from '../models/note_field';
|
import NoteFieldModel from '../models/note_field';
|
||||||
|
import IdValidate from './validate/id';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -20,66 +22,118 @@ router.get('/samples', (req, res, next) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||||
router.post('/sample/new', (req, res, next) => {
|
|
||||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
const {error, value: sample} = SampleValidate.input(req.body, 'new');
|
const {error, value: sample} = SampleValidate.input(req.body, 'change');
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
return res.status(400).json({status: 'Invalid body format'});
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialModel.findById(sample.material_id).lean().exec((err, data: any) => { // validate material_id
|
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // check if id exists
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!data) { // could not find material_id
|
if (!sampleData) {
|
||||||
return res.status(400).json({status: 'Material not available'});
|
return res.status(404).json({status: 'Not found'});
|
||||||
}
|
}
|
||||||
if (!data.numbers.find(e => e.color === sample.color)) { // color for material not specified
|
// only maintain and admin are allowed to edit other user's data
|
||||||
return res.status(400).json({status: 'Color not available for material'});
|
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||||
}
|
|
||||||
SampleModel.findOne({number: sample.number}).lean().exec((err, data) => { // validate sample number
|
|
||||||
if (err) return next(err);
|
|
||||||
if (data) { // found entry with sample number
|
|
||||||
return res.status(400).json({status: 'Sample number already taken'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sample.notes.sample_references.length > 0) { // validate sample_references
|
if (sample.hasOwnProperty('number') && sample.number !== sampleData.number) {
|
||||||
let referencesCount = sample.notes.sample_references.length;
|
if (!await numberCheck(sample, res, next)) return;
|
||||||
sample.notes.sample_references.forEach(reference => {
|
}
|
||||||
SampleModel.findById(reference.id).lean().exec((err, data) => {
|
if (sample.hasOwnProperty('material_id')) {
|
||||||
if (err) return next(err);
|
if (!await materialCheck(sample, res, next)) return;
|
||||||
if (!data) {
|
}
|
||||||
return res.status(400).json({status: 'Sample reference not available'});
|
else if (sample.hasOwnProperty('color')) {
|
||||||
}
|
if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
|
||||||
referencesCount --;
|
}
|
||||||
if (referencesCount <= 0) {
|
|
||||||
f();
|
if (sample.hasOwnProperty('notes') && sampleData.note_id !== null) { // deal with old notes data
|
||||||
}
|
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => {
|
||||||
|
if (err) return console.error(err);
|
||||||
|
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
||||||
|
customFieldsChange(Object.keys(data.custom_fields), -1);
|
||||||
|
}
|
||||||
|
NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => { // delete old notes
|
||||||
|
if (err) return console.error(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (sample.hasOwnProperty('notes') && Object.keys(sample.notes).length > 0) { // save new notes
|
||||||
|
if (!await sampleRefCheck(sample, res, next)) return;
|
||||||
|
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) { // new custom_fields
|
||||||
|
customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
|
||||||
|
}
|
||||||
|
let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)}); // save new notes
|
||||||
|
delete sample.notes;
|
||||||
|
sample.note_id = data._id;
|
||||||
|
}
|
||||||
|
SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
|
||||||
|
if (err) return next(err);
|
||||||
|
res.json(SampleValidate.output(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||||
|
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
|
SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => { // check if id exists
|
||||||
|
if (err) return next(err);
|
||||||
|
if (!sampleData) {
|
||||||
|
return res.status(404).json({status: 'Not found'});
|
||||||
|
}
|
||||||
|
// only maintain and admin are allowed to edit other user's data
|
||||||
|
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
|
SampleModel.findByIdAndDelete(req.params.id).lean().exec(err => { // delete sample
|
||||||
|
if (err) return next(err);
|
||||||
|
if (sampleData.note_id !== null) {
|
||||||
|
NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec((err, data: any) => { // delete notes
|
||||||
|
if (err) return next(err);
|
||||||
|
console.log(data);
|
||||||
|
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
||||||
|
customFieldsChange(Object.keys(data.custom_fields), -1);
|
||||||
|
}
|
||||||
|
res.json({status: 'OK'});
|
||||||
|
NoteModel.updateMany({'sample_references.id': req.params.id}, {$unset: {'sample_references.$': null}}).lean().exec(err => { // remove sample_references
|
||||||
|
if (err) console.error(err);
|
||||||
|
NoteModel.collection.updateMany({sample_references: null}, {$pull: {sample_references: null}}, err => { // only works with native MongoDB driver somehow
|
||||||
|
if (err) console.error(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
f();
|
res.json({status: 'OK'});
|
||||||
}
|
|
||||||
|
|
||||||
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
|
|
||||||
customFieldsAdd(Object.keys(sample.notes.custom_fields));
|
|
||||||
}
|
|
||||||
|
|
||||||
function f() { // to resolve async
|
|
||||||
new NoteModel(sample.notes).save((err, data) => {
|
|
||||||
if (err) return next(err);
|
|
||||||
delete sample.notes;
|
|
||||||
sample.note_id = data._id;
|
|
||||||
sample.user_id = req.authDetails.id;
|
|
||||||
new SampleModel(sample).save((err, data) => {
|
|
||||||
if (err) return next(err);
|
|
||||||
res.json(SampleValidate.output(data.toObject()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/sample/new', async (req, res, next) => {
|
||||||
|
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||||
|
|
||||||
|
const {error, value: sample} = SampleValidate.input(req.body, 'new');
|
||||||
|
if (error) return res400(error, res);
|
||||||
|
|
||||||
|
if (!await numberCheck(sample, res, next)) return;
|
||||||
|
if (!await materialCheck(sample, res, next)) return;
|
||||||
|
if (!await sampleRefCheck(sample, res, next)) return;
|
||||||
|
|
||||||
|
if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) { // new custom_fields
|
||||||
|
customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
new NoteModel(sample.notes).save((err, data) => {
|
||||||
|
if (err) return next(err);
|
||||||
|
delete sample.notes;
|
||||||
|
sample.note_id = data._id;
|
||||||
|
sample.user_id = req.authDetails.id;
|
||||||
|
new SampleModel(sample).save((err, data) => {
|
||||||
|
if (err) return next(err);
|
||||||
|
res.json(SampleValidate.output(data.toObject()));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/sample/notes/fields', (req, res, next) => {
|
router.get('/sample/notes/fields', (req, res, next) => {
|
||||||
@ -95,15 +149,69 @@ router.get('/sample/notes/fields', (req, res, next) => {
|
|||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
|
||||||
function customFieldsAdd (fields) {
|
async function numberCheck (sample, res, next) { // validate number, returns false if invalid
|
||||||
|
const sampleData = await SampleModel.findOne({number: sample.number}).lean().exec().catch(err => { return next(err)});
|
||||||
|
if (sampleData) { // found entry with sample number
|
||||||
|
res.status(400).json({status: 'Sample number already taken'});
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function materialCheck (sample, res, next, id = sample.material_id) { // validate material_id and color, returns false if invalid
|
||||||
|
const materialData = await MaterialModel.findById(id).lean().exec().catch(err => {next(err);}) as any;
|
||||||
|
if (materialData instanceof Error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!materialData) { // could not find material_id
|
||||||
|
res.status(400).json({status: 'Material not available'});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sample.hasOwnProperty('color') && !materialData.numbers.find(e => e.color === sample.color)) { // color for material not specified
|
||||||
|
res.status(400).json({status: 'Color not available for material'});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (sample.notes.sample_references.length > 0) { // there are sample_references
|
||||||
|
let referencesCount = sample.notes.sample_references.length;
|
||||||
|
sample.notes.sample_references.forEach(reference => {
|
||||||
|
SampleModel.findById(reference.id).lean().exec((err, data) => {
|
||||||
|
if (err) {next(err); resolve(false)}
|
||||||
|
if (!data) {
|
||||||
|
res.status(400).json({status: 'Sample reference not available'});
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
referencesCount --;
|
||||||
|
if (referencesCount <= 0) {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function customFieldsChange (fields, amount) {
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: 1}}).lean().exec((err, data) => { // check if field exists
|
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}}, {new: true}).lean().exec((err, data: any) => { // check if field exists
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
if (!data) { // new field
|
if (!data) { // new field
|
||||||
new NoteFieldModel({name: field, qty: 1}).save(err => {
|
new NoteFieldModel({name: field, qty: 1}).save(err => {
|
||||||
if (err) return console.error(err);
|
if (err) return console.error(err);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
else if (data.qty <= 0) {
|
||||||
|
NoteFieldModel.findOneAndDelete({name: field}).lean().exec(err => {
|
||||||
|
if (err) return console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -182,14 +182,54 @@ describe('/template', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an incomplete template for a new name', done => {
|
it('rejects a missing name for a new name', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: '/template/treatment/heat%20aging',
|
url: '/template/treatment/heat%20aging',
|
||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {parameters: [{name: 'time'}]},
|
req: {parameters: [{name: 'time', range: {min: 1}}]},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"name" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects missing parameters for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/treatment/heat%20aging',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'heat aging'},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing parameter name for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/treatment/heat%20aging',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'heat aging', parameters: [{range: {min: 1}}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].name" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing parameter range for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/treatment/heat%20aging',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'heat aging', parameters: [{name: 'time'}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a an invalid parameter range property for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/treatment/heat%20aging',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'heat aging', parameters: [{name: 'time', range: {xx: 1}}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects already existing names', done => {
|
it('rejects already existing names', done => {
|
||||||
@ -209,7 +249,7 @@ describe('/template', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {parameters: [{name: 'time'}], xx: 33},
|
req: {parameters: [{name: 'time'}], xx: 33},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"name" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an API key', done => {
|
it('rejects an API key', done => {
|
||||||
@ -466,14 +506,54 @@ describe('/template', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an incomplete template for a new name', done => {
|
it('rejects a missing name for a new name', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: '/template/measurement/vz',
|
url: '/template/measurement/spectrum2',
|
||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {parameters: [{name: 'vz'}]},
|
req: {parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"name" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects missing parameters for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/measurement/spectrum2',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'IR spectrum'},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing parameter name for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/measurement/spectrum2',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'IR spectrum', parameters: [{range: {min: 0, max: 1000}}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].name" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a missing parameter range for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/measurement/spectrum2',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'IR spectrum', parameters: [{name: 'data point table'}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects a an invalid parameter range property for a new name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'put',
|
||||||
|
url: '/template/measurement/spectrum2',
|
||||||
|
auth: {basic: 'admin'},
|
||||||
|
httpStatus: 400,
|
||||||
|
req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {xx: 0}}]},
|
||||||
|
res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects already existing names', done => {
|
it('rejects already existing names', done => {
|
||||||
@ -493,7 +573,7 @@ describe('/template', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {parameters: [{name: 'dpt'}], xx: 33},
|
req: {parameters: [{name: 'dpt'}], xx: 33},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an API key', done => {
|
it('rejects an API key', done => {
|
||||||
|
@ -3,6 +3,7 @@ import express from 'express';
|
|||||||
import TemplateValidate from './validate/template';
|
import TemplateValidate from './validate/template';
|
||||||
import TemplateTreatmentModel from '../models/treatment_template';
|
import TemplateTreatmentModel from '../models/treatment_template';
|
||||||
import TemplateMeasurementModel from '../models/measurement_template';
|
import TemplateMeasurementModel from '../models/measurement_template';
|
||||||
|
import res400 from './validate/res400';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -41,10 +42,7 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next
|
|||||||
if (err) next (err);
|
if (err) next (err);
|
||||||
const templateState = data? 'change': 'new';
|
const templateState = data? 'change': 'new';
|
||||||
const {error, value: template} = TemplateValidate.input(req.body, templateState);
|
const {error, value: template} = TemplateValidate.input(req.body, templateState);
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
res.status(400).json({status: 'Invalid body format'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (template.hasOwnProperty('name') && template.name !== req.params.name) {
|
if (template.hasOwnProperty('name') && template.name !== req.params.name) {
|
||||||
collectionModel.find({name: template.name}).lean().exec((err, data) => {
|
collectionModel.find({name: template.name}).lean().exec((err, data) => {
|
||||||
|
@ -224,7 +224,7 @@ describe('/user', () => {
|
|||||||
req: {level: 'read'}
|
req: {level: 'read'}
|
||||||
}).end((err, res) => {
|
}).end((err, res) => {
|
||||||
if (err) return done (err);
|
if (err) return done (err);
|
||||||
should(res.body).be.eql({status: 'Invalid body format'});
|
should(res.body).be.eql({status: 'Invalid body format', details: '"level" is not allowed'});
|
||||||
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
should(data).have.lengthOf(1);
|
should(data).have.lengthOf(1);
|
||||||
@ -267,7 +267,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', location: 44, device_name: 'Alpha II'},
|
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', location: 44, device_name: 'Alpha II'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"location" must be a string'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid email address', done => {
|
it('rejects an invalid email address', done => {
|
||||||
@ -277,7 +277,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe'},
|
req: {email: 'john.doe'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"email" must be a valid email'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid password', done => {
|
it('rejects an invalid password', done => {
|
||||||
@ -287,7 +287,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {pass: 'password'},
|
req: {pass: 'password'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects requests from non-admins for another user', done => {
|
it('rejects requests from non-admins for another user', done => {
|
||||||
@ -515,7 +515,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 44, device_name: 'Alpha II'},
|
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 44, device_name: 'Alpha II'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"location" must be a string'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid user level', done => {
|
it('rejects an invalid user level', done => {
|
||||||
@ -525,7 +525,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'xxx', location: 'Rng', device_name: 'Alpha II'},
|
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'xxx', location: 'Rng', device_name: 'Alpha II'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"level" must be one of [read, write, maintain, dev, admin]'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid email address', done => {
|
it('rejects an invalid email address', done => {
|
||||||
@ -535,7 +535,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
req: {email: 'john.doe', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"email" must be a valid email'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects an invalid password', done => {
|
it('rejects an invalid password', done => {
|
||||||
@ -545,7 +545,7 @@ describe('/user', () => {
|
|||||||
auth: {basic: 'admin'},
|
auth: {basic: 'admin'},
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
|
||||||
res: {status: 'Invalid body format'}
|
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects requests from non-admins', done => {
|
it('rejects requests from non-admins', done => {
|
||||||
|
@ -5,6 +5,7 @@ import bcrypt from 'bcryptjs';
|
|||||||
import UserValidate from './validate/user';
|
import UserValidate from './validate/user';
|
||||||
import UserModel from '../models/user';
|
import UserModel from '../models/user';
|
||||||
import mail from '../helpers/mail';
|
import mail from '../helpers/mail';
|
||||||
|
import res400 from './validate/res400';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -46,10 +47,7 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // thi
|
|||||||
username = req.params.username;
|
username = req.params.username;
|
||||||
}
|
}
|
||||||
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
|
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
res.status(400).json({status: 'Invalid body format'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.hasOwnProperty('pass')) {
|
if (user.hasOwnProperty('pass')) {
|
||||||
user.pass = bcrypt.hashSync(user.pass, 10);
|
user.pass = bcrypt.hashSync(user.pass, 10);
|
||||||
@ -122,10 +120,7 @@ router.post('/user/new', (req, res, next) => {
|
|||||||
|
|
||||||
// validate input
|
// validate input
|
||||||
const {error, value: user} = UserValidate.input(req.body, 'new');
|
const {error, value: user} = UserValidate.input(req.body, 'new');
|
||||||
if (error) {
|
if (error) return res400(error, res);
|
||||||
res.status(400).json({status: 'Invalid body format'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that user does not already exist
|
// check that user does not already exist
|
||||||
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import joi from '@hapi/joi';
|
import Joi from '@hapi/joi';
|
||||||
|
|
||||||
export default class IdValidate {
|
export default class IdValidate {
|
||||||
private static id = joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
|
private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
|
||||||
|
|
||||||
static get () {
|
static get () {
|
||||||
return this.id;
|
return this.id;
|
||||||
|
@ -31,9 +31,11 @@ export default class MaterialValidate { // validate input for material
|
|||||||
numbers: joi.array()
|
numbers: joi.array()
|
||||||
.items(joi.object({
|
.items(joi.object({
|
||||||
color: joi.string()
|
color: joi.string()
|
||||||
.max(128),
|
.max(128)
|
||||||
|
.required(),
|
||||||
number: joi.number()
|
number: joi.number()
|
||||||
.min(0)
|
.min(0)
|
||||||
|
.required()
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ export default class MaterialValidate { // validate input for material
|
|||||||
mineral: this.material.mineral.required(),
|
mineral: this.material.mineral.required(),
|
||||||
glass_fiber: this.material.glass_fiber.required(),
|
glass_fiber: this.material.glass_fiber.required(),
|
||||||
carbon_fiber: this.material.carbon_fiber.required(),
|
carbon_fiber: this.material.carbon_fiber.required(),
|
||||||
numbers: this.material.numbers
|
numbers: this.material.numbers.required()
|
||||||
}).validate(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
else if (param === 'change') {
|
else if (param === 'change') {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import joi from '@hapi/joi';
|
import Joi from '@hapi/joi';
|
||||||
|
|
||||||
export default class NoteFieldValidate {
|
export default class NoteFieldValidate {
|
||||||
private static note_field = {
|
private static note_field = {
|
||||||
name: joi.string()
|
name: Joi.string()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
qty: joi.number()
|
qty: Joi.number()
|
||||||
};
|
};
|
||||||
|
|
||||||
static output (data) {
|
static output (data) {
|
||||||
const {value, error} = joi.object({
|
const {value, error} = Joi.object({
|
||||||
name: this.note_field.name,
|
name: this.note_field.name,
|
||||||
qty: this.note_field.qty
|
qty: this.note_field.qty
|
||||||
}).validate(data, {stripUnknown: true});
|
}).validate(data, {stripUnknown: true});
|
||||||
|
3
src/routes/validate/res400.ts
Normal file
3
src/routes/validate/res400.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function res400 (error, res) {
|
||||||
|
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
|
||||||
|
}
|
@ -1,41 +1,41 @@
|
|||||||
import joi from '@hapi/joi';
|
import Joi from '@hapi/joi';
|
||||||
|
|
||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
|
|
||||||
export default class SampleValidate {
|
export default class SampleValidate {
|
||||||
private static sample = {
|
private static sample = {
|
||||||
number: joi.string()
|
number: Joi.string()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
color: joi.string()
|
color: Joi.string()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
type: joi.string()
|
type: Joi.string()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
batch: joi.string()
|
batch: Joi.string()
|
||||||
.max(128)
|
.max(128)
|
||||||
.allow(''),
|
.allow(''),
|
||||||
|
|
||||||
notes: joi.object({
|
notes: Joi.object({
|
||||||
comment: joi.string()
|
comment: Joi.string()
|
||||||
.max(512),
|
.max(512),
|
||||||
|
|
||||||
sample_references: joi.array()
|
sample_references: Joi.array()
|
||||||
.items(joi.object({
|
.items(Joi.object({
|
||||||
id: IdValidate.get(),
|
id: IdValidate.get(),
|
||||||
|
|
||||||
relation: joi.string()
|
relation: Joi.string()
|
||||||
.max(128)
|
.max(128)
|
||||||
})),
|
})),
|
||||||
|
|
||||||
custom_fields: joi.object()
|
custom_fields: Joi.object()
|
||||||
.pattern(/.*/, joi.alternatives()
|
.pattern(/.*/, Joi.alternatives()
|
||||||
.try(
|
.try(
|
||||||
joi.string().max(128),
|
Joi.string().max(128),
|
||||||
joi.number(),
|
Joi.number(),
|
||||||
joi.boolean(),
|
Joi.boolean(),
|
||||||
joi.date()
|
Joi.date()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -43,7 +43,7 @@ export default class SampleValidate {
|
|||||||
|
|
||||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return joi.object({
|
return Joi.object({
|
||||||
number: this.sample.number.required(),
|
number: this.sample.number.required(),
|
||||||
color: this.sample.color.required(),
|
color: this.sample.color.required(),
|
||||||
type: this.sample.type.required(),
|
type: this.sample.type.required(),
|
||||||
@ -53,7 +53,14 @@ export default class SampleValidate {
|
|||||||
}).validate(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
else if (param === 'change') {
|
else if (param === 'change') {
|
||||||
return{error: 'Not implemented!', value: {}};
|
return Joi.object({
|
||||||
|
number: this.sample.number,
|
||||||
|
color: this.sample.color,
|
||||||
|
type: this.sample.type,
|
||||||
|
batch: this.sample.batch,
|
||||||
|
material_id: IdValidate.get(),
|
||||||
|
notes: this.sample.notes,
|
||||||
|
}).validate(data);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return{error: 'No parameter specified!', value: {}};
|
return{error: 'No parameter specified!', value: {}};
|
||||||
@ -62,7 +69,7 @@ export default class SampleValidate {
|
|||||||
|
|
||||||
static output (data) {
|
static output (data) {
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
const {value, error} = joi.object({
|
const {value, error} = Joi.object({
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
number: this.sample.number,
|
number: this.sample.number,
|
||||||
color: this.sample.color,
|
color: this.sample.color,
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import joi from '@hapi/joi';
|
import Joi from '@hapi/joi';
|
||||||
import globals from '../../globals';
|
import globals from '../../globals';
|
||||||
|
|
||||||
import IdValidate from './id';
|
import IdValidate from './id';
|
||||||
|
|
||||||
export default class UserValidate { // validate input for user
|
export default class UserValidate { // validate input for user
|
||||||
private static user = {
|
private static user = {
|
||||||
name: joi.string()
|
name: Joi.string()
|
||||||
.alphanum()
|
.alphanum()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
email: joi.string()
|
email: Joi.string()
|
||||||
.email({minDomainSegments: 2})
|
.email({minDomainSegments: 2})
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
pass: joi.string()
|
pass: Joi.string()
|
||||||
.pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
|
.pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
level: joi.string()
|
level: Joi.string()
|
||||||
.valid(...globals.levels),
|
.valid(...globals.levels),
|
||||||
|
|
||||||
location: joi.string()
|
location: Joi.string()
|
||||||
.alphanum()
|
.alphanum()
|
||||||
.max(128),
|
.max(128),
|
||||||
|
|
||||||
device_name: joi.string()
|
device_name: Joi.string()
|
||||||
.allow('')
|
.allow('')
|
||||||
.max(128),
|
.max(128),
|
||||||
};
|
};
|
||||||
@ -35,7 +35,7 @@ export default class UserValidate { // validate input for user
|
|||||||
|
|
||||||
static input (data, param) {
|
static input (data, param) {
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return joi.object({
|
return Joi.object({
|
||||||
name: this.user.name.required(),
|
name: this.user.name.required(),
|
||||||
email: this.user.email.required(),
|
email: this.user.email.required(),
|
||||||
pass: this.user.pass.required(),
|
pass: this.user.pass.required(),
|
||||||
@ -45,7 +45,7 @@ export default class UserValidate { // validate input for user
|
|||||||
}).validate(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
else if (param === 'change') {
|
else if (param === 'change') {
|
||||||
return joi.object({
|
return Joi.object({
|
||||||
name: this.user.name,
|
name: this.user.name,
|
||||||
email: this.user.email,
|
email: this.user.email,
|
||||||
pass: this.user.pass,
|
pass: this.user.pass,
|
||||||
@ -54,7 +54,7 @@ export default class UserValidate { // validate input for user
|
|||||||
}).validate(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
else if (param === 'changeadmin') {
|
else if (param === 'changeadmin') {
|
||||||
return joi.object({
|
return Joi.object({
|
||||||
name: this.user.name,
|
name: this.user.name,
|
||||||
email: this.user.email,
|
email: this.user.email,
|
||||||
pass: this.user.pass,
|
pass: this.user.pass,
|
||||||
@ -70,7 +70,7 @@ export default class UserValidate { // validate input for user
|
|||||||
|
|
||||||
static output (data) { // validate output from database for needed properties, strip everything else
|
static output (data) { // validate output from database for needed properties, strip everything else
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
const {value, error} = joi.object({
|
const {value, error} = Joi.object({
|
||||||
_id: IdValidate.get(),
|
_id: IdValidate.get(),
|
||||||
name: this.user.name,
|
name: this.user.name,
|
||||||
email: this.user.email,
|
email: this.user.email,
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
"_id": {"$oid":"500000000000000000000002"},
|
"_id": {"$oid":"500000000000000000000002"},
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sample_references": [{
|
"sample_references": [{
|
||||||
"id": "400000000000000000000004",
|
"id": {"$oid":"400000000000000000000004"},
|
||||||
"relation": "granulate to sample"
|
"relation": "granulate to sample"
|
||||||
}],
|
}],
|
||||||
"custom_fields": {
|
"custom_fields": {
|
||||||
@ -73,11 +73,12 @@
|
|||||||
"_id": {"$oid":"500000000000000000000003"},
|
"_id": {"$oid":"500000000000000000000003"},
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"sample_references": [{
|
"sample_references": [{
|
||||||
"id": "400000000000000000000003",
|
"id": {"$oid":"400000000000000000000003"},
|
||||||
"relation": "part to sample"
|
"relation": "part to sample"
|
||||||
}],
|
}],
|
||||||
"custom_fields": {
|
"custom_fields": {
|
||||||
"not allowed for new applications": true
|
"not allowed for new applications": true,
|
||||||
|
"another_field": "is there"
|
||||||
},
|
},
|
||||||
"__v": 0
|
"__v": 0
|
||||||
}
|
}
|
||||||
@ -88,6 +89,12 @@
|
|||||||
"name": "not allowed for new applications",
|
"name": "not allowed for new applications",
|
||||||
"qty": 2,
|
"qty": 2,
|
||||||
"__v": 0
|
"__v": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": {"$oid":"600000000000000000000002"},
|
||||||
|
"name": "another_field",
|
||||||
|
"qty": 1,
|
||||||
|
"__v": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"materials": [
|
"materials": [
|
||||||
|
Reference in New Issue
Block a user