Function HTTP_Scan_Services(RemainingURL)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   HTTP_Scan_Services

    Description :   Handler program for the HTTP Scan service module.

    Notes       :   All HTTP web services should include the HTTP_SERVICE_SETUP insert. This will provide several useful
                    variables:

                        HTTPMethod              - The HTTP Method (Verb) submitted by the client (e.g., GET, POST, etc.)
                        APIURL                  - The URL for the API entry point (e.g., api.mysite.com/v1).
                        SelfURL                 - The URL path representing the current service.
                        FullEndPointURL         - The URL submitted by the client. This can be the same or longer than
                                                  the SelfURL.
                        NextSegment             - The URL segment immediately following the SelfURL (if any). This
                                                  could contain the name of the next service or it could contain the
                                                  Item ID for the current service (aka resource).
                        CurrentServiceHandler   - The name of this stored procedure.

    Parameters  :
        RemainingURL    [in] -- The remaining portion of the URL that follows the URL that launched this current
                                service. This information is used in the HTTP_SERVICE_SETUP insert to populate other
                                useful variables (see Notes above).
        Response       [out] -- Response to be sent back to the Controller (HTTP_MCP) or requesting procedure. Web API
                                services do not rely upon anything being returned in the response. This is what the
                                various services like SetResponseBody and SetResponseStatus services are for. A response
                                value is only helpful if the developers want to use it for debug purposes.

    History     :   (Date, Initials, Notes)
        07/31/18    dmb     Original programmer.
        09/04/18    dmb     Revamp so Scan is its own resource rather than a parent to a sub-resource. Migrate the Tools
                            API logic into this routine as a starting point. Retrofit several SCANS column name equates
                            to match the changes made to the dictionary.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert APP_INSERTS
$insert HTTP_SERVICE_SETUP
$insert HTTP_INSERTS
$insert SCANS_EQUATES

Declare function    Scan_Services, Database_Services, RTI_CreateGUID
Declare subroutine  Scan_Services, Database_Services

// Assume the current HTTP method is valid until proven otherwise.
ValidMethod     = True$
// Assume the current web service is valid until provent otherwise.
ValidService    = True$
// Assume no HTTP methods are valid until proven otherwise.
AllowedMethods  = ''
// A list of all services able to be called from this URL.
AllowedServices = ''

// Handle the HTTP request as needed.
Begin Case
    Case RemainingURL _EQC ''
        // This means the URL ends with /scan, which means this is the end point. The HTTP methods roughly function
        // as follows:
        //
        //  POST    - Creates a new resource. Assumes the server will generate the scan ID, which will be returned in
        //            the HTTP response.
        AllowedMethods  = 'POST,OPTIONS'
        Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then
            On MethodPos GoSub Post, Options
        end else
            ValidMethod = False$
        end

    Case Count(RemainingURL, '/') EQ 0
        // This means the URL ends with /scan/{scanID}. {scanID} is also known as the resource ID. When a resource is
        // closely mapped to a database row (as is the case with this Contacts API), this is where the basic CRUD
        // functionality will be added. The HTTP methods roughly function as follows:
        //
        //  PUT     - Creates* a resource using the Key ID contained in the URL. This is equivalent to the Write
        //            statement in BASIC+. 
        //  GET     - Reads the resource referenced by the Key ID contained in the URL. This is equivalent to the Read
        //            statement in BASIC+.
        //  PUT     - Updates* the resource referenced by the Key ID contained in the URL. This is the exact same
        //            feature defined above. This should make sense since the Write statement in BASIC+ is used to
        //            create and update database rows. Note, the PUT method assumes the entire resource is within the
        //            request body, not just the changes. See the PATCH method below.
        //  DELETE  - Deletes the source referenced by the Key ID contained in the URL. This is equivalent to the Delete
        //            statement in BASIC+.
        //
        //  * Many people use the POST method for creating (and updating) a resource. However, per the HTTP
        //    specification, POST is to be used when creating a new resource that does not yet have a resource ID
        //    (i.e., Key ID). The server determines the Key ID and this is returned to the client for future use.
        //
        // PATCH    - Updates specific properties (e.g., data columns) of the resource referenced by the Key ID
        //            contained in the URL. This is similar in concept to the WriteV statement in BASIC+, although
        //            multiple changes in the resource can be updated with one PATCH method.
        AllowedMethods  = 'POST,GET,PUT,PATCH,DELETE,OPTIONS'
        Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then
            On MethodPos GoSub PostItem, GetItem, PutItem, PatchItem, DeleteItem, OptionsItem
        end else
            ValidMethod = False$
        end

    Case Otherwise$
        ValidService    = False$
End Case

// Resolve any invalid conditions with the HTTP request.
Begin Case
    Case Not(ValidService)
        HTTP_Services('SetResponseStatus', 404, NextSegment : ' is not a valid service request within the ' : CurrentServiceHandler : ' module.')

    Case Not(ValidMethod)
        HTTP_Services('SetResponseStatus', 405, HTTPMethod : ' is not valid for this service.')

        GoSub SetAllowedMethods
End Case

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN                     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Web Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// Post
//
// Attempts to create a new scan. Creating a new scan follows these guidelines:
//
//   - Any unexpected system errors will return a 500 status code (Internal Server Error).
//   - If no errors occur then a 201 (Created) status code is returned. The Content-Location response header will be
//     set to the value of the URL that will allow the client to GET the newly created resource.
//   - If there is an error locking the resource then a 423 status code (Locked) is returned.
//----------------------------------------------------------------------------------------------------------------------
Post:

    ScanID  = Scan_Services('CreateScansRow', 'Lot')
    If Error_Services('NoError') then
        ItemURL     = SelfURL : '/' : ScanID
        StatusCode  = 201
        GoSub CreateHALItem
    end else
        Message = Error_Services('GetMessage')
        HTTP_Services('SetResponseStatus', 500, Message)
    end

return


//----------------------------------------------------------------------------------------------------------------------
// Options
//
// Sets the appropriate response header fields for an OPTIONS request.
//----------------------------------------------------------------------------------------------------------------------
Options:

    GoSub SetCommonOptionResponseHeaders

return


//----------------------------------------------------------------------------------------------------------------------
// PostItem
//
// Attempts to add new scan data to the scan resource.
//----------------------------------------------------------------------------------------------------------------------
PostItem:

    ScanID      = NextSegment

    // The resource will have been put into the POST string.
    Body    = HTTP_Services('GetHTTPPostString')
    If Body NE '' then
        // The POST string will have been encoded so use percent (URL) decoding.
        Body            = HTTP_Services('DecodePercentString', Body)
        ParseResponse   = SRP_JSON(hBody, 'PARSE', Body)
        If (ParseResponse EQ '') then
            ValidScan   = False$    ; // Assume the scan is not valid for now.
            ScanData    = SRP_JSON(hBody, 'GetValue', 'scanData')
            SRP_JSON(hBody, 'Release')
            Scan_Services('ProcessScanData', ScanID, ScanData)
            If Error_Services('NoError') then
                ItemURL     = SelfURL : '/' : ScanID
                StatusCode  = 200   ; // This service assume the resource already exists.
                GoSub CreateHALItem
            end else
                Message     = Error_Services('GetMessage')
                HTTP_Services('SetResponseStatus', 400, Message)
            end
        end else
            HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.')
        end
    end else
        HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.')
    end

return


//----------------------------------------------------------------------------------------------------------------------
// GetItem
//
// Returns the specific resource.
//
// The easiest way to return a resource that is mapped to a database row is to use the GetDatabaseItem service. This
// is being done in the code below. However, to demonstrate how then basic functionality can be extended, there is
// additional code below that will show how to add the Contact resource's image URL to the JSON response.
//----------------------------------------------------------------------------------------------------------------------
GetItem:

    ScanID  = NextSegment
    ItemURL = SelfURL : '/' : ScanID
    ScanRow = Scan_Services('GetScansRow', ScanID)
    If Error_Services('NoError') then
        StatusCode  = 200
        GoSub CreateHALItem
    end else
        Message = Error_Services('GetMessage')
        HTTP_Services('SetResponseStatus', 404, Message)
    end

return


//----------------------------------------------------------------------------------------------------------------------
// PutItem
//
// Attempts to replace the indicated scan resource with the incoming JSON object.
//----------------------------------------------------------------------------------------------------------------------
PutItem:

    ScanID  = NextSegment

    // The resource will have been put into the POST string.
    Body    = HTTP_Services('GetHTTPPostString')
    If Body NE '' then
        // The POST string will have been encoded so use percent (URL) decoding.
        Body            = HTTP_Services('DecodePercentString', Body)
        ParseResponse   = SRP_JSON(hBody, 'PARSE', Body)
        If (ParseResponse EQ '') then
            ScanRow                     = Database_Services('ReadDataRow', 'SCANS', ScanID)
            If Error_Services('NoError') then
                // Existing scan resource is being replaced.
                StatusCode  = 200
            end else
                // Scan resource is being created for the first time.
                StatusCode  = 201
            end
            If ScanRow<SCANS.CREATED_DATE$> EQ '' then ScanRow<SCANS.CREATED_DATE$> = Date()
            If ScanRow<SCANS.CREATED_TIME$> EQ '' then ScanRow<SCANS.CREATED_TIME$> = Time()
            ScanRow<SCANS.EMPLOYEE_ID$>     = SRP_JSON(hBody, 'GetValue', 'employeeID', '')
            ScanRow<SCANS.TOOL_ID$>         = SRP_JSON(hBody, 'GetValue', 'toolID', '')
            ScanRow<SCANS.CASSETTE_IDS$>    = SRP_JSON(hBody, 'GetValue', 'cassetteID', '')
            Accept                          = SRP_JSON(hBody, 'GetValue', 'accept', False$)
            If Accept EQ True$ then
                // Before allowing the accept field to be set to True, verify that all required data has been populated.
                Begin Case
                    Case ScanRow<SCANS.EMPLOYEE_ID$> EQ ''  ; Accept = False$
                    Case ScanRow<SCANS.TOOL_ID$> EQ ''      ; Accept = False$
                    Case ScanRow<SCANS.CASSETTE_IDS$> EQ '' ; Accept = False$
                End Case
            end
            ScanRow<SCANS.ACCEPTED$>    = Accept
            If ScanRow<SCANS.ACCEPTED$> EQ True$ then
                ScanRow<SCANS.ACCEPTED_DATE$>   = Date()
                ScanRow<SCANS.ACCEPTED_TIME$>   = Time()
            end
            Database_Services('WriteDataRow', 'SCANS', ScanID, ScanRow, True$, False$, True$)
            If Error_Services('NoError') then
                ItemURL     = SelfURL : '/' : ScanID
                GoSub CreateHALItem
            end else
                Message = Error_Services('GetMessage')
                HTTP_Services('SetResponseStatus', 500, Message)
            end
            SRP_JSON(hBody, 'Release')
        end else
            HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.')
        end
    end else
        HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.')
    end

return


//----------------------------------------------------------------------------------------------------------------------
// PatchItem
//
// Attempts to update the indicated scan resource with the incoming JSON object. The only permissible field to be
// updated is "accept".
//----------------------------------------------------------------------------------------------------------------------
PatchItem:

    ScanID  = NextSegment

    // First confirm that this is a valid Scan ID.
    jsonScan    = Scan_Services('GetScansRow', ScanID, True$)
    If Error_Services('NoError') then
        // Confirm that all required data has been scanned before allowing the "accept" field to be updated.
        ParseResponse   = SRP_JSON(objResource, 'Parse', jsonScan)
        If ParseResponse EQ '' then
            ScanAcceptable  = SRP_JSON(objResource, 'GetValue', 'scan.acceptable', False$)
            If ScanAcceptable NE True$ then
                ScanNotAcceptableReason = SRP_JSON(objResource, 'GetValue', 'scan.notAcceptableReason')
                Error_Services('Add', ScanNotAcceptableReason)
            end
            SRP_JSON(objResource, 'Release')
        end else
            Error_Services('Add', 'Unable to parse the JSON scan resource.')
        end

        If Error_Services('NoError') then
            // The resource will have been put into the POST string.
            Body    = HTTP_Services('GetHTTPPostString')
            If Body NE '' then
                // The POST string will have been encoded so use percent (URL) decoding.
                Body            = HTTP_Services('DecodePercentString', Body)
                ParseResponse   = SRP_JSON(hBody, 'PARSE', Body)
                If ParseResponse EQ '' then
                    AcceptedStatus  = SRP_JSON(hBody, 'GetValue', 'accepted.status')
                    If AcceptedStatus NE '' then
                        ScansRow                    = Scan_Services('GetScansRow', ScanID, False$)
                        ScansRow<SCANS.ACCEPTED$>   = AcceptedStatus
                        If AcceptedStatus EQ True$ then
                            ScansRow<SCANS.ACCEPTED_DATE$>  = Date()
                            ScansRow<SCANS.ACCEPTED_TIME$>  = Time()
                        end
                        Scan_Services('SetScansRow', ScanID, ScansRow)
                        If Error_Services('NoError') then
                            ItemURL     = SelfURL : '/' : ScanID
                            StatusCode  = 200
                            GoSub CreateHALItem
                        end else
                            Message = Error_Services('GetMessage')
                            HTTP_Services('SetResponseStatus', 500, Message)
                        end
                    end else
                        HTTP_Services('SetResponseStatus', 400, 'accepted.status field is missing from the JSON object.')
                    end
                    SRP_JSON(hBody, 'Release')
                end else
                    HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.')
                end
            end else
                HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.')
            end
        end else
            Message = Error_Services('GetMessage')
            HTTP_Services('SetResponseStatus', 400, Message)
        end
    end else
        Message = Error_Services('GetMessage')
        HTTP_Services('SetResponseStatus', 404, Message)
    end

return


//----------------------------------------------------------------------------------------------------------------------
// DeleteItem
//
// Attempts to delete the resource. Deleting a resource which is a database row follows these guidelines:
//
//   - Any unexpected system errors will return a 500 status code (Internal Server Error).
//   - If no errors occur then a 204 (No Content) status code is returned.
//   - If the resource was already deleted then a 204 (No Content) status code is returned.
//   - If there is an error locking the resource then a 423 status code (Locked) is returned.
//----------------------------------------------------------------------------------------------------------------------
DeleteItem:

    ScanID  = NextSegment

    ScanRow = Database_Services('ReadDataRow', 'SCANS', ScanID)
    If Error_Services('NoError') then
        If ScanRow<SCANS.ACCEPTED$> NE True$ then
            Database_Services('DeleteDataRow', 'SCANS', ScanID, True$, False$)
            If Error_Services('NoError') then
                HTTP_Services('SetResponseStatus', 200)
            end else
                Message = Error_Services('GetMessage')
                HTTP_Services('SetResponseStatus', 500, Message)
            end
        end else
            HTTP_Services('SetResponseStatus', 403, 'This scan is already accepted and cannot be deleted.')
        end
    end else
        Message = Error_Services('GetMessage')
        HTTP_Services('SetResponseStatus', 404, Message)
    end

return


//----------------------------------------------------------------------------------------------------------------------
// OptionsItem
//
// Sets the appropriate response header fields for an OPTIONS request.
//----------------------------------------------------------------------------------------------------------------------
OptionsItem:

    GoSub SetCommonOptionResponseHeaders

return


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// SetCommonOptionResponseHeaders
//
// Sets the response headers that will be common for all OPTIONS methods.
//----------------------------------------------------------------------------------------------------------------------
SetCommonOptionResponseHeaders:

    HTTP_Services('SetResponseHeaderField', 'Access-Control-Allow-Headers', 'authorization', True$)
    HTTP_Services('SetResponseHeaderField', 'Access-Control-Allow-Headers', 'x-authorization', True$)
    HTTP_Services('SetResponseHeaderField', 'Access-Control-Max-Age', 1728000)

    GoSub SetAllowedMethods

return


//----------------------------------------------------------------------------------------------------------------------
// SetAllowedMethods
//
// Sets the Allow response header field as appropriate for the requested URL.
//----------------------------------------------------------------------------------------------------------------------
SetAllowedMethods:

    If AllowedMethods NE '' then
        For Each Method in AllowedMethods using ','
            HTTP_Services('SetResponseHeaderField', 'Allow', Method, True$)
        Next Method
    end

return


//----------------------------------------------------------------------------------------------------------------------
// CreateHALItem
//
// Creates a HAL+JSON object based on the OpenInsight data row representation of the scan.
//----------------------------------------------------------------------------------------------------------------------
CreateHALItem:

    jsonScan    = Scan_Services('ConvertMVScanToJSON', ScanID, '', itemURL)

    If Error_Services('NoError') then
        If SRP_JSON(objResource, 'Parse', jsonScan) EQ '' then
            lastModified    = SRP_JSON(objResource, 'GetValue', 'lastModified')
        end else
            lastModified    = ''
        end
        SRP_JSON(objResource, 'Release')
        HTTP_Services('SetResponseHeaderField', 'Content-Location', ItemURL)
        HTTP_Services('SetResponseHeaderField', 'Last-Modified', lastModified)
        HTTP_Services('SetResponseBody', jsonScan, False$, 'application/hal+json')
        HTTP_Services('SetResponseStatus', StatusCode)
    end else
        Message     = Error_Services('GetMessage')
        HTTP_Services('SetResponseStatus', 500, 'Error in the ' : CurrentServiceHandler : ' service. Message: ': Message)
    end

return

