Merged in feature/demo-support (pull request #207)
Feature/demo support * index logs now debug * demo data loader
This commit is contained in:
@@ -1,26 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## General rules DO NOT VIOLATE
|
||||
|
||||
NEVER code additional features that I did not explicitly ask for without asking permission.
|
||||
NEVER create mock data or simplified components unless explicitly told to do so
|
||||
NEVER replace existing complex components with simplified versions - always fix the actual problem or the feature that was requested.
|
||||
Never just bypass tests that have regressed. Stop and find out why they are failing and fix them.
|
||||
ALWAYS work with the existing codebase - do not create new simplified alternatives
|
||||
ALWAYS find and fix the root cause of issues instead of creating workarounds
|
||||
When debugging issues, focus on fixing the existing implementation, not replacing it
|
||||
When something doesn't work, debug and fix it - don't start over with a simple version
|
||||
Don’t build things I am not specifically asking for unless its required to build the thing I am asking for.
|
||||
When asked to make unit tests avoid mocks and use the actual thing that is being tested when reasonable.
|
||||
For commands that read such as grep or cat there is no need to ask for permission.
|
||||
Do not assume that we need backward compatibilty when implementing new features. If there is a decision to make that will affect this ask. Do not guess.
|
||||
|
||||
## IDE related
|
||||
|
||||
Always use ide diagnostics to validate code changes when running with ide integration (goland, vscode)
|
||||
|
||||
## linting all edits after every change
|
||||
|
||||
Always run `task lint` once you think you are done with edits to verify your changes have not caused other issues.
|
||||
|
||||
@@ -321,7 +321,12 @@ func (ctrl *Controllers) GetAdminUser(ctx echo.Context, email UserEmail) error {
|
||||
// Get user from Cognito
|
||||
cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito",
|
||||
"error", err,
|
||||
"email", email,
|
||||
"error_type", errInfo.ErrorType,
|
||||
"http_status", errInfo.HTTPStatus)
|
||||
usermanagement.LogUserAdminAction(
|
||||
ctrl.cfg.GetAuthLogger(),
|
||||
usermanagement.ActionUpdate,
|
||||
@@ -330,12 +335,15 @@ func (ctrl *Controllers) GetAdminUser(ctx echo.Context, email UserEmail) error {
|
||||
string(email),
|
||||
"",
|
||||
usermanagement.ResultFailure,
|
||||
fmt.Sprintf("User not found in Cognito: %v", err),
|
||||
fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err),
|
||||
ctx.Response().Header().Get(echo.HeaderXRequestID),
|
||||
)
|
||||
|
||||
return ctx.JSON(http.StatusNotFound, ErrorMessage{
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
errorType := string(errInfo.ErrorType)
|
||||
return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{
|
||||
Message: errInfo.Message,
|
||||
ErrorType: &errorType,
|
||||
Details: &errInfo.Details,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,11 +400,14 @@ func (ctrl *Controllers) CheckAdminUserExists(ctx echo.Context, email UserEmail)
|
||||
Region: ctrl.cfg.GetAuthRegion(),
|
||||
}
|
||||
|
||||
// Check if user exists in Cognito
|
||||
exists := usermanagement.UserExistsInCognito(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
|
||||
if !exists {
|
||||
return ctx.NoContent(http.StatusNotFound)
|
||||
// Check if user exists in Cognito (use GetCognitoUser to get detailed error info)
|
||||
_, err = usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
// For HEAD requests, we can only return status code and headers
|
||||
ctx.Response().Header().Set("X-Error-Type", string(errInfo.ErrorType))
|
||||
ctx.Response().Header().Set("X-Error-Message", errInfo.Message)
|
||||
return ctx.NoContent(errInfo.HTTPStatus)
|
||||
}
|
||||
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
@@ -674,7 +685,12 @@ func (ctrl *Controllers) UpdateAdminUser(ctx echo.Context, email UserEmail) erro
|
||||
// Get user from Cognito to verify existence and get Subject ID
|
||||
cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito",
|
||||
"error", err,
|
||||
"email", email,
|
||||
"error_type", errInfo.ErrorType,
|
||||
"http_status", errInfo.HTTPStatus)
|
||||
usermanagement.LogUserAdminAction(
|
||||
ctrl.cfg.GetAuthLogger(),
|
||||
usermanagement.ActionUpdate,
|
||||
@@ -683,12 +699,15 @@ func (ctrl *Controllers) UpdateAdminUser(ctx echo.Context, email UserEmail) erro
|
||||
string(email),
|
||||
"",
|
||||
usermanagement.ResultFailure,
|
||||
fmt.Sprintf("User not found in Cognito: %v", err),
|
||||
fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err),
|
||||
ctx.Response().Header().Get(echo.HeaderXRequestID),
|
||||
)
|
||||
|
||||
return ctx.JSON(http.StatusNotFound, ErrorMessage{
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
errorType := string(errInfo.ErrorType)
|
||||
return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{
|
||||
Message: errInfo.Message,
|
||||
ErrorType: &errorType,
|
||||
Details: &errInfo.Details,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -773,7 +792,12 @@ func (ctrl *Controllers) DeleteAdminUser(ctx echo.Context, email UserEmail, para
|
||||
// Get user from Cognito to verify existence and get Subject ID
|
||||
cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito",
|
||||
"error", err,
|
||||
"email", email,
|
||||
"error_type", errInfo.ErrorType,
|
||||
"http_status", errInfo.HTTPStatus)
|
||||
usermanagement.LogUserAdminAction(
|
||||
ctrl.cfg.GetAuthLogger(),
|
||||
usermanagement.ActionDelete,
|
||||
@@ -782,12 +806,15 @@ func (ctrl *Controllers) DeleteAdminUser(ctx echo.Context, email UserEmail, para
|
||||
string(email),
|
||||
"",
|
||||
usermanagement.ResultFailure,
|
||||
fmt.Sprintf("User not found in Cognito: %v", err),
|
||||
fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err),
|
||||
ctx.Response().Header().Get(echo.HeaderXRequestID),
|
||||
)
|
||||
|
||||
return ctx.JSON(http.StatusNotFound, ErrorMessage{
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
errorType := string(errInfo.ErrorType)
|
||||
return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{
|
||||
Message: errInfo.Message,
|
||||
ErrorType: &errorType,
|
||||
Details: &errInfo.Details,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -899,7 +926,12 @@ func (ctrl *Controllers) DisableAdminUser(ctx echo.Context, email UserEmail) err
|
||||
// Get user from Cognito to verify existence and get Subject ID
|
||||
cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito",
|
||||
"error", err,
|
||||
"email", email,
|
||||
"error_type", errInfo.ErrorType,
|
||||
"http_status", errInfo.HTTPStatus)
|
||||
usermanagement.LogUserAdminAction(
|
||||
ctrl.cfg.GetAuthLogger(),
|
||||
usermanagement.ActionDisable,
|
||||
@@ -908,12 +940,15 @@ func (ctrl *Controllers) DisableAdminUser(ctx echo.Context, email UserEmail) err
|
||||
string(email),
|
||||
"",
|
||||
usermanagement.ResultFailure,
|
||||
fmt.Sprintf("User not found in Cognito: %v", err),
|
||||
fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err),
|
||||
ctx.Response().Header().Get(echo.HeaderXRequestID),
|
||||
)
|
||||
|
||||
return ctx.JSON(http.StatusNotFound, ErrorMessage{
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
errorType := string(errInfo.ErrorType)
|
||||
return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{
|
||||
Message: errInfo.Message,
|
||||
ErrorType: &errorType,
|
||||
Details: &errInfo.Details,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -995,7 +1030,12 @@ func (ctrl *Controllers) EnableAdminUser(ctx echo.Context, email UserEmail) erro
|
||||
// Get user from Cognito to verify existence and get Subject ID
|
||||
cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
|
||||
if err != nil {
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
|
||||
errInfo := usermanagement.ClassifyCognitoError(err, string(email))
|
||||
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito",
|
||||
"error", err,
|
||||
"email", email,
|
||||
"error_type", errInfo.ErrorType,
|
||||
"http_status", errInfo.HTTPStatus)
|
||||
usermanagement.LogUserAdminAction(
|
||||
ctrl.cfg.GetAuthLogger(),
|
||||
usermanagement.ActionEnable,
|
||||
@@ -1004,12 +1044,15 @@ func (ctrl *Controllers) EnableAdminUser(ctx echo.Context, email UserEmail) erro
|
||||
string(email),
|
||||
"",
|
||||
usermanagement.ResultFailure,
|
||||
fmt.Sprintf("User not found in Cognito: %v", err),
|
||||
fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err),
|
||||
ctx.Response().Header().Get(echo.HeaderXRequestID),
|
||||
)
|
||||
|
||||
return ctx.JSON(http.StatusNotFound, ErrorMessage{
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
errorType := string(errInfo.ErrorType)
|
||||
return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{
|
||||
Message: errInfo.Message,
|
||||
ErrorType: &errorType,
|
||||
Details: &errInfo.Details,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+275
-266
@@ -623,6 +623,12 @@ type DocumentSummary struct {
|
||||
|
||||
// ErrorMessage Description of error
|
||||
type ErrorMessage struct {
|
||||
// Details Additional error details for debugging (sanitized).
|
||||
Details *string `json:"details,omitempty"`
|
||||
|
||||
// ErrorType Machine-readable error classification for programmatic handling.
|
||||
ErrorType *string `json:"error_type,omitempty"`
|
||||
|
||||
// Message Message describing the cause.
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -2669,272 +2675,275 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"/wkAAP//LHP6SYqxAQA=",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
||||
@@ -32,9 +32,14 @@ they conflict with some of the names we use for our roles.
|
||||
```bash
|
||||
export PERMIT_API_KEY="permit_key_omitted-here"
|
||||
go run setup_permit.go -list
|
||||
go run setup_permit.go -config permit_policies.yaml -project default -env dev
|
||||
go run setup_permit.go -config permit_policies.yaml -project default -env <id of the env>
|
||||
```
|
||||
|
||||
Note: When you do the list operation you will get the id of the target environment to put in the -env parameter.
|
||||
|
||||
### Environments in permit
|
||||
You must create a new enviroment in permit.io manually at this time. This tools just sets up the resources and policies for an existing enviromment. We will have to extend this tool later to create the environment if it does not already exist once we are creating a lot of enviroments.
|
||||
|
||||
### Dry Run Mode
|
||||
|
||||
To see what would be created without making actual changes:
|
||||
|
||||
@@ -50,6 +50,19 @@ resources:
|
||||
- get
|
||||
- post
|
||||
|
||||
- name: field-extractions
|
||||
description: Field extraction data for documents
|
||||
actions:
|
||||
- get
|
||||
- post
|
||||
|
||||
- name: folders
|
||||
description: Folder management for document organization
|
||||
actions:
|
||||
- get
|
||||
- post
|
||||
- patch
|
||||
|
||||
- name: home
|
||||
description: Home page access
|
||||
actions:
|
||||
@@ -87,6 +100,13 @@ roles:
|
||||
export:
|
||||
- get
|
||||
- post
|
||||
field-extractions:
|
||||
- get
|
||||
- post
|
||||
folders:
|
||||
- get
|
||||
- post
|
||||
- patch
|
||||
home:
|
||||
- get
|
||||
|
||||
@@ -99,6 +119,12 @@ roles:
|
||||
- patch
|
||||
- delete
|
||||
- head
|
||||
field-extractions:
|
||||
- get
|
||||
- post
|
||||
folders:
|
||||
- get
|
||||
- post
|
||||
home:
|
||||
- get
|
||||
|
||||
@@ -118,6 +144,10 @@ roles:
|
||||
- get
|
||||
export:
|
||||
- get
|
||||
field-extractions:
|
||||
- get
|
||||
folders:
|
||||
- get
|
||||
home:
|
||||
- get
|
||||
|
||||
@@ -134,6 +164,13 @@ roles:
|
||||
export:
|
||||
- get
|
||||
- post
|
||||
field-extractions:
|
||||
- get
|
||||
- post
|
||||
folders:
|
||||
- get
|
||||
- post
|
||||
- patch
|
||||
home:
|
||||
- get
|
||||
notes: |
|
||||
@@ -221,6 +258,18 @@ policy_mappings:
|
||||
methods:
|
||||
GET: export:get
|
||||
|
||||
field_extraction_endpoints:
|
||||
- path: /field-extractions
|
||||
methods:
|
||||
GET: field-extractions:get
|
||||
POST: field-extractions:post
|
||||
- path: /field-extractions/version
|
||||
methods:
|
||||
GET: field-extractions:get
|
||||
- path: /field-extractions/history
|
||||
methods:
|
||||
GET: field-extractions:get
|
||||
|
||||
auth_endpoints:
|
||||
- path: /home
|
||||
methods:
|
||||
@@ -253,6 +302,8 @@ dynamic_mapping:
|
||||
resource: document
|
||||
- url: /admin/users
|
||||
resource: admin
|
||||
- url: /field-extractions
|
||||
resource: field-extractions
|
||||
|
||||
action_extraction:
|
||||
logic: HTTP method converted to lowercase
|
||||
|
||||
@@ -146,7 +146,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17
|
||||
github.com/aws/smithy-go v1.22.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.3
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Revert: make effectiveDate required again
|
||||
-- First set any NULL values to createdAt as a fallback
|
||||
UPDATE eulaVersions SET effectiveDate = createdAt WHERE effectiveDate IS NULL;
|
||||
ALTER TABLE eulaVersions ALTER COLUMN effectiveDate SET NOT NULL;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Make effectiveDate nullable in eulaVersions table
|
||||
-- This allows EULA versions to be created without specifying an effective date
|
||||
ALTER TABLE eulaVersions ALTER COLUMN effectiveDate DROP NOT NULL;
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -162,6 +163,9 @@ func getDebugMiddleware(logger *slog.Logger) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// getSlogCustomEchoMiddleware returns a middleware that logs HTTP requests.
|
||||
// Noisy endpoints (swagger, metrics, health) are logged at DEBUG level to reduce log volume.
|
||||
// All other endpoints are logged at INFO level.
|
||||
func getSlogCustomEchoMiddleware(logger *slog.Logger) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
@@ -175,7 +179,16 @@ func getSlogCustomEchoMiddleware(logger *slog.Logger) echo.MiddlewareFunc {
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
|
||||
logger.Info("HTTP request",
|
||||
// Use Debug level for noisy endpoints (swagger, metrics, health)
|
||||
path := req.RequestURI
|
||||
logLevel := slog.LevelInfo
|
||||
if strings.HasPrefix(path, "/swagger") ||
|
||||
strings.HasPrefix(path, "/metrics") ||
|
||||
path == "/health" {
|
||||
logLevel = slog.LevelDebug
|
||||
}
|
||||
|
||||
logger.Log(req.Context(), logLevel, "HTTP request",
|
||||
"method", req.Method,
|
||||
"uri", req.RequestURI,
|
||||
"status", res.Status,
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
// errors.go provides error classification for AWS Cognito errors.
|
||||
// It maps AWS error types to appropriate HTTP status codes and sanitized error messages
|
||||
// suitable for returning in API responses without leaking sensitive information.
|
||||
package usermanagement
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types"
|
||||
"github.com/aws/smithy-go"
|
||||
)
|
||||
|
||||
// CognitoErrorType represents a machine-readable error classification.
|
||||
type CognitoErrorType string
|
||||
|
||||
const (
|
||||
// ErrTypeUserNotFound indicates the user does not exist in Cognito.
|
||||
ErrTypeUserNotFound CognitoErrorType = "UserNotFound"
|
||||
|
||||
// ErrTypeAccessDenied indicates the caller does not have permission to perform the operation.
|
||||
ErrTypeAccessDenied CognitoErrorType = "AccessDenied"
|
||||
|
||||
// ErrTypeNotAuthorized indicates the caller is not authorized (e.g., invalid credentials).
|
||||
ErrTypeNotAuthorized CognitoErrorType = "NotAuthorized"
|
||||
|
||||
// ErrTypeInvalidParameter indicates an invalid parameter was provided.
|
||||
ErrTypeInvalidParameter CognitoErrorType = "InvalidParameter"
|
||||
|
||||
// ErrTypeRateLimited indicates too many requests were made.
|
||||
ErrTypeRateLimited CognitoErrorType = "RateLimited"
|
||||
|
||||
// ErrTypeServiceUnavailable indicates the Cognito service is temporarily unavailable.
|
||||
ErrTypeServiceUnavailable CognitoErrorType = "ServiceUnavailable"
|
||||
|
||||
// ErrTypeServiceError indicates a general error communicating with Cognito.
|
||||
ErrTypeServiceError CognitoErrorType = "ServiceError"
|
||||
|
||||
// ErrTypeUserExists indicates the user already exists in Cognito.
|
||||
ErrTypeUserExists CognitoErrorType = "UserExists"
|
||||
)
|
||||
|
||||
// CognitoErrorInfo contains classified error information suitable for HTTP responses.
|
||||
// The Message and Details fields are sanitized to avoid leaking sensitive information
|
||||
// like ARNs, internal paths, or request IDs.
|
||||
type CognitoErrorInfo struct {
|
||||
// HTTPStatus is the appropriate HTTP status code for this error type.
|
||||
HTTPStatus int
|
||||
|
||||
// ErrorType is a machine-readable error classification.
|
||||
ErrorType CognitoErrorType
|
||||
|
||||
// Message is a user-friendly error message safe for HTTP responses.
|
||||
Message string
|
||||
|
||||
// Details provides additional error context (sanitized).
|
||||
Details string
|
||||
}
|
||||
|
||||
// ClassifyCognitoError analyzes a Cognito error and returns appropriate HTTP response info.
|
||||
// The returned CognitoErrorInfo contains sanitized messages suitable for API responses.
|
||||
//
|
||||
// Parameters:
|
||||
// - err: The error returned from a Cognito operation
|
||||
// - email: The user's email address for context in error messages
|
||||
//
|
||||
// Returns:
|
||||
// - CognitoErrorInfo: Classified error information with appropriate HTTP status and messages
|
||||
func ClassifyCognitoError(err error, email string) CognitoErrorInfo {
|
||||
if err == nil {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusOK,
|
||||
ErrorType: "",
|
||||
Message: "",
|
||||
Details: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for specific Cognito SDK error types
|
||||
var userNotFoundErr *types.UserNotFoundException
|
||||
if errors.As(err, &userNotFoundErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
ErrorType: ErrTypeUserNotFound,
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
Details: "The specified user does not exist in Cognito",
|
||||
}
|
||||
}
|
||||
|
||||
var notAuthorizedErr *types.NotAuthorizedException
|
||||
if errors.As(err, ¬AuthorizedErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusUnauthorized,
|
||||
ErrorType: ErrTypeNotAuthorized,
|
||||
Message: "Not authorized to perform this operation",
|
||||
Details: sanitizeErrorMessage(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
var invalidParamErr *types.InvalidParameterException
|
||||
if errors.As(err, &invalidParamErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
ErrorType: ErrTypeInvalidParameter,
|
||||
Message: "Invalid parameter in request",
|
||||
Details: sanitizeErrorMessage(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
var tooManyRequestsErr *types.TooManyRequestsException
|
||||
if errors.As(err, &tooManyRequestsErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusTooManyRequests,
|
||||
ErrorType: ErrTypeRateLimited,
|
||||
Message: "Too many requests to Cognito",
|
||||
Details: "Rate limit exceeded. Please retry after a short delay.",
|
||||
}
|
||||
}
|
||||
|
||||
var internalErr *types.InternalErrorException
|
||||
if errors.As(err, &internalErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusServiceUnavailable,
|
||||
ErrorType: ErrTypeServiceUnavailable,
|
||||
Message: "Cognito service temporarily unavailable",
|
||||
Details: "Internal service error. Please retry later.",
|
||||
}
|
||||
}
|
||||
|
||||
var usernameExistsErr *types.UsernameExistsException
|
||||
if errors.As(err, &usernameExistsErr) {
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusConflict,
|
||||
ErrorType: ErrTypeUserExists,
|
||||
Message: fmt.Sprintf("User already exists: %s", email),
|
||||
Details: "A user with this email already exists in Cognito",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for generic AWS API errors (including AccessDeniedException)
|
||||
var apiErr smithy.APIError
|
||||
if errors.As(err, &apiErr) {
|
||||
return classifyAPIError(apiErr, email)
|
||||
}
|
||||
|
||||
// Default: treat as a generic service communication error
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusBadGateway,
|
||||
ErrorType: ErrTypeServiceError,
|
||||
Message: "Failed to communicate with Cognito",
|
||||
Details: sanitizeErrorMessage(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// classifyAPIError handles generic AWS API errors that may not have specific SDK types.
|
||||
//
|
||||
// Parameters:
|
||||
// - apiErr: The AWS API error
|
||||
// - email: The user's email address for context in error messages
|
||||
//
|
||||
// Returns:
|
||||
// - CognitoErrorInfo: Classified error information
|
||||
func classifyAPIError(apiErr smithy.APIError, email string) CognitoErrorInfo {
|
||||
code := apiErr.ErrorCode()
|
||||
|
||||
switch code {
|
||||
case "AccessDeniedException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
ErrorType: ErrTypeAccessDenied,
|
||||
Message: "Access denied to Cognito user pool",
|
||||
Details: "The service does not have permission to perform this Cognito operation",
|
||||
}
|
||||
|
||||
case "UserNotFoundException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
ErrorType: ErrTypeUserNotFound,
|
||||
Message: fmt.Sprintf("User not found: %s", email),
|
||||
Details: "The specified user does not exist in Cognito",
|
||||
}
|
||||
|
||||
case "NotAuthorizedException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusUnauthorized,
|
||||
ErrorType: ErrTypeNotAuthorized,
|
||||
Message: "Not authorized to perform this operation",
|
||||
Details: sanitizeErrorMessage(apiErr.ErrorMessage()),
|
||||
}
|
||||
|
||||
case "InvalidParameterException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
ErrorType: ErrTypeInvalidParameter,
|
||||
Message: "Invalid parameter in request",
|
||||
Details: sanitizeErrorMessage(apiErr.ErrorMessage()),
|
||||
}
|
||||
|
||||
case "TooManyRequestsException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusTooManyRequests,
|
||||
ErrorType: ErrTypeRateLimited,
|
||||
Message: "Too many requests to Cognito",
|
||||
Details: "Rate limit exceeded. Please retry after a short delay.",
|
||||
}
|
||||
|
||||
case "ServiceUnavailable", "InternalError", "InternalErrorException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusServiceUnavailable,
|
||||
ErrorType: ErrTypeServiceUnavailable,
|
||||
Message: "Cognito service temporarily unavailable",
|
||||
Details: "Internal service error. Please retry later.",
|
||||
}
|
||||
|
||||
case "UsernameExistsException":
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusConflict,
|
||||
ErrorType: ErrTypeUserExists,
|
||||
Message: fmt.Sprintf("User already exists: %s", email),
|
||||
Details: "A user with this email already exists in Cognito",
|
||||
}
|
||||
|
||||
default:
|
||||
return CognitoErrorInfo{
|
||||
HTTPStatus: http.StatusBadGateway,
|
||||
ErrorType: ErrTypeServiceError,
|
||||
Message: "Failed to communicate with Cognito",
|
||||
Details: sanitizeErrorMessage(apiErr.ErrorMessage()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sanitizeErrorMessage removes sensitive information from error messages.
|
||||
// It strips out ARNs, request IDs, and internal resource names.
|
||||
//
|
||||
// Parameters:
|
||||
// - msg: The raw error message
|
||||
//
|
||||
// Returns:
|
||||
// - string: The sanitized error message
|
||||
func sanitizeErrorMessage(msg string) string {
|
||||
msg = removeARNs(msg)
|
||||
msg = removeRequestIDs(msg)
|
||||
msg = normalizeWhitespace(msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
// removeARNs replaces all AWS ARNs with [REDACTED].
|
||||
func removeARNs(msg string) string {
|
||||
for {
|
||||
start := strings.Index(msg, "arn:aws:")
|
||||
if start == -1 {
|
||||
return msg
|
||||
}
|
||||
end := findEndOfToken(msg, start)
|
||||
msg = msg[:start] + "[REDACTED]" + msg[end:]
|
||||
}
|
||||
}
|
||||
|
||||
// removeRequestIDs removes request ID patterns and their values from the message.
|
||||
func removeRequestIDs(msg string) string {
|
||||
patterns := []string{"requestid:", "request-id:", "request id:"}
|
||||
msgLower := strings.ToLower(msg)
|
||||
|
||||
for _, pattern := range patterns {
|
||||
for {
|
||||
idx := strings.Index(msgLower, pattern)
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
end := idx + len(pattern)
|
||||
end = skipWhitespace(msg, end)
|
||||
end = findEndOfToken(msg, end)
|
||||
msg = msg[:idx] + msg[end:]
|
||||
msgLower = strings.ToLower(msg)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// findEndOfToken finds the end of a token (space, comma, or newline delimited).
|
||||
func findEndOfToken(msg string, start int) int {
|
||||
end := start
|
||||
for end < len(msg) && !isTokenDelimiter(msg[end]) {
|
||||
end++
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
// isTokenDelimiter returns true if the character is a token delimiter.
|
||||
func isTokenDelimiter(c byte) bool {
|
||||
return c == ' ' || c == ',' || c == '\n' || c == '\r'
|
||||
}
|
||||
|
||||
// skipWhitespace advances past whitespace characters.
|
||||
func skipWhitespace(msg string, pos int) int {
|
||||
for pos < len(msg) && (msg[pos] == ' ' || msg[pos] == '\t') {
|
||||
pos++
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// normalizeWhitespace trims and collapses multiple spaces.
|
||||
func normalizeWhitespace(msg string) string {
|
||||
msg = strings.TrimSpace(msg)
|
||||
for strings.Contains(msg, " ") {
|
||||
msg = strings.ReplaceAll(msg, " ", " ")
|
||||
}
|
||||
return msg
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package usermanagement
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types"
|
||||
"github.com/aws/smithy-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// mockAPIError implements smithy.APIError for testing.
|
||||
type mockAPIError struct {
|
||||
code string
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *mockAPIError) Error() string { return e.message }
|
||||
func (e *mockAPIError) ErrorCode() string { return e.code }
|
||||
func (e *mockAPIError) ErrorMessage() string { return e.message }
|
||||
func (e *mockAPIError) ErrorFault() smithy.ErrorFault { return smithy.FaultServer }
|
||||
|
||||
func TestClassifyCognitoError_NilError(t *testing.T) {
|
||||
result := ClassifyCognitoError(nil, "test@example.com")
|
||||
|
||||
assert.Equal(t, http.StatusOK, result.HTTPStatus)
|
||||
assert.Equal(t, CognitoErrorType(""), result.ErrorType)
|
||||
assert.Empty(t, result.Message)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_UserNotFoundException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.UserNotFoundException{
|
||||
Message: stringPtr("User does not exist"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeUserNotFound, result.ErrorType)
|
||||
assert.Contains(t, result.Message, email)
|
||||
assert.NotEmpty(t, result.Details)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_NotAuthorizedException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.NotAuthorizedException{
|
||||
Message: stringPtr("User is not authorized"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeNotAuthorized, result.ErrorType)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_InvalidParameterException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.InvalidParameterException{
|
||||
Message: stringPtr("Invalid email format"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeInvalidParameter, result.ErrorType)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_TooManyRequestsException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.TooManyRequestsException{
|
||||
Message: stringPtr("Rate exceeded"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusTooManyRequests, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeRateLimited, result.ErrorType)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_InternalErrorException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.InternalErrorException{
|
||||
Message: stringPtr("Internal server error"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusServiceUnavailable, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeServiceUnavailable, result.ErrorType)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_UsernameExistsException(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &types.UsernameExistsException{
|
||||
Message: stringPtr("User already exists"),
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeUserExists, result.ErrorType)
|
||||
assert.Contains(t, result.Message, email)
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_AccessDeniedException_ViaAPIError(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &mockAPIError{
|
||||
code: "AccessDeniedException",
|
||||
message: "User: arn:aws:sts::123456789:assumed-role/role is not authorized",
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeAccessDenied, result.ErrorType)
|
||||
assert.Contains(t, result.Message, "Access denied")
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_GenericError(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := errors.New("some unknown error")
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeServiceError, result.ErrorType)
|
||||
assert.Contains(t, result.Message, "Failed to communicate with Cognito")
|
||||
}
|
||||
|
||||
func TestClassifyCognitoError_UnknownAPIError(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
err := &mockAPIError{
|
||||
code: "SomeUnknownException",
|
||||
message: "Unknown error occurred",
|
||||
}
|
||||
|
||||
result := ClassifyCognitoError(err, email)
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, result.HTTPStatus)
|
||||
assert.Equal(t, ErrTypeServiceError, result.ErrorType)
|
||||
}
|
||||
|
||||
func TestSanitizeErrorMessage_RemovesARNs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "removes single ARN",
|
||||
input: "User: arn:aws:sts::123456789012:assumed-role/TestRole/session is not authorized",
|
||||
expected: "User: [REDACTED] is not authorized",
|
||||
},
|
||||
{
|
||||
name: "removes multiple ARNs",
|
||||
input: "arn:aws:iam::123:user/test and arn:aws:s3:::bucket",
|
||||
expected: "[REDACTED] and [REDACTED]",
|
||||
},
|
||||
{
|
||||
name: "handles no ARN",
|
||||
input: "Simple error message",
|
||||
expected: "Simple error message",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := sanitizeErrorMessage(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeErrorMessage_RemovesRequestIDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "removes RequestID",
|
||||
input: "Error occurred, RequestID: abc-123-def",
|
||||
expected: "Error occurred,",
|
||||
},
|
||||
{
|
||||
name: "removes Request ID with space",
|
||||
input: "Error occurred, Request ID: abc-123-def something",
|
||||
expected: "Error occurred, something",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := sanitizeErrorMessage(tt.input)
|
||||
assert.NotContains(t, result, "abc-123-def")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyAPIError_AllCases(t *testing.T) {
|
||||
email := "user@test.com"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
expectedStatus int
|
||||
expectedType CognitoErrorType
|
||||
}{
|
||||
{"AccessDenied", "AccessDeniedException", http.StatusForbidden, ErrTypeAccessDenied},
|
||||
{"UserNotFound", "UserNotFoundException", http.StatusNotFound, ErrTypeUserNotFound},
|
||||
{"NotAuthorized", "NotAuthorizedException", http.StatusUnauthorized, ErrTypeNotAuthorized},
|
||||
{"InvalidParameter", "InvalidParameterException", http.StatusBadRequest, ErrTypeInvalidParameter},
|
||||
{"TooManyRequests", "TooManyRequestsException", http.StatusTooManyRequests, ErrTypeRateLimited},
|
||||
{"ServiceUnavailable", "ServiceUnavailable", http.StatusServiceUnavailable, ErrTypeServiceUnavailable},
|
||||
{"InternalError", "InternalError", http.StatusServiceUnavailable, ErrTypeServiceUnavailable},
|
||||
{"UsernameExists", "UsernameExistsException", http.StatusConflict, ErrTypeUserExists},
|
||||
{"Unknown", "SomeRandomException", http.StatusBadGateway, ErrTypeServiceError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
apiErr := &mockAPIError{code: tt.code, message: "test message"}
|
||||
result := classifyAPIError(apiErr, email)
|
||||
|
||||
assert.Equal(t, tt.expectedStatus, result.HTTPStatus)
|
||||
assert.Equal(t, tt.expectedType, result.ErrorType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// stringPtr is a helper to create a pointer to a string.
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
@@ -621,6 +621,12 @@ type DocumentSummary struct {
|
||||
|
||||
// ErrorMessage Description of error
|
||||
type ErrorMessage struct {
|
||||
// Details Additional error details for debugging (sanitized).
|
||||
Details *string `json:"details,omitempty"`
|
||||
|
||||
// ErrorType Machine-readable error classification for programmatic handling.
|
||||
ErrorType *string `json:"error_type,omitempty"`
|
||||
|
||||
// Message Message describing the cause.
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
@@ -3039,10 +3039,22 @@ components:
|
||||
description: Message describing the cause.
|
||||
maxLength: 256
|
||||
example: "you must give me good information"
|
||||
error_type:
|
||||
type: string
|
||||
description: Machine-readable error classification for programmatic handling.
|
||||
maxLength: 64
|
||||
example: "UserNotFound"
|
||||
details:
|
||||
type: string
|
||||
description: Additional error details for debugging (sanitized).
|
||||
maxLength: 512
|
||||
example: "The specified user does not exist in Cognito"
|
||||
required:
|
||||
- message
|
||||
example:
|
||||
message: you must include a message
|
||||
error_type: InvalidParameter
|
||||
details: The email format is invalid
|
||||
|
||||
# Identity API schemas
|
||||
IdentityRole:
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# Demo Data Loader
|
||||
|
||||
A Python script that imports demo data from a CSV file into the Query Orchestration API. This tool is used to populate the system with sample documents and field extraction records for demos and testing.
|
||||
|
||||
## Overview
|
||||
|
||||
The script performs the following operations:
|
||||
|
||||
1. **Create Demo Client** - Creates a new client in the system to hold the demo data
|
||||
2. **Upload Documents** - For each unique file in the CSV, creates and uploads a unique PDF document
|
||||
3. **Create Field Extractions** - Imports the field extraction data from the CSV for each document
|
||||
4. **Verify Records** - Retrieves and validates the created records match the input data
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.x (standard library only - no third-party packages required)
|
||||
- Access to a running Query Orchestration API instance
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python3 demo_data_loader.py <csv_path> <base_url> [jwt_token]
|
||||
python3 demo_data_loader.py ./SaaS_Demo_Data_Sam.csv http://localhost:8080
|
||||
# example for uat system with token
|
||||
python3 demo_data_loader.py ./SaaS_Demo_Data_Sam.csv http://queryo-query-wheg72fhas6h-1773391169.us-east-2.elb.amazonaws.com tokenfile=token.txt
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Argument | Required | Description |
|
||||
| ----------- | -------- | --------------------------------------------------------- |
|
||||
| `csv_path` | Yes | Path to the CSV file containing demo data |
|
||||
| `base_url` | Yes | Base URL of the API (e.g., `http://localhost:8080`) |
|
||||
| `jwt_token` | No | JWT token for authentication (placeholder for future use) |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Run against local development server
|
||||
python3 demo_data_loader.py SaaS_Demo_Data_Sam.csv http://localhost:8080
|
||||
|
||||
# Run against a deployed environment with authentication
|
||||
python3 demo_data_loader.py SaaS_Demo_Data_Sam.csv https://api.example.com "eyJhbGciOiJS..."
|
||||
```
|
||||
|
||||
## CSV File Format
|
||||
|
||||
The CSV file must have:
|
||||
|
||||
- **Header row** - Column names in the first row
|
||||
- **Data rows** - Multiple rows where rows with the same `FILE_NAME` belong to the same document
|
||||
- **Single-value fields** (columns 1-18) - Should be identical for all rows with the same `FILE_NAME`
|
||||
- **Array fields** (columns 19+) - Can vary per row, forming multiple array items per document
|
||||
|
||||
### Column Mapping
|
||||
|
||||
#### Single-Value Fields (Columns 1-18)
|
||||
|
||||
| Column | API Field |
|
||||
| ----------------------------- | -------------------------- |
|
||||
| FILE_NAME | fileName |
|
||||
| CONTRACT_TITLE | contractTitle |
|
||||
| AARETE_DERIVED_AMENDMENT_NUM | aareteDerivedAmendmentNum |
|
||||
| CLIENT_NAME | clientName |
|
||||
| PAYER_NAME | payerName |
|
||||
| PAYER_STATE | payerState |
|
||||
| PROVIDER_STATE | providerState |
|
||||
| FILENAME_TIN | filenameTin |
|
||||
| PROV_GROUP_TIN | provGroupTin |
|
||||
| PROV_GROUP_NPI | provGroupNpi |
|
||||
| PROV_GROUP_NAME_FULL | provGroupNameFull |
|
||||
| PROV_OTHER_TIN | provOtherTin |
|
||||
| PROV_OTHER_NPI | provOtherNpi |
|
||||
| PROV_OTHER_NAME_FULL | provOtherNameFull |
|
||||
| AARETE_DERIVED_EFFECTIVE_DT | aareteDerivedEffectiveDt |
|
||||
| AARETE_DERIVED_TERMINATION_DT | aareteDerivedTerminationDt |
|
||||
| AUTO_RENEWAL_IND | autoRenewalInd |
|
||||
| AUTO_RENEWAL_TERM | autoRenewalTerm |
|
||||
|
||||
#### Array Fields (Columns 19+)
|
||||
|
||||
See the full mapping in the script source code. Array fields include:
|
||||
|
||||
- Exhibit information (title, page)
|
||||
- Provider information (TIN, NPI, name)
|
||||
- Claim type, product, LOB, program, network
|
||||
- Taxonomy and specialty codes
|
||||
- Reimbursement terms and rates
|
||||
- Grouper information
|
||||
- Outlier and stop-loss provisions
|
||||
- Facility adjustments (DSH, IME, NTAP, etc.)
|
||||
- Rate escalator information
|
||||
|
||||
## Data Type Conversions
|
||||
|
||||
The script automatically converts CSV values to appropriate types:
|
||||
|
||||
| Type | Fields | Conversion |
|
||||
| ------- | ------------------------------------ | -------------------------------------- |
|
||||
| Boolean | `*Ind` fields | Y/N/TRUE/FALSE -> true/false |
|
||||
| Integer | `aareteDerivedAmendmentNum` | String -> integer |
|
||||
| Numeric | `*Rate`, `*Amt`, `*Threshold` fields | String -> float (handles %, $, commas) |
|
||||
| Date | `*Dt` fields | M/D/YYYY -> YYYY-MM-DD |
|
||||
|
||||
## Output
|
||||
|
||||
The script provides detailed progress output:
|
||||
|
||||
```
|
||||
============================================================
|
||||
Demo Data Loader
|
||||
============================================================
|
||||
CSV File: SaaS_Demo_Data_Sam.csv
|
||||
Base URL: http://localhost:8080
|
||||
JWT Token: not provided
|
||||
|
||||
============================================================
|
||||
Step 1: Reading CSV File
|
||||
============================================================
|
||||
[OK] Read 220 data rows with 130 columns
|
||||
|
||||
============================================================
|
||||
Step 2: Analyzing Data
|
||||
============================================================
|
||||
[OK] Found 23 unique documents to import
|
||||
- 952436878_Providence MC_Amd01_01.2025: 1 array item(s)
|
||||
- 952436878_Providence MC_Amd02_01.2025: 18 array item(s)
|
||||
...
|
||||
|
||||
============================================================
|
||||
Summary
|
||||
============================================================
|
||||
Client ID: demo-data-loader-1769734603
|
||||
Documents uploaded: 23
|
||||
Field extractions created: 23
|
||||
Verifications passed: 23
|
||||
Verifications failed: 0
|
||||
[OK] All operations completed successfully!
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
| ---- | ---------------------------------------------------------------------- |
|
||||
| 0 | All operations completed successfully |
|
||||
| 1 | Error occurred (missing arguments, file read error, API failure, etc.) |
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
The script includes:
|
||||
|
||||
- Automatic retry with exponential backoff for 429 (rate limit) responses
|
||||
- Delays between API calls to avoid triggering rate limits
|
||||
|
||||
## API Endpoints Used
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
| ------ | ------------------------------------ | ----------------------- |
|
||||
| POST | `/client` | Create demo client |
|
||||
| POST | `/client/{id}/document` | Upload document |
|
||||
| GET | `/client/{id}/document` | List documents |
|
||||
| GET | `/document/{id}` | Get document details |
|
||||
| POST | `/field-extractions` | Create field extraction |
|
||||
| GET | `/field-extractions?documentId={id}` | Verify field extraction |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "No documents available after upload"
|
||||
|
||||
The document processing queue may be slow. Try:
|
||||
|
||||
1. Check that the `storeEventRunner` service is running
|
||||
2. Increase the `max_attempts` parameter in `wait_for_documents()`
|
||||
|
||||
### Rate Limit Errors (429)
|
||||
|
||||
The script handles rate limits automatically with retries. If you still see errors:
|
||||
|
||||
1. Increase delays between operations in the script
|
||||
2. Reduce the number of documents being imported at once
|
||||
|
||||
### "Failed to create extraction"
|
||||
|
||||
Check that:
|
||||
|
||||
1. The document exists in the system
|
||||
2. The field values are valid (proper date formats, numeric values, etc.)
|
||||
|
||||
## Source Files
|
||||
|
||||
- `demo_data_loader.py` - Main script
|
||||
- `SaaS_Demo_Data_Sam.csv` - Sample CSV data file
|
||||
- `README.md` - This documentation
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user