From c25da1d25c63626b6600b6c1c0b7b63c8c38c054 Mon Sep 17 00:00:00 2001 From: Le Bon Date: Mon, 2 Dec 2024 01:46:48 +0000 Subject: [PATCH 1/9] Completed pattern for Detecting Text via Amazon Textract and deploying via SAM. --- textract-lambda-sam-python/README.md | 70 ++++++++++++++++++ .../example-pattern.json | 67 +++++++++++++++++ ...bda-start-detect-document-text-textract.py | 27 +++++++ textract-lambda-sam-python/template.yaml | 72 +++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 textract-lambda-sam-python/README.md create mode 100644 textract-lambda-sam-python/example-pattern.json create mode 100644 textract-lambda-sam-python/src/lambda-start-detect-document-text-textract.py create mode 100644 textract-lambda-sam-python/template.yaml diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md new file mode 100644 index 000000000..1b45c2d4b --- /dev/null +++ b/textract-lambda-sam-python/README.md @@ -0,0 +1,70 @@ +# Amazon Textract to Automatically Detect Text in Stored Files. + +This pattern explains how to deploy an AWS Sam application with Amazon S3, AWS Lambda, and Amazon DynamoDB to detect text stored within pdf or image files. When an image file is created in Amazon S3, the event-driven workflow begins, sending an event to AWS Lambda. This Lambda function, written in Python, invokes the Amazon Textract `DetectDocumentText` function using boto3. Once Textracts's function has returned it's response, Lambda stores the detected text in an Amazon DynamoDB table. + +Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/textract-lambda-sam-python](https://serverlessland.com/patterns/textract-lambda-sam-python) + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd textract-lambda-sam-python + ``` +3. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: + ``` + sam deploy --guided + ``` +4. During the prompts: + * Enter a stack name + * Enter the desired AWS Region + * Allow SAM CLI to create IAM roles with the required permissions. + + Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults. + +5. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing. + +## How it works + +- This pattern is designed to create all services required to run this workflow. +- The workflow begins with an Amazon S3 bucket. +- When an object is created within the S3 bucket, it sends an event to a AWS Lambda function. +- This Lambda function invokes Amazon Textracts's [DetectDocumentText](https://docs.aws.amazon.com/textract/latest/dg/API_DetectDocumentText.html) function, which synchronously analyses the newly stored file. +- Once this task is complete, the Lambda function stores the results in an Amazon DynamoDB table. + +## Testing +1. Upload a test image or pdf file which contains some text to the Amazon S3 bucket created during the deployment step. You can either do this via the console, or by running the following command. Replace `` with your image or pdf file name. Replace `` with the name of the S3 bucket generated from the AWS SAM deployment outputs. + ``` + aws s3 cp s3:// + ``` +3. Wait for the lambda-start-detect-document-text-textract Lambda function to complete, then retrieve the record output from the DynamoDB table. Replace `` with the name of the DynamoDB table generated by the AWS SAM deployment output, in this case, it will be `TextractResultsTable`. You can use the following command: + ``` + aws dynamodb scan --table-name + ``` + For example: aws dynamodb scan --table-name TextractResultsTable +4. There should now be a newly uploaded record within DynamoDB displayed, depending on the file, the key 'DetectedText' will either be empty, or contain values indicating what was found in the uploaded file. + +## Cleanup + +1. Delete the stack + ``` + sam delete + ``` + +⚠️ **IMPORTANT** - The above command `sam delete` does not delete the **Amazon S3 bucket** if there are still objects stored within it. Please [empty the bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/empty-bucket.html) before running the above command. +---- +Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 \ No newline at end of file diff --git a/textract-lambda-sam-python/example-pattern.json b/textract-lambda-sam-python/example-pattern.json new file mode 100644 index 000000000..0b96e89d8 --- /dev/null +++ b/textract-lambda-sam-python/example-pattern.json @@ -0,0 +1,67 @@ +{ + "title": "Automatic Text Detection with Amazon Textract", + "description": "An event-driven workflow to automatically detect and store text found within pdf files by leveraging Amazon Textract, AWS Lambda, and Amazon DynamoDB.", + "language": "Python", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to deliver an event-driven architecture to detect text within pdf files, while storing the results in Amazon DynamoDB.", + "This pattern allows you to store image files in an Amazon S3 bucket, which triggers the workflow. Upon an object being created in the S3 bucket, a Lambda function is invoked, which initiates Amazon Textracts's DetectDocumentText function. Once the function call is finished and it has retrieved any text found within the file, the Lambda function stores this information in our DynamoDB table.", + "This pattern deploys 1 S3 bucket, 1 Lambda Function, and 1 DynamoDB Table." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/textract-lambda-sam-python", + "templateURL": "https://github.com/aws-samples/serverless-patterns/main/textract-lambda-sam-python/template.yaml", + "projectFolder": "textract-lambda-sam-python", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Simple Storage Service (S3)", + "link": "https://aws.amazon.com/s3/" + }, + { + "text": "AWS Lambda", + "link": "https://aws.amazon.com/lambda/" + }, + { + "text": "Amazon Textract", + "link": "https://aws.amazon.com/textract/" + }, + { + "text": "Amazon DynamoDB", + "link": "https://aws.amazon.com/dynamodb/" + } + ] + }, + "deploy": { + "text": [ + "sam deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: sam delete" + ] + }, + "authors": [ + { + "name": "Jack Le Bon", + "image": "https://media.licdn.com/dms/image/C4E03AQHvuSBgwssMBQ/profile-displayphoto-shrink_800_800/0/1663884739192?e=1706140800&v=beta&t=jAtE5SqZKE3J0BqaBOTSn8GKg995guStVwsQmodM7O0", + "bio": "AWS Solutions Architect", + "linkedin": "jack-le-bon" + } + ] + } + diff --git a/textract-lambda-sam-python/src/lambda-start-detect-document-text-textract.py b/textract-lambda-sam-python/src/lambda-start-detect-document-text-textract.py new file mode 100644 index 000000000..86fe1a8c3 --- /dev/null +++ b/textract-lambda-sam-python/src/lambda-start-detect-document-text-textract.py @@ -0,0 +1,27 @@ +import json +import boto3 +import os +import decimal + +client = boto3.client('textract') +dynamodb = boto3.resource('dynamodb') +table = dynamodb.Table(os.environ.get('dynamoDBTableName')) + +def lambda_handler(event, context): + bucket = event['Records'][0]['s3']['bucket']['name'] + key = event['Records'][0]['s3']['object']['key'] + record = {'Image': key, 'Bucket': bucket} + response = client.detect_document_text( + Document={ + 'S3Object': { + 'Bucket': bucket, + 'Name': key + } + } + ) + record['DetectedText'] = response + dynamodb_response = table.put_item(Item=json.loads(json.dumps(record), parse_float=decimal.Decimal)) + return { + 'statusCode': 200, + 'body': json.dumps('Textract complete.') + } \ No newline at end of file diff --git a/textract-lambda-sam-python/template.yaml b/textract-lambda-sam-python/template.yaml new file mode 100644 index 000000000..320f78878 --- /dev/null +++ b/textract-lambda-sam-python/template.yaml @@ -0,0 +1,72 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + An Amazon S3 bucket for the storing of Image files. + Upon uploading a new object, triggers an AWS Lambda function which consumes the event and leverages Amazon Textract's DetectDocumentText function to find text within an image or file. + The Lambda function then stores the result in Amazon DynamoDB. + +Resources: + # S3 bucket to store Image files from the user. + ImageFileBucket: + Type: AWS::S3::Bucket + + # Define the DynamoDB table + DynamoDBTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: "Image" + AttributeType: "S" + - AttributeName: "Bucket" + AttributeType: "S" + KeySchema: + - AttributeName: "Image" + KeyType: HASH + - AttributeName: "Bucket" + KeyType: RANGE + BillingMode: PAY_PER_REQUEST + TableName: "TextractResultsTable" + + # Lambda Function to begin Amazon Rekognition's DetectLabels function. + StartProcessingFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: lambda-start-detect-document-text-textract + Runtime: python3.13 + Handler: src/lambda-start-detect-document-text-textract.lambda_handler + MemorySize: 128 + Timeout: 10 + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "s3:GetObject" + - "s3:PutObject" + Resource: "*" + - Effect: Allow + Action: + - "textract:DetectDocumentText" + Resource: "*" + - Effect: Allow + Action: + - "dynamodb:PutItem" + Resource: !GetAtt DynamoDBTable.Arn + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: ImageFileBucket + Events: s3:ObjectCreated:* + Environment: + Variables: + dynamoDBTableName: !Ref DynamoDBTable + +Outputs: + ImageFileBucket: + Value: !Ref ImageFileBucket + Description: S3 Bucket for object storage + DynamoDBTable: + Value: !Ref DynamoDBTable + Description: DynamoDB table containing Textract Results From 241364e0554534f5e52bb9e572b6b35f923e33e7 Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:21:43 +0000 Subject: [PATCH 2/9] Update example-pattern.json --- textract-lambda-sam-python/example-pattern.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/example-pattern.json b/textract-lambda-sam-python/example-pattern.json index 0b96e89d8..dc7b58076 100644 --- a/textract-lambda-sam-python/example-pattern.json +++ b/textract-lambda-sam-python/example-pattern.json @@ -58,7 +58,7 @@ "authors": [ { "name": "Jack Le Bon", - "image": "https://media.licdn.com/dms/image/C4E03AQHvuSBgwssMBQ/profile-displayphoto-shrink_800_800/0/1663884739192?e=1706140800&v=beta&t=jAtE5SqZKE3J0BqaBOTSn8GKg995guStVwsQmodM7O0", + "image": "https://serverlessland.com/assets/images/resources/contributors/ext-jack-le-bon.jpg", "bio": "AWS Solutions Architect", "linkedin": "jack-le-bon" } From 0d15baaa8fe41c670d97db7474c8ad4f1e842186 Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:10:29 +0000 Subject: [PATCH 3/9] Update textract-lambda-sam-python/README.md Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- textract-lambda-sam-python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md index 1b45c2d4b..5ca2300c1 100644 --- a/textract-lambda-sam-python/README.md +++ b/textract-lambda-sam-python/README.md @@ -1,4 +1,4 @@ -# Amazon Textract to Automatically Detect Text in Stored Files. +# Automatically Detect Text with Amazon Textract and AWS Lambda This pattern explains how to deploy an AWS Sam application with Amazon S3, AWS Lambda, and Amazon DynamoDB to detect text stored within pdf or image files. When an image file is created in Amazon S3, the event-driven workflow begins, sending an event to AWS Lambda. This Lambda function, written in Python, invokes the Amazon Textract `DetectDocumentText` function using boto3. Once Textracts's function has returned it's response, Lambda stores the detected text in an Amazon DynamoDB table. From 90e57da42f5e7b9ff545e6c3518b190587a6a36c Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:10:59 +0000 Subject: [PATCH 4/9] Update textract-lambda-sam-python/README.md Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- textract-lambda-sam-python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md index 5ca2300c1..7af02ec73 100644 --- a/textract-lambda-sam-python/README.md +++ b/textract-lambda-sam-python/README.md @@ -1,6 +1,6 @@ # Automatically Detect Text with Amazon Textract and AWS Lambda -This pattern explains how to deploy an AWS Sam application with Amazon S3, AWS Lambda, and Amazon DynamoDB to detect text stored within pdf or image files. When an image file is created in Amazon S3, the event-driven workflow begins, sending an event to AWS Lambda. This Lambda function, written in Python, invokes the Amazon Textract `DetectDocumentText` function using boto3. Once Textracts's function has returned it's response, Lambda stores the detected text in an Amazon DynamoDB table. +This pattern explains how to deploy an AWS SAM application with Amazon S3, AWS Lambda, and Amazon DynamoDB to detect text stored within pdf or image files. When an image file is uploaded to Amazon S3, the event-driven workflow begins, sending an event to AWS Lambda. This Lambda function, written in Python, invokes the Amazon Textract `DetectDocumentText` function using boto3. Once Textract returns the response, Lambda stores the detected text in a DynamoDB table. Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/textract-lambda-sam-python](https://serverlessland.com/patterns/textract-lambda-sam-python) From 706e0887fc5d08fb74e374a1574eee2a91dd8e6c Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:11:23 +0000 Subject: [PATCH 5/9] Update textract-lambda-sam-python/README.md Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- textract-lambda-sam-python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md index 7af02ec73..5c8853cc7 100644 --- a/textract-lambda-sam-python/README.md +++ b/textract-lambda-sam-python/README.md @@ -32,7 +32,7 @@ Important: this application uses various AWS services and there are costs associ * Enter the desired AWS Region * Allow SAM CLI to create IAM roles with the required permissions. - Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults. + Once you run `sam deploy --guided` for the first time, and save the arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future deployments to use these defaults. 5. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing. From e742a158375c2014b2988c093035042cb1616f3b Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:11:38 +0000 Subject: [PATCH 6/9] Update textract-lambda-sam-python/README.md Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- textract-lambda-sam-python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md index 5c8853cc7..a3866c48c 100644 --- a/textract-lambda-sam-python/README.md +++ b/textract-lambda-sam-python/README.md @@ -40,7 +40,7 @@ Important: this application uses various AWS services and there are costs associ - This pattern is designed to create all services required to run this workflow. - The workflow begins with an Amazon S3 bucket. -- When an object is created within the S3 bucket, it sends an event to a AWS Lambda function. +- When an object is created within the S3 bucket, it sends an event to an AWS Lambda function. - This Lambda function invokes Amazon Textracts's [DetectDocumentText](https://docs.aws.amazon.com/textract/latest/dg/API_DetectDocumentText.html) function, which synchronously analyses the newly stored file. - Once this task is complete, the Lambda function stores the results in an Amazon DynamoDB table. From 4faeb45ef671d3282924ad253d27d647b0b9c221 Mon Sep 17 00:00:00 2001 From: jack-lebon <147945672+jack-lebon@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:11:50 +0000 Subject: [PATCH 7/9] Update textract-lambda-sam-python/README.md Co-authored-by: ellisms <114107920+ellisms@users.noreply.github.com> --- textract-lambda-sam-python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/README.md b/textract-lambda-sam-python/README.md index a3866c48c..d927998e3 100644 --- a/textract-lambda-sam-python/README.md +++ b/textract-lambda-sam-python/README.md @@ -45,7 +45,7 @@ Important: this application uses various AWS services and there are costs associ - Once this task is complete, the Lambda function stores the results in an Amazon DynamoDB table. ## Testing -1. Upload a test image or pdf file which contains some text to the Amazon S3 bucket created during the deployment step. You can either do this via the console, or by running the following command. Replace `` with your image or pdf file name. Replace `` with the name of the S3 bucket generated from the AWS SAM deployment outputs. +1. Upload a test image, or pdf file, containing text to the Amazon S3 bucket created during the deployment step. You can either do this via the console, or by running the following command. Replace `` with your image or pdf file name. Replace `` with the name of the S3 bucket generated from the AWS SAM deployment outputs. ``` aws s3 cp s3:// ``` From 37acb03ac40b814414fa1b4f9a8c55c528348078 Mon Sep 17 00:00:00 2001 From: ellisms <114107920+ellisms@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:09:13 -0500 Subject: [PATCH 8/9] publishing file --- .../textract-lambda-sam-python.json | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 textract-lambda-sam-python/textract-lambda-sam-python.json diff --git a/textract-lambda-sam-python/textract-lambda-sam-python.json b/textract-lambda-sam-python/textract-lambda-sam-python.json new file mode 100644 index 000000000..36757aae8 --- /dev/null +++ b/textract-lambda-sam-python/textract-lambda-sam-python.json @@ -0,0 +1,107 @@ +{ + "title": "Automatic Text Detection with Amazon Textract", + "description": "An event-driven workflow to automatically detect and store text found within pdf files by leveraging Amazon Textract, AWS Lambda, and Amazon DynamoDB.", + "language": "Python", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to deliver an event-driven architecture to detect text within pdf files, while storing the results in Amazon DynamoDB.", + "Upon an object creation in the S3 bucket, a Lambda function is invoked, which initiates Amazon Textracts's DetectDocumentText function. Textract returns the results to the Lambda function which stores this information in the DynamoDB table.", + "This pattern deploys 1 S3 bucket, 1 Lambda Function, and 1 DynamoDB Table." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/textract-lambda-sam-python", + "templateURL": "https://github.com/aws-samples/serverless-patterns/main/textract-lambda-sam-python/template.yaml", + "projectFolder": "textract-lambda-sam-python", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Simple Storage Service (S3)", + "link": "https://aws.amazon.com/s3/" + }, + { + "text": "AWS Lambda", + "link": "https://aws.amazon.com/lambda/" + }, + { + "text": "Amazon Textract", + "link": "https://aws.amazon.com/textract/" + }, + { + "text": "Amazon DynamoDB", + "link": "https://aws.amazon.com/dynamodb/" + } + ] + }, + "deploy": { + "text": [ + "sam deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: sam delete" + ] + }, + "authors": [ + { + "name": "Jack Le Bon", + "image": "https://serverlessland.com/assets/images/resources/contributors/ext-jack-le-bon.jpg", + "bio": "AWS Solutions Architect", + "linkedin": "jack-le-bon" + } + ], + "patternArch": { + "icon1": { + "x": 10, + "y": 50, + "service": "s3", + "label": "Amazon S3" + }, + "icon2": { + "x": 40, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "icon3": { + "x": 80, + "y": 25, + "service": "textract", + "label": "Amazon Textract" + }, + "icon4": { + "x": 80, + "y": 70, + "service": "dynamodb", + "label": "Amazon DynamoDB" + }, + "line1": { + "from": "icon1", + "to": "icon2", + "label": "Object Created" + }, + "line2": { + "from": "icon2", + "to": "icon3", + "label": "Document" + }, + "line3": { + "from": "icon2", + "to": "icon4", + "label": "Results" + } + } +} From f9a3d7b9ecf0ffe8c490629b68c157e9bb1aa1db Mon Sep 17 00:00:00 2001 From: ellisms <114107920+ellisms@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:10:21 -0500 Subject: [PATCH 9/9] tagging --- textract-lambda-sam-python/template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textract-lambda-sam-python/template.yaml b/textract-lambda-sam-python/template.yaml index 320f78878..3fb068813 100644 --- a/textract-lambda-sam-python/template.yaml +++ b/textract-lambda-sam-python/template.yaml @@ -3,7 +3,7 @@ Transform: AWS::Serverless-2016-10-31 Description: > An Amazon S3 bucket for the storing of Image files. Upon uploading a new object, triggers an AWS Lambda function which consumes the event and leverages Amazon Textract's DetectDocumentText function to find text within an image or file. - The Lambda function then stores the result in Amazon DynamoDB. + The Lambda function then stores the result in Amazon DynamoDB. (uksb-1tthgi812) (tag:textract-lambda-sam-python) Resources: # S3 bucket to store Image files from the user.