Merge pull request #3 in ~VLE2FE/dfop-api from sample to develop
* commit '16a1cf5ba8255e7537eb1fdb20ee42951bea38af': deleting a material is rejected if it is referenced by a sample implemented more /sample methods
This commit is contained in:
commit
9580af9dd3
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>
|
@ -79,6 +79,8 @@
|
||||
responses:
|
||||
200:
|
||||
$ref: 'api.yaml#/components/responses/Ok'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
403:
|
||||
|
@ -41,8 +41,8 @@
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
put:
|
||||
summary: TODO change sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
summary: change sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin, only maintain and admin are allowed to edit samples created by another user'
|
||||
tags:
|
||||
- /sample
|
||||
security:
|
||||
@ -59,7 +59,7 @@
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'api.yaml#/components/schemas/SampleDetail'
|
||||
$ref: 'api.yaml#/components/schemas/SampleRefs'
|
||||
400:
|
||||
$ref: 'api.yaml#/components/responses/400'
|
||||
401:
|
||||
@ -71,8 +71,8 @@
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
delete:
|
||||
summary: TODO delete sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin'
|
||||
summary: delete sample
|
||||
description: 'Auth: basic, levels: write, maintain, dev, admin, only maintain and admin are allowed to edit samples created by another user'
|
||||
tags:
|
||||
- /sample
|
||||
security:
|
||||
@ -123,7 +123,7 @@
|
||||
|
||||
/sample/notes/fields:
|
||||
get:
|
||||
summary: TODO list all existing field names for custom notes fields
|
||||
summary: list all existing field names for custom notes fields
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
tags:
|
||||
- /sample
|
||||
|
@ -7,7 +7,8 @@
|
||||
"tsc": "tsc",
|
||||
"test": "mocha dist/**/**.spec.js",
|
||||
"start": "tsc && node dist/index.js || exit 1",
|
||||
"dev": "nodemon -e ts,yaml --exec \"npm run start\""
|
||||
"dev": "nodemon -e ts,yaml --exec \"npm run start\"",
|
||||
"loadDev": "node dist/test/loadDev.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
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('disconnected', () => { // reset state on disconnect
|
||||
console.log('Database disconnected');
|
||||
console.info('Database disconnected');
|
||||
this.state.db = 0;
|
||||
done();
|
||||
});
|
||||
process.on('SIGINT', () => { // close connection when app is terminated
|
||||
mongoose.connection.close(() => {
|
||||
console.log('Mongoose default connection disconnected through app termination');
|
||||
console.info('Mongoose default connection disconnected through app termination');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
mongoose.connection.once('open', () => {
|
||||
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;
|
||||
done();
|
||||
});
|
||||
@ -90,13 +90,7 @@ export default class db {
|
||||
|
||||
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
||||
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
||||
for(let i in json.collections[collectionName]) { // convert $oid fields to actual ObjectIds
|
||||
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);
|
||||
}
|
||||
})
|
||||
}
|
||||
json.collections[collectionName] = this.oidResolve(json.collections[collectionName]);
|
||||
this.state.db.createCollection(collectionName, (err, collection) => {
|
||||
collection.insertMany(json.collections[collectionName], () => { // insert JSON data
|
||||
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') {
|
||||
console.log('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
||||
console.info('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content);
|
||||
f();
|
||||
}
|
||||
else { // dev
|
||||
|
@ -8,7 +8,7 @@ import db from './db';
|
||||
|
||||
|
||||
// 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
|
||||
@ -75,7 +75,7 @@ app.use((err, req, res, ignore) => { // internal server error handling
|
||||
|
||||
// hook up server to 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;
|
@ -1,6 +1,6 @@
|
||||
import should from 'should/as-function';
|
||||
import MaterialModel from '../models/material';
|
||||
import TestHelper from "../helpers/test";
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
describe('/material', () => {
|
||||
@ -171,14 +171,54 @@ describe('/material', () => {
|
||||
res: {status: 'Material name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong material properties', done => {
|
||||
it('rejects a wrong mineral property', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
req: {mineral: 'x'},
|
||||
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 => {
|
||||
@ -231,20 +271,28 @@ describe('/material', () => {
|
||||
it('deletes the material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000001',
|
||||
url: '/material/100000000000000000000002',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({status: 'OK'});
|
||||
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
|
||||
MaterialModel.findById('100000000000000000000002').lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
should(data).be.null();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects deleting a material referenced by samples');
|
||||
it('rejects deleting a material referenced by samples', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/material/100000000000000000000004',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
res: {status: 'Material still in use'}
|
||||
})
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
@ -347,24 +395,94 @@ describe('/material', () => {
|
||||
res: {status: 'Material name already taken'}
|
||||
});
|
||||
});
|
||||
it('rejects wrong material properties', done => {
|
||||
it('rejects a missing 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: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
req: {supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
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, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510'},
|
||||
res: {status: 'Invalid body format'}
|
||||
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', 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 => {
|
||||
|
@ -2,7 +2,10 @@ import express from 'express';
|
||||
|
||||
import MaterialValidate from './validate/material';
|
||||
import MaterialModel from '../models/material'
|
||||
import SampleModel from '../models/sample';
|
||||
import IdValidate from './validate/id';
|
||||
import res400 from './validate/res400';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
@ -34,10 +37,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
const {error, value: material} = MaterialValidate.input(req.body, 'change');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (material.hasOwnProperty('name')) {
|
||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||
@ -71,14 +71,21 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => {
|
||||
// check if there are still samples referencing this material
|
||||
SampleModel.find({'material_id': new mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json({status: 'OK'})
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
if (data.length) {
|
||||
return res.status(400).json({status: 'Material still in use'});
|
||||
}
|
||||
MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json({status: 'OK'})
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -87,10 +94,7 @@ router.post('/material/new', (req, res, next) => {
|
||||
|
||||
// validate input
|
||||
const {error, value: material} = MaterialValidate.input(req.body, 'new');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
if (error) return res400(error, res);
|
||||
|
||||
MaterialModel.find({name: material.name}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import TestHelper from "../helpers/test";
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
describe('/', () => {
|
||||
|
@ -2,7 +2,7 @@ import should from 'should/as-function';
|
||||
import SampleModel from '../models/sample';
|
||||
import NoteModel from '../models/note';
|
||||
import NoteFieldModel from '../models/note_field';
|
||||
import TestHelper from "../helpers/test";
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
describe('/sample', () => {
|
||||
@ -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', () => {
|
||||
it('returns the right sample', done => {
|
||||
TestHelper.request(server, done, {
|
||||
@ -209,7 +590,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -219,7 +600,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -229,7 +610,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -239,7 +620,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -249,7 +630,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -259,7 +640,7 @@ describe('/sample', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
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'}]}},
|
||||
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 => {
|
||||
|
@ -2,11 +2,12 @@ import express from 'express';
|
||||
|
||||
import SampleValidate from './validate/sample';
|
||||
import NoteFieldValidate from './validate/note_field';
|
||||
import res400 from './validate/res400';
|
||||
import SampleModel from '../models/sample'
|
||||
import MaterialModel from '../models/material';
|
||||
import NoteModel from '../models/note';
|
||||
import NoteFieldModel from '../models/note_field';
|
||||
|
||||
import IdValidate from './validate/id';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
@ -20,66 +21,119 @@ router.get('/samples', (req, res, next) => {
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
router.post('/sample/new', (req, res, next) => {
|
||||
router.put('/sample/' + IdValidate.parameter(), (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 res.status(400).json({status: 'Invalid body format'});
|
||||
}
|
||||
const {error, value: sample} = SampleValidate.input(req.body, 'change');
|
||||
if (error) return res400(error, res);
|
||||
|
||||
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 (!data) { // could not find material_id
|
||||
return res.status(400).json({status: 'Material not available'});
|
||||
if (!sampleData) {
|
||||
return res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
if (!data.numbers.find(e => e.color === sample.color)) { // color for material not specified
|
||||
return res.status(400).json({status: 'Color not available for material'});
|
||||
}
|
||||
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'});
|
||||
}
|
||||
// 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;
|
||||
|
||||
if (sample.notes.sample_references.length > 0) { // validate 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) return next(err);
|
||||
if (!data) {
|
||||
return res.status(400).json({status: 'Sample reference not available'});
|
||||
}
|
||||
referencesCount --;
|
||||
if (referencesCount <= 0) {
|
||||
f();
|
||||
}
|
||||
if (sample.hasOwnProperty('number') && sample.number !== sampleData.number) {
|
||||
if (!await numberCheck(sample, res, next)) return;
|
||||
}
|
||||
if (sample.hasOwnProperty('material_id')) {
|
||||
if (!await materialCheck(sample, res, next)) return;
|
||||
}
|
||||
else if (sample.hasOwnProperty('color')) {
|
||||
if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
|
||||
}
|
||||
|
||||
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 {
|
||||
f();
|
||||
}
|
||||
|
||||
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()));
|
||||
});
|
||||
});
|
||||
res.json({status: 'OK'});
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
console.log(sample);
|
||||
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) => {
|
||||
@ -95,15 +149,69 @@ router.get('/sample/notes/fields', (req, res, next) => {
|
||||
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 => {
|
||||
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 (!data) { // new field
|
||||
new NoteFieldModel({name: field, qty: 1}).save(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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import should from 'should/as-function';
|
||||
import TemplateTreatmentModel from '../models/treatment_template';
|
||||
import TemplateMeasurementModel from '../models/measurement_template';
|
||||
import TestHelper from "../helpers/test";
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
describe('/template', () => {
|
||||
@ -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, {
|
||||
method: 'put',
|
||||
url: '/template/treatment/heat%20aging',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'time'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
req: {parameters: [{name: 'time', range: {min: 1}}]},
|
||||
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 => {
|
||||
@ -209,7 +249,7 @@ describe('/template', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -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, {
|
||||
method: 'put',
|
||||
url: '/template/measurement/vz',
|
||||
url: '/template/measurement/spectrum2',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
req: {parameters: [{name: 'vz'}]},
|
||||
res: {status: 'Invalid body format'}
|
||||
req: {parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]},
|
||||
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 => {
|
||||
@ -493,7 +573,7 @@ describe('/template', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
|
@ -3,6 +3,7 @@ import express from 'express';
|
||||
import TemplateValidate from './validate/template';
|
||||
import TemplateTreatmentModel from '../models/treatment_template';
|
||||
import TemplateMeasurementModel from '../models/measurement_template';
|
||||
import res400 from './validate/res400';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
@ -41,10 +42,7 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next
|
||||
if (err) next (err);
|
||||
const templateState = data? 'change': 'new';
|
||||
const {error, value: template} = TemplateValidate.input(req.body, templateState);
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (template.hasOwnProperty('name') && template.name !== req.params.name) {
|
||||
collectionModel.find({name: template.name}).lean().exec((err, data) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import should from 'should/as-function';
|
||||
import UserModel from '../models/user';
|
||||
import TestHelper from "../helpers/test";
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
describe('/user', () => {
|
||||
@ -224,7 +224,7 @@ describe('/user', () => {
|
||||
req: {level: 'read'}
|
||||
}).end((err, res) => {
|
||||
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) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.lengthOf(1);
|
||||
@ -267,7 +267,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -277,7 +277,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -287,7 +287,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -515,7 +515,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -525,7 +525,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -535,7 +535,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
@ -545,7 +545,7 @@ describe('/user', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 400,
|
||||
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 => {
|
||||
|
@ -5,6 +5,7 @@ import bcrypt from 'bcryptjs';
|
||||
import UserValidate from './validate/user';
|
||||
import UserModel from '../models/user';
|
||||
import mail from '../helpers/mail';
|
||||
import res400 from './validate/res400';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -46,10 +47,7 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // thi
|
||||
username = req.params.username;
|
||||
}
|
||||
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (user.hasOwnProperty('pass')) {
|
||||
user.pass = bcrypt.hashSync(user.pass, 10);
|
||||
@ -122,10 +120,7 @@ router.post('/user/new', (req, res, next) => {
|
||||
|
||||
// validate input
|
||||
const {error, value: user} = UserValidate.input(req.body, 'new');
|
||||
if (error) {
|
||||
res.status(400).json({status: 'Invalid body format'});
|
||||
return;
|
||||
}
|
||||
if (error) return res400(error, res);
|
||||
|
||||
// check that user does not already exist
|
||||
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 {
|
||||
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 () {
|
||||
return this.id;
|
||||
|
@ -31,9 +31,11 @@ export default class MaterialValidate { // validate input for material
|
||||
numbers: joi.array()
|
||||
.items(joi.object({
|
||||
color: joi.string()
|
||||
.max(128),
|
||||
.max(128)
|
||||
.required(),
|
||||
number: joi.number()
|
||||
.min(0)
|
||||
.required()
|
||||
}))
|
||||
};
|
||||
|
||||
@ -46,7 +48,7 @@ export default class MaterialValidate { // validate input for material
|
||||
mineral: this.material.mineral.required(),
|
||||
glass_fiber: this.material.glass_fiber.required(),
|
||||
carbon_fiber: this.material.carbon_fiber.required(),
|
||||
numbers: this.material.numbers
|
||||
numbers: this.material.numbers.required()
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
|
@ -1,15 +1,15 @@
|
||||
import joi from '@hapi/joi';
|
||||
import Joi from '@hapi/joi';
|
||||
|
||||
export default class NoteFieldValidate {
|
||||
private static note_field = {
|
||||
name: joi.string()
|
||||
name: Joi.string()
|
||||
.max(128),
|
||||
|
||||
qty: joi.number()
|
||||
qty: Joi.number()
|
||||
};
|
||||
|
||||
static output (data) {
|
||||
const {value, error} = joi.object({
|
||||
const {value, error} = Joi.object({
|
||||
name: this.note_field.name,
|
||||
qty: this.note_field.qty
|
||||
}).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';
|
||||
|
||||
export default class SampleValidate {
|
||||
private static sample = {
|
||||
number: joi.string()
|
||||
number: Joi.string()
|
||||
.max(128),
|
||||
|
||||
color: joi.string()
|
||||
color: Joi.string()
|
||||
.max(128),
|
||||
|
||||
type: joi.string()
|
||||
type: Joi.string()
|
||||
.max(128),
|
||||
|
||||
batch: joi.string()
|
||||
batch: Joi.string()
|
||||
.max(128)
|
||||
.allow(''),
|
||||
|
||||
notes: joi.object({
|
||||
comment: joi.string()
|
||||
notes: Joi.object({
|
||||
comment: Joi.string()
|
||||
.max(512),
|
||||
|
||||
sample_references: joi.array()
|
||||
.items(joi.object({
|
||||
sample_references: Joi.array()
|
||||
.items(Joi.object({
|
||||
id: IdValidate.get(),
|
||||
|
||||
relation: joi.string()
|
||||
relation: Joi.string()
|
||||
.max(128)
|
||||
})),
|
||||
|
||||
custom_fields: joi.object()
|
||||
.pattern(/.*/, joi.alternatives()
|
||||
custom_fields: Joi.object()
|
||||
.pattern(/.*/, Joi.alternatives()
|
||||
.try(
|
||||
joi.string().max(128),
|
||||
joi.number(),
|
||||
joi.boolean(),
|
||||
joi.date()
|
||||
Joi.string().max(128),
|
||||
Joi.number(),
|
||||
Joi.boolean(),
|
||||
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)
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
number: this.sample.number.required(),
|
||||
color: this.sample.color.required(),
|
||||
type: this.sample.type.required(),
|
||||
@ -53,7 +53,14 @@ export default class SampleValidate {
|
||||
}).validate(data);
|
||||
}
|
||||
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 {
|
||||
return{error: 'No parameter specified!', value: {}};
|
||||
@ -62,7 +69,7 @@ export default class SampleValidate {
|
||||
|
||||
static output (data) {
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
const {value, error} = Joi.object({
|
||||
_id: IdValidate.get(),
|
||||
number: this.sample.number,
|
||||
color: this.sample.color,
|
||||
|
@ -1,32 +1,32 @@
|
||||
import joi from '@hapi/joi';
|
||||
import Joi from '@hapi/joi';
|
||||
import globals from '../../globals';
|
||||
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class UserValidate { // validate input for user
|
||||
private static user = {
|
||||
name: joi.string()
|
||||
name: Joi.string()
|
||||
.alphanum()
|
||||
.lowercase()
|
||||
.max(128),
|
||||
|
||||
email: joi.string()
|
||||
email: Joi.string()
|
||||
.email({minDomainSegments: 2})
|
||||
.lowercase()
|
||||
.max(128),
|
||||
|
||||
pass: joi.string()
|
||||
pass: Joi.string()
|
||||
.pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
|
||||
.max(128),
|
||||
|
||||
level: joi.string()
|
||||
level: Joi.string()
|
||||
.valid(...globals.levels),
|
||||
|
||||
location: joi.string()
|
||||
location: Joi.string()
|
||||
.alphanum()
|
||||
.max(128),
|
||||
|
||||
device_name: joi.string()
|
||||
device_name: Joi.string()
|
||||
.allow('')
|
||||
.max(128),
|
||||
};
|
||||
@ -35,7 +35,7 @@ export default class UserValidate { // validate input for user
|
||||
|
||||
static input (data, param) {
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.user.name.required(),
|
||||
email: this.user.email.required(),
|
||||
pass: this.user.pass.required(),
|
||||
@ -45,7 +45,7 @@ export default class UserValidate { // validate input for user
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
pass: this.user.pass,
|
||||
@ -54,7 +54,7 @@ export default class UserValidate { // validate input for user
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'changeadmin') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
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
|
||||
data = IdValidate.stringify(data);
|
||||
const {value, error} = joi.object({
|
||||
const {value, error} = Joi.object({
|
||||
_id: IdValidate.get(),
|
||||
name: this.user.name,
|
||||
email: this.user.email,
|
||||
|
@ -61,7 +61,7 @@
|
||||
"_id": {"$oid":"500000000000000000000002"},
|
||||
"comment": "",
|
||||
"sample_references": [{
|
||||
"id": "400000000000000000000004",
|
||||
"id": {"$oid":"400000000000000000000004"},
|
||||
"relation": "granulate to sample"
|
||||
}],
|
||||
"custom_fields": {
|
||||
@ -73,11 +73,12 @@
|
||||
"_id": {"$oid":"500000000000000000000003"},
|
||||
"comment": "",
|
||||
"sample_references": [{
|
||||
"id": "400000000000000000000003",
|
||||
"id": {"$oid":"400000000000000000000003"},
|
||||
"relation": "part to sample"
|
||||
}],
|
||||
"custom_fields": {
|
||||
"not allowed for new applications": true
|
||||
"not allowed for new applications": true,
|
||||
"another_field": "is there"
|
||||
},
|
||||
"__v": 0
|
||||
}
|
||||
@ -88,6 +89,12 @@
|
||||
"name": "not allowed for new applications",
|
||||
"qty": 2,
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"600000000000000000000002"},
|
||||
"name": "another_field",
|
||||
"qty": 1,
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
|
@ -28,7 +28,7 @@ export default class TestHelper {
|
||||
server = require('../index');
|
||||
db.drop(err => { // reset database
|
||||
if (err) return done(err);
|
||||
db.loadJson(require('../test/db.json'), done);
|
||||
db.loadJson(require('./db.json'), done);
|
||||
});
|
||||
return server
|
||||
}
|
12
src/test/loadDev.ts
Normal file
12
src/test/loadDev.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import db from '../db';
|
||||
|
||||
db.connect('dev', () => {
|
||||
console.info('dropping data...');
|
||||
db.drop(() => { // reset database
|
||||
console.info('loading data...');
|
||||
db.loadJson(require('./db.json'), () => {
|
||||
console.info('done');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user