Files
svc-api-x/v1/resources/instance_operation_cfs_param_ls.cfc
T
2025-04-21 12:23:15 +03:00

398 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams">
<!--- Вот здесь мы могли бы строить URI /instances/{instanceUid}/instanceOperations/{instanceOperationUid}/CfsParams, но это создает избыточность, у нас простые суррогатные первичные ключи. С другой стороны, контекст инстанса и операции у клиента всегда имеется. Но стоит ли передавать избыточную информацию? Это иногда провоцирует путаницу. Для POST мы можем передать необходимый контекст в теле запроса, а можем в URL.
В данном случае никакого смысла смотреть на все CFS параметры нет, только в рамках операции
Но: мы не хотели бы менять положение сущности в дереве. Либо она в корне, либо она ниже. И получится, когда мы выбираем один параметр, нам ни к чему контекст - мы к нему однозначно адресуемся.
Проголосуем за отсутстие избыточности?
--->
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<!---спецификация полей, пригодных для фильтрации --->
<cfset this.fieldsSpec={
dt_created={prefix="iop", type="date"},
svc_operation_cfs_param={prefix="sop", type="string"}
<!--- ,
dt_updated={prefix="e", type="date"},
service_id={prefix="e", type="integer"},
svc={prefix="v", type="string"},
is_test={prefix="e", type="boolean"} --->
}
/>
<cffunction name="get" hint="Список CFS параметров">
<cfargument name="pageSize" type="string" hint="type:integer" default="1000"/> <!--- *** тут на самом деле есть лимит --->
<cfargument name="page" type="string" hint="type:integer" default="1"/>
<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>
<cfargument name="instanceOperationUid" type="string" required=false hint="type:guid"/>
<!---parse and validate request parameters--->
<cftry>
<!--- *** Добавить проверку ссылочной целостности --->
<cfif !this.helper.keyExistsAndValid(arguments,"instanceOperationUid","guid")>
<cfthrow type="invalidParamValue" message="Missing required parameter" detail="instanceOperationUid is missing or not a valid GUID"/>
</cfif>
<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
<cfset this.helper.validateField(arguments, "page", "integer")/>
<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
<!--- <cfset var order=this.helper.parseOrderBy(this.fieldsSpec, arguments.orderBy)/> --->
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset var out={}/>
<cfset var maxrows=arguments.pageSize*arguments.page/>
<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
<cfquery name="local.qRead" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">
<m:field title="instance_operation_cfs_param_uid">iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
<m:field title="instance_operation_uid">iop.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field title="svc_operation_cfs_param_id">iop.svc_operation_cfs_param_id</m:field>
<m:field title="param_value">case when sop.is_sensitive then '********' else iop.param_value end as param_value </m:field>
<m:field>iop.note</m:field>
<m:field>to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field>iop.creator_id</m:field>
<m:field>u.login as creator_login</m:field>
<m:field>u.shortname as creator_shortname</m:field>
<m:field>sop.svc_operation_cfs_param</m:field>
<m:field>sop.sort</m:field>
<m:field formatter=#request.castToBool#>sop.is_sensitive</m:field>
</m:field_set>
from instance_operation_cfs_param iop
join instance_operation io on (iop.instance_operation_uid=io.instance_operation_uid)
join instance e on (io.instance_uid=e.instance_uid)
join specification_item si on (e.specification_item_id=si.specification_item_id)
join svc_operation_cfs_param sop on (iop.svc_operation_cfs_param_id=sop.svc_operation_cfs_param_id)
left outer join usr u on (iop.creator_id=u.usr_id)
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
<m:filter_build filter=#filter#/>
order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/><!---no sort length limit--->
--limit #maxrows#
</cfquery>
<cfquery name="local.qTotal">
select count(*) as cnt
from instance_operation_cfs_param iop
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
</cfquery>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.total"=#local.qTotal.cnt#/>
<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
<cfset "out.pageSize"=#arguments.pageSize*1#/>
<cfset "out.page"=#arguments.page*1#/>
<cfset "out.orderBy"=#arguments.orderBy#/>
<cfset var resultCollection=[]/>
<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
<cfset var rec={}/>
<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
<cfset arrayAppend(resultCollection, rec)/>
</cfloop>
<cfset "out.size"=#arrayLen(resultCollection)#/>
<cfset "out.results"=#resultCollection#/>
<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>
<!---<cfset "out.sql"=#local.result.sql#/>--->
<cfreturn representationOf(out)/>
</cffunction>
<cffunction name="post" hint="Создание нового CFS параметра операции.">
<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
<cfargument name="svcOperationCfsParamId" type="string" required=true hint="type:integer"/>
<cfargument name="paramValue" type="string" required=true hint="type:string ***Валидацию пока не проводим, а надо"/>
<cfargument name="note" type="string" required=false default="" hint="type:string description: комментарий пользователя"/>
<cftry>
<cfset this.helper.validateField(arguments, "instanceOperationUid", "guid")/>
<cfset this.helper.validateField(arguments, "svcOperationCfsParamId", "integer")/>
<!--- Еще возможна ошибка, что мы подадим параметр не от своей операции или сервиса - он останется невидим для всех --->
<cfset checkParam(arguments.svcOperationCfsParamId, arguments.paramValue)/>
<cfset checkResourceRealmAccess(arguments.instanceOperationUid, arguments.svcOperationCfsParamId, arguments.paramValue)/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset local={}/>
<cfset local.instanceOperationCfsParamUid=#createGUID()#/>
<cftry>
<cfquery name="local.qCheck" result="local.result">
select io.dt_submit, io.dt_finish, io.submit_result
from instance_operation io
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
</cfquery>
<cfif left(local.qCheck.submit_result,1) EQ "2" AND len(local.qCheck.dt_finish) EQ 0><!--- 201 etc --->
<cfreturn representationOf(this.helper.formatMessage("Operation already started", "Parameter creation disabled for started operation")).withStatus(422)/>
</cfif>
<!--- Проверка: параметр от нашего ли сервиса *** и операции --->
<cfquery name="local.qInstanceService" result="local.result">
select e.service_id, io.operation
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
</cfquery>
<cfquery name="local.qTemplateSvc" result="local.result">
select so.svc_id, so.operation
from svc_operation_cfs_param sop
join svc_operation so on (sop.svc_operation_id=so.svc_operation_id)
where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
</cfquery>
<cfif local.qInstanceService.service_id NEQ local.qTemplateSvc.svc_id>
<cfreturn representationOf(this.helper.formatMessage("Invalid CFS parameter", "Parameter specified does not belong to this service")).withStatus(400)/>
</cfif>
<cfif local.qInstanceService.operation NEQ local.qTemplateSvc.operation>
<cfreturn representationOf(this.helper.formatMessage("Invalid CFS parameter", "Parameter specified does not belong to this operation")).withStatus(400)/>
</cfif>
<cfquery name="local.qSave">
insert into instance_operation_cfs_param (
instance_operation_cfs_param_uid,instance_operation_uid,svc_operation_cfs_param_id,param_value,note,dt_created,creator_id
) values (
<cfqueryparam cfsqltype="cf_sql_other" value="#local.instanceOperationCfsParamUid#"/>
,<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.paramValue#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.note#"/>
,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
);
</cfquery>
<!--- <cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch> --->
<cfcatch type="any">
<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/>
</cfcatch>
</cftry>
<cfreturn noData()
.withHeaders({location: "./#local.instanceOperationCfsParamUid#"})
.withStatus(201, "Created")
/>
</cffunction>
<cffunction name="checkParam" returnType="void">
<!--- проверка параметров по метаданным, независимо от семантики самого параметра --->
<cfargument name="svcOperationCfsParamId" type="numeric" required = true/>
<cfargument name="paramValue" required = true/>
<cfargument name="instanceOperationCfsParamUid" type="guid" default="00000000-0000-0000-0000-000000000000"/><!--- используется при проверке уникальности, чтобы не сравнивать с собой --->
<!--- *** Вероятно, стоит возвращать конкретную ругань --->
<!--- принадлежит ли параметр данному сервису, проверяется выше --->
<!--- получаем метаданные параметра --->
<cfquery name="qSvcOperationCfsParam">
select sop.svc_operation_cfs_param_id
,sop.svc_operation_cfs_param
,sop.value_list
,sop.data_type
,sop.is_required
,sop.maxlength
,sop.minlength
,sop.regex
,sop.unique_scope
,sop.maxvalue
,sop.minvalue
from svc_operation_cfs_param sop
where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
</cfquery>
<cfif qSvcOperationCfsParam.is_required GT 0 AND len(arguments.paramValue) EQ 0>
<cfthrow type="invalidParamValue" message="Missing required parameter (#qSvcOperationCfsParam.svc_operation_cfs_param#)"/>
<cfelseif len(arguments.paramValue) AND NOT validateDataType(arguments.paramValue,qSvcOperationCfsParam.data_type)>
<cfthrow type="invalidParamValue" message="Invalid format (#qSvcOperationCfsParam.svc_operation_cfs_param#)"/>
</cfif>
<cfif len(arguments.paramValue) AND listLen(qSvcOperationCfsParam.value_list) GT 0 AND NOT listFind(qSvcOperationCfsParam.value_list, arguments.paramValue)>
<cfthrow type="invalidParamValue" message="Значение параметра #qSvcOperationCfsParam.svc_operation_cfs_param# отсутствует в списке '#qSvcOperationCfsParam.value_list#'" detail="listLen:#listLen(qSvcOperationCfsParam.value_list)#"/>
</cfif>
<cfif len(arguments.paramValue) AND qSvcOperationCfsParam.maxlength GT 0 AND len(arguments.paramValue) GT qSvcOperationCfsParam.maxlength>
<cfthrow type="invalidParamValue" message="Длина #qSvcOperationCfsParam.svc_operation_cfs_param# превышает #qSvcOperationCfsParam.maxlength#"/>
</cfif>
<cfif len(arguments.paramValue) AND qSvcOperationCfsParam.minlength GT 0 AND len(arguments.paramValue) LT qSvcOperationCfsParam.minlength>
<cfthrow type="invalidParamValue" message="Длина #qSvcOperationCfsParam.svc_operation_cfs_param# меньше #qSvcOperationCfsParam.minlength#"/>
</cfif>
<cfif len(arguments.paramValue) AND len(qSvcOperationCfsParam.regex) AND reFind(qSvcOperationCfsParam.regex,arguments.paramValue) EQ 0>
<cfthrow type="invalidParamValue" message='Parameter "#qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" does not match pattern "#qSvcOperationCfsParam.regex#" '/>
</cfif>
<cfif isNumeric(arguments.paramValue) AND isNumeric(qSvcOperationCfsParam.maxvalue) AND arguments.paramValue GT qSvcOperationCfsParam.maxvalue>
<cfthrow type="invalidParamValue" message='Parameter "#qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" is greater than "#qSvcOperationCfsParam.maxvalue#" '/>
</cfif>
<cfif isNumeric(arguments.paramValue) AND isNumeric(qSvcOperationCfsParam.minvalue) AND arguments.paramValue LT qSvcOperationCfsParam.minvalue>
<cfthrow type="invalidParamValue" message='Parameter "#qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" is less than "#qSvcOperationCfsParam.minvalue#" '/>
</cfif>
<!--- *** интересно, как мы ухитряемся проверить уникальность параметра, если он относится к операции, а не к экземпляру. Нам-то нужна уникальность для стейта (и то, скорее, единовременная, и интересовать будут только успешные операции) --->
<!--- ****** Внимание! Этот код сравнивает параметры одноименных операций, но уникальность должна быть и между create-modify и т.п. --->
<cfset checkUniqueness(
arguments.svcOperationCfsParamId,
qSvcOperationCfsParam.svc_operation_cfs_param,
arguments.paramValue,
qSvcOperationCfsParam.unique_scope,
arguments.instanceOperationCfsParamUid
)/>
</cffunction>
<cffunction name="checkUniqueness" returnType="void">
<!--- использую void, потому что с исключением проще передавать информацию о конкретном характере ошибки. Чем возвращать объект ошибки - проше бросить исключение --->
<cfargument name="svcOperationCfsParamId" type="numeric"/>
<cfargument name="svcOperationCfsParamName" type="string"/><!--- нужен только для формирования текста сообщения об ошибке --->
<cfargument name="paramValue" type="string"/>
<cfargument name="scope" type="string"/>
<cfargument name="instanceOperationCfsParamUid" type="guid"/>
<!--- request.usr_id passed implicitly --->
<cfswitch expression=#qSvcOperationCfsParam.unique_scope#>
<cfcase value="provider">
<cfquery name="qUnique">
select count(*) as cnt
from instance_operation_cfs_param p
join instance_operation o on (p.instance_operation_uid=o.instance_operation_uid)
join instance e on (o.instance_uid=e.instance_uid)
where p.param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.paramValue#/>
AND p.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId#/>/*limit to the current svc param*/
<cfif arguments.instanceOperationCfsParamUid NEQ "00000000-0000-0000-0000-000000000000">
<!--- не решаюсь оставить эту проверку на БД, потому что наличие записи с 00000000-0000-0000-0000-000000000000 может все сломать --->
AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationCfsParamUid#/>/*exclude itself*/
</cfif>
AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) IS NOT TRUE /*ignore deleted instances*/
</cfquery><!--- ****** здесь может таиться трудно понимаемый баг с отсутствием записи --->
<!--- <cfdump var=#qUnique#/><cfabort/> --->
<cfif qUnique.cnt GT 0>
<cfthrow type="invalidParamValue" message='#arguments.svcOperationCfsParamName# = "#arguments.paramValue#" is not unique in the provider scope'/>
</cfif>
</cfcase>
<cfcase value="tenant">
<cfquery name="qUnique">
select count(*) as cnt
from instance_operation_cfs_param p
join instance_operation o on (p.instance_operation_uid=o.instance_operation_uid)
join instance e on (o.instance_uid=e.instance_uid)
join specification_item i on (e.specification_item_id=i.specification_item_id)
join specification s on (i.specification_id=s.specification_id)
join contract d on (s.contract_id=d.contract_id)
join contragent k on (d.contragent_id=k.contragent_id)
join usr u on (k.contragent_id=u.contragent_id)
where p.param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.paramValue#/>
AND svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId#/>/*limit to the current svc param*/
AND u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#request.usr_id#/>/*limit to the contragent of current user*/
<cfif arguments.instanceOperationCfsParamUid NEQ "00000000-0000-0000-0000-000000000000">
<!--- не решаюсь оставить эту проверку на БД, потому что наличие записи с 00000000-0000-0000-0000-000000000000 может все сломать --->
AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationCfsParamUid#/>/*exclude itself*/
</cfif>
AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) IS NOT TRUE /*ignore deleted instances*/
</cfquery><!--- ****** здесь может таиться трудно понимаемый баг с отсутствием записи --->
<!--- <cfdump var=#qUnique#/> --->
<cfif qUnique.cnt GT 0>
<cfthrow type="invalidParamValue" message='#arguments.svcOperationCfsParamName# = "#arguments.paramValue#" is not unique in the tenant scope'/>
</cfif>
</cfcase>
<cfdefaultcase></cfdefaultcase>
</cfswitch>
</cffunction>
<cffunction name="validateDataType" returnType="boolean">
<cfargument name="value" type="string"/>
<cfargument name="dataType" type="string"/>
<!--- Что возвращать-то?
Идею бросать исключение при некорректном вводе надо отринуть. Все введенные параметры надо обработать и не терять --->
<cfswitch expression=#arguments.dataType#>
<cfcase value=",string">
<cfreturn true/>
</cfcase>
<cfcase value="boolean">
<cfreturn IsBoolean(arguments.value)/><!--- можно было сделать и единообразно --->
</cfcase>
<cfcase value="integer">
<cfreturn isValid("integer",arguments.value)/>
</cfcase>
<cfcase value="integer &gt; 0">
<cfreturn isValid("integer",arguments.value) AND arguments.value GT 0/>
</cfcase>
<cfcase value="integer &gt;= 0">
<cfreturn isValid("integer",arguments.value) AND arguments.value GE 0/>
</cfcase>
<cfcase value="numeric">
<cfreturn isNumeric(arguments.value)/>
</cfcase>
<cfcase value="uuid">
<cfreturn isValid("guid",arguments.value)/><!--- *** или GUID? Или принимать оба формата GUID UUID и приводить к одному? А как быть с БД--->
</cfcase>
<cfdefaultcase><cfreturn true/>
<cfthrow type="custom" detail="unsupported CFS parameter value type (#arguments.dataType#)"/>
<!--- --->
</cfdefaultcase>
</cfswitch>
<!--- --->
</cffunction>
<cffunction name="checkResourceRealmAccess" returnType="void">
<!---*** В отличие от других методов, этому небезразлично имя параметра --->
<cfargument name="instanceOperationUid" type="guid"/>
<cfargument name="svcOperationCfsParamId" type="numeric"/>
<cfargument name="paramValue" type="string"/>
<cfset var local={}/>
<cfquery name="local.qSvcOperationCfsParam">
select sop.svc_operation_cfs_param_id
,sop.svc_operation_cfs_param
from svc_operation_cfs_param sop
where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
</cfquery>
<cfif lcase(trim(local.qSvcOperationCfsParam.svc_operation_cfs_param)) NEQ lcase("resourceRealm")>
<cfreturn/>
</cfif>
<cfquery name="local.qContract">
select c.contract_id
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
join specification_item si on (e.specification_item_id=si.specification_item_id)
join specification s on (si.specification_id=s.specification_id)
join contract c on (s.contract_id=c.contract_id)
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/>
</cfquery>
<cfquery name="local.qCheckResourceRealmAccess">
select r.resource_realm_id, r.resource_realm, a.contract_id, a.is_enabled
from resource_realm r
join resource_realm_access a on (r.resource_realm_id=a.resource_realm_id)
where r.resource_realm = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.paramValue#"/>
AND (a.contract_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#local.qContract.contract_id# null=#!isValid("integer",local.qContract.contract_id)#/> OR a.contract_id=0)
AND a.is_enabled
</cfquery>
<cfif local.qCheckResourceRealmAccess.recordCount EQ 0>
<cfthrow message="Resource realm specified in CFS param is not available for the current contract" detail="CFS resource realm #arguments.paramValue# unawailable. Instance operation UID #arguments.instanceOperationUid#. Contract ID #arguments.contractId#"/>
</cfif>
</cffunction>
</cfcomponent>