{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8a265247-42b1-4d16-867a-c1c7fca8f013",
   "metadata": {},
   "source": [
    "## Medium Level 3 App\n",
    "* We will create an app that manages PDF files stored in AWS S3.\n",
    "* You will be able to create, read, update and delete the PDF files.\n",
    "* You will use a Postgres database to store the name of the PDF file and the link to its location in AWS S3."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c322d30-f1c2-4afb-8d93-afa5a1f6deba",
   "metadata": {},
   "source": [
    "#### As always, it is recommended to create a virtual environment"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3585640-e78c-46ee-9500-97d5843de364",
   "metadata": {},
   "source": [
    "## Part 1: AWS S3 Preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60a1bf5f-25e6-4005-9d84-0972a48021ad",
   "metadata": {},
   "source": [
    "#### Create an AWS S3 account"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e13b48bf-d3f1-4899-b944-31df5a22349e",
   "metadata": {},
   "source": [
    "Creating an account on Amazon Web Services (AWS) and starting to use the Amazon S3 (Simple Storage Service) involves several steps. Here is a guide through the process:\n",
    "\n",
    "1. **Sign Up for Amazon Web Services (AWS)**:\n",
    "   - Visit the AWS website at [https://aws.amazon.com/](https://aws.amazon.com/).\n",
    "   - Click on \"Create an AWS Account\".\n",
    "   - Follow the instructions to create your account, which include providing your email address, creating a password, and choosing a name for your AWS account.\n",
    "   - Then, you'll be asked to provide contact information and billing details (credit card information), as AWS operates on a pay-as-you-go basis.\n",
    "\n",
    "2. **Log in to the AWS Management Console**:\n",
    "   - Once your account is active, log in to the AWS Management Console at [https://aws.amazon.com/console/](https://aws.amazon.com/console/).\n",
    "   - Use the email and password you registered with to log in.\n",
    "\n",
    "3. **Access the Amazon S3 Service**:\n",
    "   - In the AWS Management Console, search for \"S3\" in the search bar or find it under the \"Storage\" section.\n",
    "   - Click on S3 to open the service management panel.\n",
    "\n",
    "4. **Create a Bucket in S3**:\n",
    "   - Within the S3 console, you can start by creating a \"bucket\", which is a basic container where data is stored.\n",
    "   - Click on \"Create bucket\".\n",
    "   - Provide a unique name for the bucket and select the region where you want to store your data.\n",
    "   - Configure additional options as needed, such as access control, versioning, etc.\n",
    "   - Click on \"Create\" to finalize the creation of the bucket.\n",
    "\n",
    "5. **Upload Files to Your Bucket**:\n",
    "   - Once the bucket is created, you can upload files to it. To do this, select your bucket and then use the \"Upload\" option to add files from your computer.\n",
    "\n",
    "6. **Configure Permissions and Policies**:\n",
    "   - It's important to properly configure permissions and security policies to control who can access your files in S3.\n",
    "\n",
    "7. **Additional Uses**:\n",
    "   - Besides storing files, you can use S3 for a variety of purposes, such as hosting static websites, as part of backup solutions, etc.\n",
    "\n",
    "Remember that AWS S3 is a pay-for-use service, so you will be charged for storage and data transfer according to AWS's pricing. Also, it's advisable to familiarize yourself with best practices for security and management to protect your data on AWS."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06e8d072-7e7f-42d1-8bd0-ef62bc5dc6c3",
   "metadata": {},
   "source": [
    "#### Configure the necessary permissions in AWS S3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddbdb4bb-0324-4ffb-9d2f-79b5e81af9b6",
   "metadata": {},
   "source": [
    "To create and attach a permissions policy to a user in AWS IAM, follow these steps:\n",
    "\n",
    "**Step 1: Create the Policy**\n",
    "1. **Log in** to the AWS IAM console at [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).\n",
    "2. In the navigation panel, select **Policies**.\n",
    "3. In the content panel, choose **Create policy**.\n",
    "4. Choose the **JSON** option and copy the text from a JSON policy document. For example, to allow `PutObject` operations in S3, you can use something like this:\n",
    "   ```json\n",
    "   {\n",
    "       \"Version\": \"2012-10-17\",\n",
    "       \"Statement\": [\n",
    "           {\n",
    "               \"Effect\": \"Allow\",\n",
    "               \"Action\": \"s3:PutObject\",\n",
    "               \"Resource\": \"arn:aws:s3:::your-bucket-name/*\"\n",
    "           }\n",
    "       ]\n",
    "   }\n",
    "   ```\n",
    "   Replace `\"your-bucket-name\"` with the actual name of your S3 bucket.\n",
    "5. Resolve any security warnings, errors, or general alerts generated during the **policy validation**, and then choose **Next**.\n",
    "6. On the **Review and create** page, write a name for the policy, review the permissions your policy grants, and then choose **Create policy**.\n",
    "\n",
    "**Step 2: Attach the Policy to the User**\n",
    "1. In the IAM console, in the navigation panel, choose **Users**.\n",
    "2. Find and select the user to whom you want to attach the policy.\n",
    "3. On the user's details page, choose the **Permissions** tab.\n",
    "4. Choose **Add permissions**.\n",
    "5. Select **Attach policies directly**.\n",
    "6. Search for the policy you just created and select it.\n",
    "7. Choose **Next: Review** and then **Add permissions**.\n",
    "\n",
    "**Step 3: Verify and Test**\n",
    "- After attaching the policy, verify that the user has the correct permissions.\n",
    "- If possible, test the permissions by performing a `PutObject` operation in S3 with the credentials of this user.\n",
    "\n",
    "**Note:** If you don't have an IAM user yet, you'll first need to create one and then follow these steps to attach the policy to the user."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7094d409-97ef-44a3-a7cc-f017c2cb65f6",
   "metadata": {},
   "source": [
    "#### Create a public bucket in AWS S3\n",
    "* Name used for the bucked: pdf-basic-app"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d859fbf-4d79-4598-a3c4-01915712246c",
   "metadata": {},
   "source": [
    "To create a public bucket in Amazon S3, you need to configure the bucket's access options to allow public access. However, it's important to consider the security implications of making a bucket public, as this could expose your data to anyone on the Internet. If you're sure that you need a public bucket (for example, to host assets for a static website), here's how you can do it:\n",
    "\n",
    "#### Step 1: Create the Bucket\n",
    "1. **Log in to AWS Management Console**:\n",
    "   - Go to the [AWS Management Console](https://aws.amazon.com/console/) and log in.\n",
    "\n",
    "2. **Open the Amazon S3 Service**:\n",
    "   - Once in the console, search for and select the S3 service.\n",
    "\n",
    "3. **Create a New Bucket**:\n",
    "   - Click on \"Create bucket\".\n",
    "   - Provide a name for your bucket and select the region.\n",
    "   - Continue with the configuration until you reach the permissions section.\n",
    "\n",
    "#### Step 2: Configure Public Access Permissions\n",
    "In the permissions section during bucket creation:\n",
    "\n",
    "1. **Block Public Access Settings**:\n",
    "   - Disable the \"Block all public access\" option. This will allow public access to the bucket.\n",
    "   - AWS will warn you about the risks of doing this; ensure you understand the consequences.\n",
    "\n",
    "2. **Review and Create the Bucket**:\n",
    "   - Review your configuration and then create the bucket.\n",
    "\n",
    "#### Step 3 (Optional, recommended): Set Up a Bucket Policy for Public Access\n",
    "Once the bucket is created, you need to define a bucket policy to explicitly allow public access:\n",
    "\n",
    "1. **Select Your Bucket**:\n",
    "   - In the S3 console, click on the name of your bucket.\n",
    "\n",
    "2. **Go to the Permissions Section**:\n",
    "   - Click on the \"Permissions\" tab.\n",
    "\n",
    "3. **Add a Bucket Policy**:\n",
    "   - Click on “Bucket Policy”.\n",
    "   - Add a policy that grants public read permissions. For example:\n",
    "     ```json\n",
    "     {\n",
    "       \"Version\": \"2012-10-17\",\n",
    "       \"Statement\": [\n",
    "         {\n",
    "           \"Effect\": \"Allow\",\n",
    "           \"Principal\": \"*\",\n",
    "           \"Action\": [\"s3:GetObject\"],\n",
    "           \"Resource\": [\"arn:aws:s3:::your-bucket-name/*\"]\n",
    "         }\n",
    "       ]\n",
    "     }\n",
    "     ```\n",
    "   - Replace `your-bucket-name` with the actual name of your bucket.\n",
    "\n",
    "4. **Save the Policy**:\n",
    "   - Click on “Save” to apply the policy.\n",
    "\n",
    "#### Additional Considerations\n",
    "- **Security**: Keep in mind that making a bucket public can expose your data. Use this setting only when it's absolutely necessary and when the data is intended to be public.\n",
    "- **CORS Usage**: If your bucket will be used to serve content to websites on different domains, you might also need to configure CORS rules.\n",
    "\n",
    "By following these steps, you will have created a bucket in S3 that is publicly accessible, meaning anyone with the correct URL can access or download the files stored in it."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "205b0746-fea2-4b53-b0e4-aa8ede303374",
   "metadata": {},
   "source": [
    "## Part 2: Backend with FastAPI and Postgres"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e6992f4-e244-44d5-85a2-e1c098799904",
   "metadata": {},
   "source": [
    "#### Add your AWS credentials in the .env file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "8b2f6ea4-2967-4ae3-b398-7c16362d2593",
   "metadata": {},
   "outputs": [],
   "source": [
    "#AWS_KEY=...\n",
    "#AWS_SECRET=..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91a9727d-9246-4330-8604-beb9f2273147",
   "metadata": {},
   "source": [
    "#### Create .gitignore file\n",
    "* To avoid loading your credentials to a public repository"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d104e50e-40a8-422c-a827-b8c56a89d908",
   "metadata": {},
   "outputs": [],
   "source": [
    "#.env"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b35035a1-da75-420c-ac6f-c1edab4d29c5",
   "metadata": {},
   "source": [
    "#### Install Boto3 to be able to use AWS in your computer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa6beccd-4da4-4782-9d39-8c9d9f14990e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#pip install boto3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a39b8fa-7ee1-4387-9378-e80b567b78f2",
   "metadata": {},
   "source": [
    "#### Create backend directory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "29859933-9a01-4eef-b724-42614e4b4a4e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#mkdir backend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f88adea9-9442-4721-b6f1-9409ec8bc1b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#cd backend"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87298130-ca35-436c-940e-9be44a004735",
   "metadata": {},
   "source": [
    "#### You will need to have Postgres installed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d6286c92-d964-46d9-9f48-b50fb04c6333",
   "metadata": {},
   "outputs": [],
   "source": [
    "#brew install postgresql\n",
    "#brew services start postgresql"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de7eb839-df2f-4dbb-a9d3-d3c643773b6c",
   "metadata": {},
   "source": [
    "#### Install the necessary packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b5205418-5831-4ee5-bd1b-96866e5b691d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#pip install fastapi \"uvicorn[standard]\" alembic psycopg2 pytest requests pydantic_settings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4b94aeb-991e-495b-bf20-d95a06cc3cb1",
   "metadata": {},
   "source": [
    "#### Save them in requirements.txt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f516344f-bc29-4779-a5bb-a5afea2fb949",
   "metadata": {},
   "outputs": [],
   "source": [
    "#pip freeze > requirements.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "385ffd71-e23a-4d2b-aa21-d16b6fe45e88",
   "metadata": {},
   "source": [
    "#### Create the Postgress database"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5ab3aa9a-5d98-4c17-ab70-ad3c9fbb0859",
   "metadata": {},
   "outputs": [],
   "source": [
    "#createdb mypdfdatabase"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30c4b925-d5c1-402d-94c6-9e102e221459",
   "metadata": {},
   "source": [
    "#### Add DB credentials to the .env file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ed256b9-ca63-4dc1-a68a-3f709f56b683",
   "metadata": {},
   "outputs": [],
   "source": [
    "# DATABASE_HOST=localhost\n",
    "# DATABASE_NAME=mypdfdatabase\n",
    "# DATABASE_USER=postgres\n",
    "# DATABASE_PASSWORD=\n",
    "# DATABASE_PORT=5432\n",
    "# APP_NAME=\"Full Stack PDF CRUD App\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83145dca-9332-41e6-8316-eaab7807926e",
   "metadata": {},
   "source": [
    "#### Create config.py file\n",
    "* Pydantic settings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "109fda92-e7d2-4b30-897a-cf666beaf02c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# import boto3\n",
    "# from pydantic_settings import BaseSettings\n",
    "\n",
    "# class Settings(BaseSettings):\n",
    "#     DATABASE_HOST: str\n",
    "#     DATABASE_NAME: str\n",
    "#     DATABASE_USER: str\n",
    "#     DATABASE_PASSWORD: str\n",
    "#     DATABASE_PORT: int\n",
    "#     app_name: str = \"Full Stack PDF CRUD App\"\n",
    "#     AWS_KEY: str\n",
    "#     AWS_SECRET: str\n",
    "#     AWS_S3_BUCKET: pdf-basic-app\n",
    "\n",
    "#     @staticmethod\n",
    "#     def get_s3_client():\n",
    "#         return boto3.client(\n",
    "#             's3',\n",
    "#             aws_access_key_id=Settings().AWS_KEY,\n",
    "#             aws_secret_access_key=Settings().AWS_SECRET\n",
    "#         )\n",
    "\n",
    "#     class Config:\n",
    "#         env_file = \".env\"\n",
    "#         extra = \"ignore\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a7ae7c1-b0aa-418f-96a1-048153b462a0",
   "metadata": {},
   "source": [
    "#### Create main.py file\n",
    "* Create app\n",
    "* CORS configuration for next frontend development\n",
    "* Global HTTP exception handler\n",
    "* Two endpoints:\n",
    "    * Root (home page)\n",
    "    * items/{item_id}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "831fa83d-efc0-4dc4-bf0a-d6398e9d38a6",
   "metadata": {},
   "source": [
    "#### Start server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3cf7b8d-2ef2-440f-adcf-d625afffd768",
   "metadata": {},
   "outputs": [],
   "source": [
    "#uvicorn main:app --reload"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2062bb63-8677-4e01-aa88-1637e2b117bd",
   "metadata": {},
   "source": [
    "#### Check the app in http://127.0.0.1:8000/\n",
    "* In the terminal, check if the app name is printed"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4edb229d-41bc-4538-8f1d-ebb826689932",
   "metadata": {},
   "source": [
    "#### Alternative way to check the app from a second window of your terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ae5766a3-83ab-4d38-a938-b59ab9a3246a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://localhost:8000"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57af7105-9098-4761-8aa0-96d3e2b7917f",
   "metadata": {},
   "source": [
    "#### Another alternative to check the app from a second window of your terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "19cef3a4-ded6-4e86-86e6-d4841ca30778",
   "metadata": {},
   "outputs": [],
   "source": [
    "#pip install httpie\n",
    "#http http://localhost:8000"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27753359-5d99-4cc2-b3f6-2a9531e7382e",
   "metadata": {},
   "source": [
    "#### After the initial check, now close the app and set the database migrations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "72a8e4a3-29c9-4d5b-a544-13925017a622",
   "metadata": {},
   "outputs": [],
   "source": [
    "#ctrl-c to stop the server in the terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0240cdd0-da5f-4ed3-ab19-8bed11d0f82e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#alembic init alembic"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "823eea02-e869-4920-9e79-05434f12c294",
   "metadata": {},
   "source": [
    "The `alembic init alembic` command is used to initialize Alembic in a project. Alembic is a database migration library for Python, commonly used with SQLAlchemy (an object-relational mapping tool for Python). This command sets up Alembic so it can be used to handle database versioning in your project.\n",
    "\n",
    "When you run `alembic init alembic`, the following happens:\n",
    "\n",
    "1. **Creation of Directory Structure**: The command creates a new directory named `alembic` in your project. Within this directory, Alembic stores migration scripts and some configuration files.\n",
    "\n",
    "2. **Configuration File**: It generates an `alembic.ini` file in the root directory of your project. This file contains the necessary configuration for Alembic to connect to your database and other relevant settings.\n",
    "\n",
    "3. **Versions Directory**: Inside the `alembic` directory, a subdirectory named `versions` is created. This directory will house the individual migration scripts you create to modify your database (for example, to add tables, change schemas, etc.).\n",
    "\n",
    "4. **`env.py` File**: An `env.py` file is also created in the `alembic` directory. This file is the entry point for Alembic and is used to configure the migration context, database connection, and other aspects of the migration environment.\n",
    "\n",
    "The purpose of using Alembic is to facilitate tracking and applying changes to the database schema in a controlled and consistent manner. It allows for incremental versions to be applied to the database, which is crucial in development, testing, and production environments, especially in large teams where multiple developers may be making changes to the database."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "875e6b66-04ce-4eed-8e90-fc46c7adfe76",
   "metadata": {},
   "source": [
    "#### Edit alembic/env.py\n",
    "* To have access to .env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c253f7af-e297-423b-b816-7ffe727f7b1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from dotenv import load_dotenv\n",
    "# load_dotenv()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "244d9067-edb3-47db-974d-f0cce2e155f0",
   "metadata": {},
   "source": [
    "Insert next line after line 13:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edc93a6c-cbb8-46c3-8544-3f2a505e5cb8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# config.set_main_option(\"sqlalchemy.url\", f\"postgresql://{os.environ['DATABASE_USER']}:@{os.environ['DATABASE_HOST']}:{os.environ['DATABASE_PORT']}/{os.environ['DATABASE_NAME']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0a7b1f1-090e-4ccc-85a2-dc296180a0cd",
   "metadata": {},
   "source": [
    "#### Create pdfs table from terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "871b2512-e54d-4d5d-9284-bc4c5e5641af",
   "metadata": {},
   "outputs": [],
   "source": [
    "#alembic revision -m \"create pdfs table\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b089570-2ce2-4aad-8f05-1810f7275a9d",
   "metadata": {},
   "source": [
    "#### Check the updates in alambic/versions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e64b1350-797e-492d-bd9d-c9f529ec50e4",
   "metadata": {},
   "source": [
    "#### Edit xxx_create_pdfs_table.py to define the schema of the new table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f130691e-86c2-4de5-a416-0255134f0d08",
   "metadata": {},
   "outputs": [],
   "source": [
    "# def upgrade():\n",
    "#     op.create_table(\n",
    "#         'pdfs',\n",
    "#         sa.Column('id', sa.BigInteger, primary_key=True),\n",
    "#         sa.Column('name', sa.Text, nullable=False),\n",
    "#         sa.Column('file', sa.Text, nullable=False),\n",
    "#         sa.Column('selected', sa.Boolean, nullable=False, default=False)\n",
    "#     )\n",
    "\n",
    "# def downgrade():\n",
    "#     op.drop_table('pdfs')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed83c9af-c5a2-486d-b215-721aa4ed5571",
   "metadata": {},
   "source": [
    "#### Run the migration from terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4742e693-71b9-41b9-a63c-72e28f2b9555",
   "metadata": {},
   "outputs": [],
   "source": [
    "#psql -d mypdfdatabase"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d5bf3c9c-dff7-4b41-8263-b21e520b7827",
   "metadata": {},
   "outputs": [],
   "source": [
    "#CREATE USER user52 WITH PASSWORD 'pass52';"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da96adba-4a0b-4901-9c26-562953c5fc8f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#GRANT ALL PRIVILEGES ON DATABASE mypdfdatabase TO user52;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "beeffb74-a479-4278-8fae-6fbf1bf12e5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#\\q"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8705bac0-13d4-4b0b-9d72-86a92e978713",
   "metadata": {},
   "source": [
    "#### Edit this line in alembic.ini"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec9b4f57-9deb-452f-9128-7d00c8e1d97a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#sqlalchemy.url = postgresql://user52:pass52@localhost/mydatabase"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f4f3c26-a70c-438e-be6c-b13d14f6c342",
   "metadata": {},
   "source": [
    "#### Edit .env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8079d8cf-ca50-4370-b043-42bac20869aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# DATABASE_USER=user52\n",
    "# DATABASE_PASSWORD=pass52"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b25c3def-73c6-41af-9983-78fb216edd9a",
   "metadata": {},
   "source": [
    "#### Run the database migration from terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12241ec1-4dd4-442b-a6c2-6d8ccb0452e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "#alembic upgrade head"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8a36a7d-3531-43b0-8666-0e5aa8685135",
   "metadata": {},
   "source": [
    "#### Check the database in terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "400f4882-e1f1-4617-847d-7c03118a4a3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#psql mypdfdatabase\n",
    "#\\dt\n",
    "#select * from pdfs\n",
    "#\\q"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d844f73-c7be-4a18-819e-4c93bab3c0a9",
   "metadata": {},
   "source": [
    "#### Set up the schemas\n",
    "* Create the file `schemas.py` in the root directory of the app\n",
    "* It defines the data model: data structure, type, validation and extra configuration\n",
    "* PDFRequest is a data model with 3 data types\n",
    "* PDFResponse is a data model with 4 data types\n",
    "* Config defines an extra configuration in PDFResponse\n",
    "    * from_attributes = True allows the model to work with ORMs like SQLAlchemy. Useful when we retrieve data from a database using a ORM (Object-Relational Mapping) and deliver those data in a structured format through an API.\n",
    "    * because we want to serialize our database entities (convert Python objects into JSON format)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2bb9a8b7-ea8a-44be-a44d-2b70398cc25b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from pydantic import BaseModel\n",
    "# from typing import Optional\n",
    "\n",
    "# class PDFRequest(BaseModel):\n",
    "#     name: str\n",
    "#     selected: bool\n",
    "#     file: str\n",
    "\n",
    "# class PDFResponse(BaseModel):\n",
    "#     id: int\n",
    "#     name: str\n",
    "#     selected: bool\n",
    "#     file: str\n",
    "\n",
    "#     class Config:\n",
    "#         from_attributes = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "935df77f-20d9-481f-9ff9-dbcfcb65feaf",
   "metadata": {},
   "source": [
    "#### Create the ORM (Object-Relational Mapping)\n",
    "* Create the file `database.py` in the root directory of the app\n",
    "* create_engine connects with the database\n",
    "* sessionmaker allows to interact with the database\n",
    "* declarative_base creates a base class for the models of the database, that will be all the tables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "a447da7d-620e-45ba-bc00-23bf3337e8e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# from sqlalchemy import create_engine\n",
    "# from sqlalchemy.ext.declarative import declarative_base\n",
    "# from sqlalchemy.orm import sessionmaker\n",
    "# from dotenv import load_dotenv\n",
    "\n",
    "# load_dotenv()\n",
    "\n",
    "# SQLALCHEMY_DATABASE_URL = f\"postgresql://{os.environ['DATABASE_USER']}:@{os.environ['DATABASE_HOST']}/{os.environ['DATABASE_NAME']}\"\n",
    "\n",
    "# engine = create_engine(\n",
    "#     SQLALCHEMY_DATABASE_URL\n",
    "# )\n",
    "# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n",
    "\n",
    "# Base = declarative_base()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1afdc012-70b8-49c8-ba86-f86005b018b0",
   "metadata": {},
   "source": [
    "#### Create models.py\n",
    "* This is where we will define the database model using SQLAlquemy, a ORM library very popular in Python.\n",
    "* Using SQLAlchemy we can interact with the table usign PDFS objects instead of writing SQL commands manually.\n",
    "* The model represents the table PDFS in a database.\n",
    "* Column, Integer, String and Boolean are column types.\n",
    "* `__tablename__` assigns a name to the table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99f7de74-552a-44f9-8f0a-c6b9c7a4574b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from sqlalchemy import Boolean, Column, LargeBinary, Integer, Text\n",
    "# from database import Base\n",
    "\n",
    "# class PDF(Base):\n",
    "#     __tablename__ = \"pdfs\"\n",
    "\n",
    "#     id = Column(Integer, primary_key=True, index=True)\n",
    "#     name = Column(Text)\n",
    "#     file = Column(Text)\n",
    "#     selected = Column(Boolean, default=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73c466a2-102a-40af-bb98-27b330b9a951",
   "metadata": {},
   "source": [
    "#### Create crud.py\n",
    "* With our CRUD (Create, Read, Update, Delete) helpers\n",
    "* We import the `model` and `schema` we have defined earlier\n",
    "* `Session` is used to manage the database operations\n",
    "* `create_pdf` creates a new pdf item in the dababase\n",
    "    * `db: Session` is a SQLAlchemy session to interact with the database\n",
    "    * `pdf: schemas.PdfRequest` is a Pydantic object with the data of the new pdf item\n",
    "    *  `db_pdf` creates a new pdf item\n",
    "    *  `db_add` adds the new pdf item to the database Session\n",
    "    *  `db.commit` saves the changes in the database\n",
    "    *  `db.refresh` updates the database\n",
    "    *  returns the `db_pdf` object\n",
    "\n",
    "* `read_pdfs` displays all the non-completed pdf items\n",
    "    * if all completed, displays all the pdf items completed\n",
    "\n",
    "* `read_pdf` display the pdf item identified with the id\n",
    "\n",
    "* `uddate_pdf` updates the pdf item idenfitied with the id\n",
    "    * if the pdf item does not exist, it returns `None`\n",
    "    * if the pdf item exists, it updates it and saves it\n",
    "      \n",
    "* `delete_pdf` deletes the pdf item identified with the id\n",
    "    * if the pdf item does not exist, it returns `None`\n",
    "    * if the pdf item exists, it deletes it and saves the changes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2ab90fad-9de7-4983-aa53-07f0c2a9df0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from sqlalchemy.orm import Session\n",
    "# from fastapi import UploadFile, HTTPException\n",
    "# import models, schemas\n",
    "# from config import Settings\n",
    "# from botocore.exceptions import NoCredentialsError, BotoCoreError\n",
    "\n",
    "# def create_pdf(db: Session, pdf: schemas.PDFRequest):\n",
    "#     db_pdf = models.PDF(name=pdf.name, selected=pdf.selected, file=pdf.file)\n",
    "#     db.add(db_pdf)\n",
    "#     db.commit()\n",
    "#     db.refresh(db_pdf)\n",
    "#     return db_pdf\n",
    "\n",
    "# def read_pdfs(db: Session, selected: bool = None):\n",
    "#     if selected is None:\n",
    "#         return db.query(models.PDF).all()\n",
    "#     else:\n",
    "#         return db.query(models.PDF).filter(models.PDF.selected == selected).all()\n",
    "\n",
    "# def read_pdf(db: Session, id: int):\n",
    "#     return db.query(models.PDF).filter(models.PDF.id == id).first()\n",
    "\n",
    "# def update_pdf(db: Session, id: int, pdf: schemas.PDFRequest):\n",
    "#     db_pdf = db.query(models.PDF).filter(models.PDF.id == id).first()\n",
    "#     if db_pdf is None:\n",
    "#         return None\n",
    "#     update_data = pdf.dict(exclude_unset=True)\n",
    "#     for key, value in update_data.items():\n",
    "#         setattr(db_pdf, key, value)\n",
    "#     db.commit()\n",
    "#     db.refresh(db_pdf)\n",
    "#     return db_pdf\n",
    "\n",
    "# def delete_pdf(db: Session, id: int):\n",
    "#     db_pdf = db.query(models.PDF).filter(models.PDF.id == id).first()\n",
    "#     if db_pdf is None:\n",
    "#         return None\n",
    "#     db.delete(db_pdf)\n",
    "#     db.commit()\n",
    "#     return True\n",
    "\n",
    "# def upload_pdf(db: Session, file: UploadFile, file_name: str):\n",
    "#     s3_client = Settings.get_s3_client()\n",
    "#     BUCKET_NAME = Settings().AWS_S3_BUCKET\n",
    "\n",
    "#     try:\n",
    "#         s3_client.upload_fileobj(\n",
    "#             file.file,\n",
    "#             BUCKET_NAME,\n",
    "#             file_name,\n",
    "#             ExtraArgs={'ACL': 'public-read'}\n",
    "#         )\n",
    "#         file_url = f'https://{BUCKET_NAME}.s3.amazonaws.com/{file_name}'\n",
    "        \n",
    "#         db_pdf = models.PDF(name=file.filename, selected=False, file=file_url)\n",
    "#         db.add(db_pdf)\n",
    "#         db.commit()\n",
    "#         db.refresh(db_pdf)\n",
    "#         return db_pdf\n",
    "#     except NoCredentialsError:\n",
    "#         raise HTTPException(status_code=500, detail=\"Error in AWS credentials\")\n",
    "#     except BotoCoreError as e:\n",
    "#         raise HTTPException(status_code=500, detail=str(e))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c4d9859-094c-4532-bbe7-f7c331bc460e",
   "metadata": {},
   "source": [
    "#### Set up the router\n",
    "* Create the new file `routers/todos.py`\n",
    "* FastAPI API CRUD routes\n",
    "* APIRouter creates the API router with the prefix /pdfs\n",
    "* `get_db` opens and closes a SQLAlchemy session\n",
    "* `Depends(get_db)` injects a db session on each route\n",
    "\n",
    "* `@router.post`: route to create a new pdf item\n",
    "    * `schemas.ToDoRequest` is the model that validates the data types\n",
    "    * uses the `create_pdf` function defined in crud.py\n",
    "\n",
    "* `@router.get(\"\")` en `/pdfs`: route to display all pdf items\n",
    "    * uses the `read_pdfs` function defined in crud.py\n",
    "    * can filter pdf items based on `selected` status\n",
    " \n",
    "* `@router.get(\"/{id}\")`: route to display one pdf item by id\n",
    "    * uses the `read_pdf` function defined in crud.py\n",
    "    * if it is not found, it returns a 404 error message\n",
    "\n",
    "* `@router.put(\"/{id}\")`: route to update one pdf item by id\n",
    "    * uses the `update_pdf` function defined in crud.py\n",
    "\n",
    "* `@router.delete(\"/{id}\")`: route to display one pdf item by id\n",
    "    * uses the `delete_pdf` function defined in crud.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cabbd9cc-756b-4bbe-ae7d-fc17bbcebb7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from typing import List\n",
    "# from sqlalchemy.orm import Session\n",
    "# from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File\n",
    "# import schemas\n",
    "# import crud\n",
    "# from database import SessionLocal\n",
    "# from uuid import uuid4\n",
    "\n",
    "# router = APIRouter(prefix=\"/pdfs\")\n",
    "\n",
    "# def get_db():\n",
    "#     db = SessionLocal()\n",
    "#     try:\n",
    "#         yield db\n",
    "#     finally:\n",
    "#         db.close()\n",
    "\n",
    "# @router.post(\"\", response_model=schemas.PDFResponse, status_code=status.HTTP_201_CREATED)\n",
    "# def create_pdf(pdf: schemas.PDFRequest, db: Session = Depends(get_db)):\n",
    "#     return crud.create_pdf(db, pdf)\n",
    "\n",
    "# @router.post(\"/upload\", response_model=schemas.PDFResponse, status_code=status.HTTP_201_CREATED)\n",
    "# def upload_pdf(file: UploadFile = File(...), db: Session = Depends(get_db)):\n",
    "#     file_name = f\"{uuid4()}-{file.filename}\"\n",
    "#     return crud.upload_pdf(db, file, file_name)\n",
    "\n",
    "# @router.get(\"\", response_model=List[schemas.PDFResponse])\n",
    "# def get_pdfs(selected: bool = None, db: Session = Depends(get_db)):\n",
    "#     return crud.read_pdfs(db, selected)\n",
    "\n",
    "# @router.get(\"/{id}\", response_model=schemas.PDFResponse)\n",
    "# def get_pdf_by_id(id: int, db: Session = Depends(get_db)):\n",
    "#     pdf = crud.read_pdf(db, id)\n",
    "#     if pdf is None:\n",
    "#         raise HTTPException(status_code=404, detail=\"PDF not found\")\n",
    "#     return pdf\n",
    "\n",
    "# @router.put(\"/{id}\", response_model=schemas.PDFResponse)\n",
    "# def update_pdf(id: int, pdf: schemas.PDFRequest, db: Session = Depends(get_db)):\n",
    "#     updated_pdf = crud.update_pdf(db, id, pdf)\n",
    "#     if updated_pdf is None:\n",
    "#         raise HTTPException(status_code=404, detail=\"PDF not found\")\n",
    "#     return updated_pdf\n",
    "\n",
    "# @router.delete(\"/{id}\", status_code=status.HTTP_200_OK)\n",
    "# def delete_pdf(id: int, db: Session = Depends(get_db)):\n",
    "#     if not crud.delete_pdf(db, id):\n",
    "#         raise HTTPException(status_code=404, detail=\"PDF not found\")\n",
    "#     return {\"message\": \"PDF successfully deleted\"}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc235a1f-9e12-4f96-a36f-63beb6bf240c",
   "metadata": {},
   "source": [
    "#### Uncomment these two lines in main.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "f565c1a7-9f74-45d5-8755-3e24265b2574",
   "metadata": {},
   "outputs": [],
   "source": [
    "#from routers import todos\n",
    "#app.include_router(todos.router)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d5c84e5-ef96-4a51-a91c-34b2e4d44beb",
   "metadata": {},
   "source": [
    "#### Run the server from the terminal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "f80d90fc-8328-4a85-b863-9ab9df4c0581",
   "metadata": {},
   "outputs": [],
   "source": [
    "#uvicorn main:app --reload"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "739674aa-c345-4d7a-a918-4eafc327b535",
   "metadata": {},
   "source": [
    "#### In a second terminal window, make the following tests (need httpie installed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77d59b99-4bfa-423a-9c2a-7f0e201497ca",
   "metadata": {},
   "source": [
    "Create one pdf item:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "b6a6811c-ae5a-4bde-9fc7-2072d687623a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl -X POST -F \"file=@/YourFilePath.pdf\" http://127.0.0.1:8000/pdfs/upload"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "041c464f-fe16-4029-a2b8-43987741026c",
   "metadata": {},
   "source": [
    "Display all the pdf items:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "de18c3df-d1bb-47e8-b9ac-be6986728507",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://127.0.0.1:8000/pdfs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e16db86e-1d06-4542-8d7a-924d4f370156",
   "metadata": {},
   "source": [
    "Display the pdf item with the id=1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "5da52daa-b0c6-4c86-9960-e777ee905ce0",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://127.0.0.1:8000/pdfs/1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c742602-6d41-4319-8ddf-91e5283d1c0c",
   "metadata": {},
   "source": [
    "Update it changing selected to true:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d68733c7-2dd2-43b5-aa41-2b24b1eb0546",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl -X PUT -H \"Content-Type: application/json\" -d '{\"name\": \"allergic.pdf\", \"selected\": true, \"file\": \"https://pdf-basic-app.s3.amazonaws.com/e4f21111-40e4-48fd-bf7d-b972c37d4b16-allergic.pdf\"}' http://127.0.0.1:8000/pdfs/1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1bd9536-fcdc-4ed1-a958-35c12d071258",
   "metadata": {},
   "source": [
    "Display all the pdf items:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "3d7742e7-d094-49cd-b884-f0069d295423",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://127.0.0.1:8000/pdfs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4343710-936b-4b07-88ef-14d8a166c8c0",
   "metadata": {},
   "source": [
    "Delete the pdf item with the id=1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5592f481-9a79-4923-beef-f91a52c5e828",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl -X DELETE http://127.0.0.1:8000/pdfs/1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7dcda4c5-660a-4910-831e-ca6a97a6d220",
   "metadata": {},
   "source": [
    "Display all the pdf items:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "540c064c-a8fd-4f62-b9e3-a96833650232",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://127.0.0.1:8000/pdfs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c12ed1dc-366a-4fb1-bf54-a23124421f65",
   "metadata": {},
   "source": [
    "The following should get a \"PDF not found\" message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0be5eff4-80e5-4c70-b8a1-38a336dfbd9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#curl http://127.0.0.1:8000/pdfs/1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0609c187-ebb8-40f5-8304-061c15b7ac45",
   "metadata": {},
   "source": [
    "## Part 3: Frontend with Next.js"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b84bffa-d0c7-4af6-b184-8217326c8ea4",
   "metadata": {},
   "source": [
    "#### Create frontend directory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "15fe91c4-13a5-49c0-bd5b-47103eba2b30",
   "metadata": {},
   "outputs": [],
   "source": [
    "#cd .."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "040da2dc-c452-4e11-aba2-abfae9b90714",
   "metadata": {},
   "outputs": [],
   "source": [
    "#mkdir frontend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b2a9f9fe-19ae-4489-981a-ae8c0d3c0734",
   "metadata": {},
   "outputs": [],
   "source": [
    "#cd frontend"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51eb14fb-2eda-4881-b76a-1b9035930a19",
   "metadata": {},
   "source": [
    "#### Create a Next.js starter template\n",
    "* app name: pdf-app\n",
    "* typescript: no\n",
    "* eslint: yes\n",
    "* tailwind css: no\n",
    "* src/ directory: no\n",
    "* app router: no\n",
    "* customize import alias: no"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "8fc5564c-f37d-4a7f-b892-69da28f4de58",
   "metadata": {},
   "outputs": [],
   "source": [
    "#npx create-next-app@latest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "171d3b40-5bcb-4324-805a-c238e73198b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "#cd pdf-app\n",
    "#npm run dev"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04d71c35-0367-4553-a188-8a3444405ca1",
   "metadata": {},
   "source": [
    "#### Check how the starter template looks in your browser\n",
    "* open browser in localhost:3000"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a4c9d13-e7f4-4512-b848-20f99fa9d54f",
   "metadata": {},
   "source": [
    "#### Open the starter template in your editor"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1f15e75-2c0d-44b9-b2b6-0a5c3f5a3032",
   "metadata": {},
   "source": [
    "#### Create the .env file\n",
    "* Enter the URL of the backend API\n",
    "* This is a Next.js convention: any variable starting with NEXT_PUBLIC_ will be available in the client side and in the server side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "ddfa46c6-de1b-4a67-ad27-8ca8abb52756",
   "metadata": {},
   "outputs": [],
   "source": [
    "# NEXT_PUBLIC_API_URL=http://localhost:8000"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb5661da-df0b-4d7e-93ec-3aea4d418d5c",
   "metadata": {},
   "source": [
    "#### Edit index.js\n",
    "* remove the default content provided for this file\n",
    "* the following content imports 2 next.js components we have not created yet:\n",
    "    * Layout (we can re-use this component in any page).\n",
    "    * ToDoList (all of our TODO functionality).\n",
    "\n",
    "* The ToDoList component will be inside the Layout component. This is called \"composition\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "39d02fb2-8705-4ad0-aca0-742f949e0eb6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import Head from 'next/head'\n",
    "# import Layout from '../components/layout';\n",
    "# import PDFList from '../components/pdf-list';\n",
    "# import styles from '../styles/layout.module.css'\n",
    "\n",
    "# export default function Home() {\n",
    "#   return (\n",
    "#     <div>\n",
    "#       <Head>\n",
    "#         <title>Basic PDF CRUD App</title>\n",
    "#         <meta name=\"description\" content=\"Basic PDF CRUD App\" />\n",
    "#         <link rel=\"icon\" href=\"/favicon.ico\" />\n",
    "#       </Head>\n",
    "#       <Layout>\n",
    "#         <PDFList />\n",
    "#       </Layout>\n",
    "#     </div>\n",
    "#   )\n",
    "# }"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48a124fa-190b-42a5-b344-911c4778973d",
   "metadata": {},
   "source": [
    "#### Create the components folder in the root directory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcae6151-c8c1-428e-9b4e-be6f192e6608",
   "metadata": {},
   "source": [
    "#### Create the components/layout.js file\n",
    "* Imports css styles that are not created yet\n",
    "* Defines a React component called Layout\n",
    "    * This component accepts the parameter props\n",
    "    * React components use JSX syntax, very similar to HTML\n",
    "    * The {props.children} is where we use composition, this means that we can replace this with another React component when we use the Layout component anywhere."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20133e4b-229e-460c-a146-aee5cca67b25",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import styles from '../styles/layout.module.css'\n",
    "\n",
    "# export default function Layout(props) {\n",
    "#   return (\n",
    "#     <div className={styles.layout}>\n",
    "#       <h1 className={styles.title}>Basic PDF CRUD App</h1>\n",
    "#       <p className={styles.subtitle}>By <a href=\"https://aiaccelera.com/\" target=\"_blank\">AI Accelera</a> and <a href=\"https://aceleradoraai.com/\" target=\"_blank\">Aceleradora AI</a></p>\n",
    "#       {props.children}\n",
    "#     </div>\n",
    "#   )\n",
    "# }"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c3b62f3-7b4b-4338-ab75-9698f6bb851f",
   "metadata": {},
   "source": [
    "### Create the components/pdf-list.js file\n",
    "* Will require to install lodash. In terminal: npm install lodash\n",
    "* Imports css that is still not created.\n",
    "* Imports PDF from a file not yet created."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a53ba14-3cc7-4bb5-a798-6b61f280db96",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import styles from '../styles/pdf-list.module.css';\n",
    "# import { useState, useEffect, useCallback, useRef } from 'react';\n",
    "# import { debounce } from 'lodash';\n",
    "# import PDFComponent from './pdf';\n",
    "\n",
    "# export default function PdfList() {\n",
    "#   const [pdfs, setPdfs] = useState([]);\n",
    "#   const [selectedFile, setSelectedFile] = useState(null);\n",
    "#   const [filter, setFilter] = useState();\n",
    "#   const didFetchRef = useRef(false);\n",
    "\n",
    "#   useEffect(() => {\n",
    "#     if (!didFetchRef.current) {\n",
    "#       didFetchRef.current = true;\n",
    "#       fetchPdfs();\n",
    "#     }\n",
    "#   }, []);\n",
    "\n",
    "#   async function fetchPdfs(selected) {\n",
    "#     let path = '/pdfs';\n",
    "#     if (selected !== undefined) {\n",
    "#       path = `/pdfs?selected=${selected}`;\n",
    "#     }\n",
    "#     const res = await fetch(process.env.NEXT_PUBLIC_API_URL + path);\n",
    "#     const json = await res.json();\n",
    "#     setPdfs(json);\n",
    "#   }\n",
    "\n",
    "#   const debouncedUpdatePdf = useCallback(debounce((pdf, fieldChanged) => {\n",
    "#     updatePdf(pdf, fieldChanged);\n",
    "#   }, 500), []);\n",
    "\n",
    "#   function handlePdfChange(e, id) {\n",
    "#     const target = e.target;\n",
    "#     const value = target.type === 'checkbox' ? target.checked : target.value;\n",
    "#     const name = target.name;\n",
    "#     const copy = [...pdfs];\n",
    "#     const idx = pdfs.findIndex((pdf) => pdf.id === id);\n",
    "#     const changedPdf = { ...pdfs[idx], [name]: value };\n",
    "#     copy[idx] = changedPdf;\n",
    "#     debouncedUpdatePdf(changedPdf, name);\n",
    "#     setPdfs(copy);\n",
    "#   }\n",
    "\n",
    "#   async function updatePdf(pdf, fieldChanged) {\n",
    "#     const data = { [fieldChanged]: pdf[fieldChanged] };\n",
    "\n",
    "#     await fetch(process.env.NEXT_PUBLIC_API_URL + `/pdfs/${pdf.id}`, {\n",
    "#       method: 'PUT',\n",
    "#       body: JSON.stringify(data),\n",
    "#       headers: { 'Content-Type': 'application/json' }\n",
    "#     });\n",
    "#   }\n",
    "\n",
    "#   async function handleDeletePdf(id) {\n",
    "#     const res = await fetch(process.env.NEXT_PUBLIC_API_URL + `/pdfs/${id}`, {\n",
    "#       method: 'DELETE',\n",
    "#       headers: { 'Content-Type': 'application/json' }\n",
    "#     });\n",
    "\n",
    "#     if (res.ok) {\n",
    "#       const copy = pdfs.filter((pdf) => pdf.id !== id);\n",
    "#       setPdfs(copy);\n",
    "#     }\n",
    "#   }\n",
    "\n",
    "#   const handleFileChange = (event) => {\n",
    "#     setSelectedFile(event.target.files[0]);\n",
    "#   };\n",
    "\n",
    "#   const handleUpload = async (event) => {\n",
    "#     event.preventDefault();\n",
    "#     if (!selectedFile) {\n",
    "#       alert(\"Please select file to load.\");\n",
    "#       return;\n",
    "#     }\n",
    "\n",
    "#     const formData = new FormData();\n",
    "#     formData.append(\"file\", selectedFile);\n",
    "\n",
    "#     const response = await fetch(process.env.NEXT_PUBLIC_API_URL + \"/pdfs/upload\", {\n",
    "#       method: \"POST\",\n",
    "#       body: formData,\n",
    "#     });\n",
    "\n",
    "#     if (response.ok) {\n",
    "#       const newPdf = await response.json();\n",
    "#       setPdfs([...pdfs, newPdf]);\n",
    "#     } else {\n",
    "#       alert(\"Error loading file.\");\n",
    "#     }\n",
    "#   };\n",
    "\n",
    "#   function handleFilterChange(value) {\n",
    "#     setFilter(value);\n",
    "#     fetchPdfs(value);\n",
    "#   }\n",
    "\n",
    "#   return (\n",
    "#     <div className={styles.container}>\n",
    "#       <div className={styles.mainInputContainer}>\n",
    "#         <form onSubmit={handleUpload}>\n",
    "#           <input className={styles.mainInput} type=\"file\" accept=\".pdf\" onChange={handleFileChange} />\n",
    "#           <button className={styles.loadBtn} type=\"submit\">Load PDF</button>\n",
    "#         </form>\n",
    "#       </div>\n",
    "#       {!pdfs.length && <div>Loading...</div>}\n",
    "#       {pdfs.map((pdf) => (\n",
    "#         <PDFComponent key={pdf.id} pdf={pdf} onDelete={handleDeletePdf} onChange={handlePdfChange} />\n",
    "#       ))}\n",
    "#       <div className={styles.filters}>\n",
    "#         <button className={`${styles.filterBtn} ${filter === undefined && styles.filterActive}`} onClick={() => handleFilterChange()}>See All</button>\n",
    "#         <button className={`${styles.filterBtn} ${filter === true && styles.filterActive}`} onClick={() => handleFilterChange(true)}>See Selected</button>\n",
    "#         <button className={`${styles.filterBtn} ${filter === false && styles.filterActive}`} onClick={() => handleFilterChange(false)}>See Not Selected</button>\n",
    "#       </div>\n",
    "#     </div>\n",
    "#   );\n",
    "# }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50112f5d-6a47-4673-b2c2-5dda44516dba",
   "metadata": {},
   "source": [
    "#### Global explanation of this component"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2187eaa0-7362-4d1d-a9e4-613f2c2eac4f",
   "metadata": {},
   "source": [
    "This code is for a React component called `PdfList`, which is part of a web application for managing a list of PDF items. Here's a simple breakdown of what this component does:\n",
    "\n",
    "1. **Imports and Setup**:\n",
    "   - CSS styles are imported for use in the component.\n",
    "   - Several hooks from React (`useState`, `useEffect`, `useCallback`, `useRef`) and a utility (`debounce` from `lodash`) are imported. These are used for managing state, side effects, and optimizing function calls.\n",
    "   - The `PDFComponent` component is also imported, which is used to render each PDF item.\n",
    "\n",
    "2. **Component State Management**:\n",
    "   - `useState` is used to create several state variables: `pdfs` (the list of pdf items), `mainInput` (the value of a main input field for adding new pdf items), and `filter` (to filter the displayed pdf items).\n",
    "   - `useRef` is used to create a ref (`didFetchRef`) for tracking if the pdf items have been fetched.\n",
    "\n",
    "3. **Fetching PDF items on Component Mount**:\n",
    "   - `useEffect` is used to fetch the list of pdf items when the component first renders. It checks `didFetchRef` to ensure fetching only occurs once.\n",
    "\n",
    "4. **Functions for Handling PDF items**:\n",
    "   - `fetchPdfs` fetches pdf items from an API filtered by selected status.\n",
    "   - `debouncedUpdatePdf` is a debounced version of an `updatePdf` function, used for updating pdf items with a delay to improve performance.\n",
    "   - `handlePdfChange` handles changes to pdf items (like marking them as selected).\n",
    "   - `updatePdf` updates a pdf item in the backend.\n",
    "   - `addPdf` adds a new pdf item to the list.\n",
    "   - `handleDeletePdf` deletes a pdf item.\n",
    "\n",
    "5. **Handling User Input**:\n",
    "   - `handleMainInputChange` updates the `mainInput` state when the user types in the input field.\n",
    "   - `handleKeyDown` checks for the 'Enter' key to add a new pdf item.\n",
    "\n",
    "6. **Filtering PDF items**:\n",
    "   - `handleFilterChange` updates the filter state and fetches pdf items based on the selected filter.\n",
    "\n",
    "7. **Rendering the Component**:\n",
    "   - The `return` statement contains the JSX (HTML-like syntax) for rendering the component.\n",
    "   - It includes an input field for adding new pdf items, a loading message, a list of pdf itms (using the `PDFComponent` component for each item), and buttons for filtering the pdf items by different criteria (all, not selected, selected).\n",
    "\n",
    "In simple terms, the `PdfList` component allows users to add, view, filter, and delete pdf items. It interacts with an API for data fetching and updating, and it uses various React features for handling state, effects, and user interactions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f570890e-1524-4d02-829f-ac01b49ddadf",
   "metadata": {},
   "source": [
    "## Create the /components/pdf.js file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "40aa7478-844b-42a8-96b3-e9b35329236b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import Image from 'next/image';\n",
    "# import styles from '../styles/pdf.module.css';\n",
    "\n",
    "# export default function PDFComponent(props) {\n",
    "#   const { pdf, onChange, onDelete } = props;\n",
    "#   return (\n",
    "#     <div className={styles.pdfRow}>\n",
    "#       <input\n",
    "#         className={styles.pdfCheckbox}\n",
    "#         name=\"selected\"\n",
    "#         type=\"checkbox\"\n",
    "#         checked={pdf.selected}\n",
    "#         onChange={(e) => onChange(e, pdf.id)}\n",
    "#       />\n",
    "#       <input\n",
    "#         className={styles.pdfInput}\n",
    "#         autoComplete=\"off\"\n",
    "#         name=\"name\"\n",
    "#         type=\"text\"\n",
    "#         value={pdf.name}\n",
    "#         onChange={(e) => onChange(e, pdf.id)}\n",
    "#       />\n",
    "#       <a\n",
    "#         href={pdf.file}\n",
    "#         target=\"_blank\"\n",
    "#         rel=\"noopener noreferrer\"\n",
    "#         className={styles.viewPdfLink}\n",
    "#       >\n",
    "#         <Image src=\"/document-view.svg\" width=\"22\" height=\"22\" />\n",
    "#       </a>\n",
    "#       <button\n",
    "#         className={styles.deleteBtn}\n",
    "#         onClick={() => onDelete(pdf.id)}\n",
    "#       >\n",
    "#         <Image src=\"/delete-outline.svg\" width=\"24\" height=\"24\" />\n",
    "#       </button>\n",
    "#     </div>\n",
    "#   );\n",
    "# }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93cf4414-9595-4e17-8a0f-e15d1b1b59d5",
   "metadata": {},
   "source": [
    "The previous code is for a React component called `PDFComponent`, typically used in an application built with Next.js to manage PDF documents. The component is designed to display and interact with a single PDF item. Here's a breakdown of the code in simple terms:\n",
    "\n",
    "1. **Imports**:\n",
    "   - `Image` from 'next/image': This is a Next.js optimized image component that allows for efficient image loading.\n",
    "   - `styles` from a CSS module: This imports specific CSS styles for the component.\n",
    "\n",
    "2. **Component Function `PDFComponent`**:\n",
    "   - `export default function PDFComponent(props) {...}`: This defines the `PDFComponent` component. It is a functional component that takes `props` as its argument.\n",
    "   - `const { pdf, onChange, onDelete } = props;`: This line extracts the `pdf`, `onChange`, and `onDelete` properties from `props`. These are likely passed from the parent component and contain the PDF item data and functions for handling changes and deletion.\n",
    "\n",
    "3. **Rendering the PDF Item**:\n",
    "   - The component returns JSX (a syntax extension for JavaScript used with React) that describes the structure of the UI for the PDF item.\n",
    "   - `<div className={styles.pdfRow} key={pdf.id}>`: This `div` acts as a container for the PDF item. It uses styles from the imported CSS module and a unique `key` based on the PDF's `id`.\n",
    "\n",
    "4. **PDF Checkbox**:\n",
    "   - `<input className={styles.pdfCheckbox} ...>`: This is a checkbox input that allows marking the PDF item as selected or not.\n",
    "   - `checked={pdf.selected}`: The checkbox is checked or unchecked based on the `selected` property of the `pdf` object.\n",
    "   - `onChange={(e) => onChange(e, pdf.id)}`: This sets up a handler so that when the checkbox changes, the `onChange` function is called with the event `e` and the `id` of the pdf.\n",
    "\n",
    "5. **PDF Text Input**:\n",
    "   - `<input className={styles.pdfInput} ...>`: This is a text input field for the PDF item's name.\n",
    "   - `value={pdf.name}`: The input displays the name of the pdf.\n",
    "   - The `onChange` handler here works similarly to the checkbox, allowing the name of the pdf to be edited.\n",
    "\n",
    "6. **Delete Button**:\n",
    "   - `<button className={styles.deleteBtn} ...>`: This is a button for deleting the PDF item.\n",
    "   - `onClick={() => onDelete(pdf.id)}`: When the button is clicked, the `onDelete` function is called with the `id` of the pdf.\n",
    "   - The button includes an `<Image>` component, which displays an icon from a provided source (`/delete-outline.svg`). The `width` and `height` are set for the image.\n",
    "\n",
    "In simple terms, the `PDFComponent` is a part of a user interface for a PDF management application. It displays each PDF item with a checkbox to mark it as selected, an editable text field for the PDF name, and a delete button with an image icon. The component allows for interaction with the PDF item, including changing its selection status, editing its name, and removing it from the list."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f66379e-4729-4d98-904b-e63cecbd5ebb",
   "metadata": {},
   "source": [
    "## Create the style files"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14acb0d4-9921-411c-9444-b81f96a09260",
   "metadata": {},
   "source": [
    "#### Delete the content in the default css files:\n",
    "* globals.css\n",
    "* Home.module.css"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1621f75c-1d96-406d-b4ad-dabe4301ccad",
   "metadata": {},
   "source": [
    "#### styles/layout.module.css"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36ea71eb-8f59-4dcf-b6c1-c6469fda3a91",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .layout {\n",
    "#     width: 700px;\n",
    "#     margin: auto;\n",
    "#     display: block;\n",
    "# }\n",
    "\n",
    "# .title {\n",
    "#     text-align: center;\n",
    "#     font-size: 24px;\n",
    "#     margin: 50px 10px 10px 10px;\n",
    "# }\n",
    "\n",
    "# .subtitle {\n",
    "#     text-align: center;\n",
    "#     font-size: 16px;\n",
    "#     margin: 10px;\n",
    "#     margin-bottom: 20px;\n",
    "# }"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4caead0-a4eb-40ad-ab29-136a8b1320d5",
   "metadata": {},
   "source": [
    "#### styles/pdf-list.module.css"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87b8eb7b-44da-43ed-b7a0-9ca51599f6c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .container {\n",
    "#   width: 600px;\n",
    "#   border: 1px solid #ccc;\n",
    "#   padding: 50px;\n",
    "#   border-radius: 8px;\n",
    "# }\n",
    "\n",
    "# .uploadForm {\n",
    "#   margin-top: 20px;\n",
    "# }\n",
    "\n",
    "# .uploadInput {\n",
    "#   margin-bottom: 10px;\n",
    "#   width: 100%;\n",
    "#   padding: 8px;\n",
    "#   border: 1px solid #ccc;\n",
    "#   border-radius: 4px;\n",
    "# }\n",
    "\n",
    "# .mainInputContainer {\n",
    "#   width: 100%;\n",
    "#   margin: 0px 0px 50px 0px;\n",
    "# }\n",
    "\n",
    "# .mainInput {\n",
    "#   padding: 5px;\n",
    "#   border: 1px solid #ccc;\n",
    "#   margin: auto;\n",
    "#   display: block;\n",
    "#   width: 560px;\n",
    "#   height: 20px;\n",
    "#   margin-bottom: 20px;\n",
    "# }\n",
    "\n",
    "# .filters {\n",
    "#   display: flex;\n",
    "#   justify-content: space-between;\n",
    "#   padding: 20px;\n",
    "#   margin-top: 20px;\n",
    "# }\n",
    "\n",
    "\n",
    "# .loadBtn {\n",
    "#   background: none;\n",
    "#   border: 1px solid #ccc;\n",
    "#   padding: 5px 10px;\n",
    "#   border-radius: 4px;\n",
    "#   margin: auto;\n",
    "#   display: block;\n",
    "# }\n",
    "\n",
    "# .filterBtn {\n",
    "#   background: none;\n",
    "#   border: 1px solid #ccc;\n",
    "#   padding: 5px 10px;\n",
    "#   border-radius: 4px;\n",
    "#   margin-right: 5px;\n",
    "# }\n",
    "\n",
    "# .filterActive {\n",
    "#   text-decoration: underline;\n",
    "#   color: blue;\n",
    "# }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6f3f0a3-18e7-4694-8c2c-6309bdca5915",
   "metadata": {},
   "source": [
    "#### styles/pdf.module.css"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0c63e1e-722d-4850-ba89-b200e498793e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .pdfInput {\n",
    "#     padding: 8px;\n",
    "#     border: 1px solid #ccc;\n",
    "#     width: calc(100% - 10px);\n",
    "#     height: 30px;\n",
    "#     margin: 0 10px 0px 10px;\n",
    "#     border-radius: 4px;\n",
    "# }\n",
    "\n",
    "# .pdfRow {\n",
    "#     display: flex;\n",
    "#     flex-direction: row;\n",
    "#     align-items: center;\n",
    "#     margin: 10px 10px 0px 10px;\n",
    "#     padding: 5px;\n",
    "# }\n",
    "\n",
    "# .deleteBtn {\n",
    "#     background: none;\n",
    "#     border: 0;\n",
    "#     cursor: pointer;\n",
    "#     transition: color 0.3s ease;\n",
    "# }\n",
    "\n",
    "# .deleteBtn:hover {\n",
    "#     color: red;\n",
    "# }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c51e651d-bbfd-4922-882d-f22e6fedbbc0",
   "metadata": {},
   "source": [
    "## Load the delete and the view icons in the public folder\n",
    "* You can download them here:\n",
    "    * [Delete icon](https://iconduck.com/icons/28730/delete-outline)\n",
    "    * [View file icon](https://iconduck.com/icons/9856/task-view)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e23a8674-b5e8-4fb0-af3c-3ac8b84a2155",
   "metadata": {},
   "source": [
    "## Part 4: Run the full-stack app"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d58c67b9-fea3-46ca-9444-d3a8173c7f59",
   "metadata": {},
   "source": [
    "#### You will need to have the backend app open from another terminal window"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eec08577-11de-4fc4-bb0d-f7fb95bba4a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#uvicorn main:app --reload"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c70a071-eac1-4218-8d63-2c0e359306ca",
   "metadata": {},
   "source": [
    "#### Open an additional terminal window an run the frontend app"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4adf64e3-f9d2-4c91-9d8a-917108c13189",
   "metadata": {},
   "outputs": [],
   "source": [
    "#npm run dev"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f0c7779-c596-4d21-9fc1-fe6c5e15bb75",
   "metadata": {},
   "source": [
    "#### If you are using the Chrome browser, you can open DevTools and see the operations that are happening in the background when you make changes in the todo app tasks."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e46793a-1bf1-40ec-a791-725b162d4d74",
   "metadata": {},
   "source": [
    "# Note\n",
    "* In order to deploy this app to Render and Vercel you will have to follow the same process we followed for the previous ToDo Full Stack App. To avoid repeating ourselves, we will not do it again for this app.\n",
    "* In case you need it, below we copy the steps we followed for the ToDo App. Please keep in mind that you will have to make the necessary changes to adapt them for this app."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa2778dc-3486-4208-a854-62b8583d131d",
   "metadata": {},
   "source": [
    "## Part 5: Upload backend to Github"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd5e0694-d7bd-4118-bfe1-bc0bb2565473",
   "metadata": {},
   "source": [
    "Uploading your backend application to a GitHub account is a relatively straightforward process. Here's a step-by-step guide to do it:\n",
    "\n",
    "### Step 1: Create a Repository on GitHub\n",
    "\n",
    "1. **Log in to GitHub**: Go to [GitHub](https://github.com/) and make sure you're registered or log in.\n",
    "\n",
    "2. **Create a New Repository**:\n",
    "   - Click on the \"+\" icon in the top right corner and select \"New repository\".\n",
    "   - Name your repository, add a description (optional), and choose whether it should be public or private.\n",
    "   - You can also initialize the repository with a README file, a license, or a `.gitignore`, although this is optional and can be done later.\n",
    "\n",
    "### Step 2: Prepare Your Local Project\n",
    "\n",
    "1. **Organize Your Code Locally**:\n",
    "   - If you haven't already, organize your code in a folder on your computer. Ensure everything you need is included and that confidential files (like `.env` with credentials) are excluded or listed in `.gitignore`.\n",
    "\n",
    "2. **Initialize Git in Your Project** (if not already done):\n",
    "   - Open a terminal or command line.\n",
    "   - Navigate (`cd`) to your project folder.\n",
    "   - Run `git init` to initialize a new Git repository.\n",
    "\n",
    "3. **Add a `.gitignore` File** (if you don't have one):\n",
    "   - Create a `.gitignore` file at the root of your project.\n",
    "   - Add names of files or folders you don't want to upload to GitHub (for example, `node_modules`, sensitive configuration files, etc.).\n",
    "\n",
    "### Step 3: Upload Your Code to GitHub\n",
    "\n",
    "1. **Add Files to the Local Git Repository**:\n",
    "   - From the terminal, in your project folder, run `git add .` to add all files to the repository (respecting `.gitignore`).\n",
    "   - Or use `git add [file]` to add specific files.\n",
    "\n",
    "2. **Make Your First Commit**:\n",
    "   - Run `git commit -m \"First commit\"` to make the first commit with a descriptive message.\n",
    "\n",
    "3. **Link Your Local Repository with GitHub**:\n",
    "   - On GitHub, on your repository page, you'll find a URL for the repository. It will be something like `https://github.com/your-user/your-repository.git`.\n",
    "   - In your terminal, run `git remote add origin [repository URL]` to link your local repository with GitHub.\n",
    "\n",
    "4. **Push Your Code to GitHub**:\n",
    "   - Run `git push -u origin master` (or `main` if your main branch is called `main`) to push your code to the GitHub repository.\n",
    "\n",
    "### Step 4: Verify and Continue Development\n",
    "\n",
    "- **Verify on GitHub**: After uploading your code, go to your repository page on GitHub to make sure everything is there.\n",
    "- **Future Development**: For future commits, you only need to do `git add`, `git commit`, and `git push`.\n",
    "\n",
    "And with that, your backend application should be on GitHub. Always remember to keep sensitive data secure and use good Git practices for managing your code."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bdff1d2-845d-42ee-9f62-5a752438acd2",
   "metadata": {},
   "source": [
    "## Part 6: Deploy backend to Render.com"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b61b2d67-714d-4cca-9e16-e6d01115766d",
   "metadata": {},
   "source": [
    "Deploying your FastAPI backend with a PostgreSQL database on Render.com involves several steps. Render offers a fairly straightforward solution for deploying web applications and databases. Here is a basic guide to do it:\n",
    "\n",
    "### Step 1: Prepare Your FastAPI Application\n",
    "\n",
    "Before deploying, make sure your FastAPI application is production-ready. This includes:\n",
    "\n",
    "1. **Check Dependencies**: Ensure all necessary dependencies are listed in a `requirements.txt` file.\n",
    "\n",
    "2. **Application Configuration**: Verify that your application is configured to use environment variables for important configurations, such as database credentials.\n",
    "\n",
    "3. **Dockerfile (Optional)**: If you prefer to deploy using Docker, make sure you have a suitable `Dockerfile` for your application. Render supports deployments both with and without Docker.\n",
    "\n",
    "### Step 2: Set Up Your PostgreSQL Database\n",
    "\n",
    "1. **Create a Database on Render**:\n",
    "   - Go to the Render dashboard and create a new PostgreSQL database service.\n",
    "   - Render will provide the database credentials, including the hostname, port, username, password, and database name.\n",
    "\n",
    "2. **Configure Environment Variables**:\n",
    "   - Note down the database credentials, as you will need them to configure your FastAPI application.\n",
    "\n",
    "### Step 3: Deploy Your FastAPI Application\n",
    "\n",
    "1. **Create a New Web Service on Render**:\n",
    "   - In the Render dashboard, choose the option to create a new web service.\n",
    "   - Select the repository where your FastAPI code is.\n",
    "   - Configure the deployment options, such as the runtime environment (if you are not using Docker).\n",
    "\n",
    "2. **Set Environment Variables for FastAPI**:\n",
    "   - In your web service settings on Render, set the necessary environment variables for your application, including the PostgreSQL database credentials.\n",
    "\n",
    "3. **Deployment**:\n",
    "   - Render will start the deployment process once you have configured your service and saved the changes.\n",
    "   - If you have set up everything correctly, Render will build and deploy your application.\n",
    "\n",
    "4. **Review and Testing**:\n",
    "   - After deploying, be sure to review the available logs in Render to verify that everything is working as expected.\n",
    "   - Perform tests to ensure that your FastAPI application is communicating correctly with the PostgreSQL database.\n",
    "\n",
    "### Step 4: Updates and Maintenance\n",
    "\n",
    "- **Updates**: To update your application, simply push your changes to the repository connected to Render. Render will automatically initiate a new deployment.\n",
    "- **Monitor Your Application**: Use Render's tools to monitor the performance and health of your application and database.\n",
    "\n",
    "### Final Considerations\n",
    "\n",
    "- **Security**: Ensure that your application and database are configured securely.\n",
    "- **Database Backups**: Set up regular backups for your database on Render to prevent data loss.\n",
    "\n",
    "Render.com greatly facilitates the process of deploying applications and databases, integrating well with code repositories and providing a manageable platform for deployment and application management."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26eae2ea-fbb9-404e-a081-6a3853067553",
   "metadata": {},
   "source": [
    "## How to add environment variables in Render.com"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "794b3687-88b5-421d-9b7e-fc2bd5b975b6",
   "metadata": {},
   "source": [
    "To set up environment variables for your FastAPI application on Render.com, follow these steps:\n",
    "\n",
    "### Access Your Web Service Settings on Render\n",
    "\n",
    "1. **Log in to Render**: Go to [Render.com](https://render.com/) and log in to your account.\n",
    "\n",
    "2. **Navigate to Your Web Service**: In the Render dashboard, locate the web service you created for your FastAPI application.\n",
    "\n",
    "3. **Enter the Service Configuration**: Click on the web service to open its configuration panel.\n",
    "\n",
    "### Set Up the Environment Variables\n",
    "\n",
    "1. **Find the Environment Variables Section**: Within the service configuration, look for a section called \"Environment Variables\" or something similar.\n",
    "\n",
    "2. **Add New Environment Variables**:\n",
    "   - Click the button to add a new environment variable.\n",
    "   - Enter the name and value for each required variable.\n",
    "   - For example, if your FastAPI application uses environment variables for database connection, you will need to add variables such as `DATABASE_URL`, `DATABASE_USER`, `DATABASE_PASSWORD`, etc., with the corresponding values you obtained when setting up your PostgreSQL database in Render.\n",
    "\n",
    "### Common Examples of Environment Variables\n",
    "\n",
    "- **`DATABASE_URL`**: The full URL to connect to your PostgreSQL database.\n",
    "- **`DATABASE_USER` and `DATABASE_PASSWORD`**: Username and password for the database.\n",
    "- **Application Configuration Variables**: Any other variables your application needs for its configuration, such as secret keys, operation modes, etc.\n",
    "\n",
    "### Save and Apply Changes\n",
    "\n",
    "- After adding all your environment variables, make sure to save the changes.\n",
    "- Render might automatically restart your service to apply these changes. If not, you can manually restart the service to ensure the new environment variables are in use.\n",
    "\n",
    "### Verify Everything Works\n",
    "\n",
    "- Once your service restarts with the new variables, verify that your application is running correctly and can connect to the database using the configured environment variables.\n",
    "\n",
    "Properly configuring environment variables is crucial for the security and correct functioning of your application in production. These variables allow your application to access important resources such as databases and external APIs, maintaining the sensitivity and configurability of these details."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db072e29-18c4-4b82-975e-19efa2053931",
   "metadata": {},
   "source": [
    "## Create the todos table in the postgres database"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f42cc16-a43c-4720-86dc-6ce2cb258509",
   "metadata": {},
   "source": [
    "Edit the remote postgress database hosted in Render.com from your terminal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9d5452e4-3ee0-4d68-aceb-58f291f8499a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#psql postgresql://user:password@host:port/databasename"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "faa6f45f-f4a5-4061-b50b-a3f01ef784ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "# CREATE TABLE todos (\n",
    "#     id BIGSERIAL PRIMARY KEY,\n",
    "#     name TEXT,\n",
    "#     completed BOOLEAN NOT NULL DEFAULT false\n",
    "# );"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5504806-6b2c-4501-92d6-eced90d417a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "#\\dt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3693d637-ed24-4ddf-8c9d-4ac92777e8c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "#\\q"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8aa14d6-dc48-414a-8c2c-85f1771acf71",
   "metadata": {},
   "source": [
    "## How to verify the backend is running correctly on Render.com"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43b2ee97-75e7-4840-baf2-bfd32df75b66",
   "metadata": {},
   "source": [
    "To verify that your application is running correctly and can connect to the database using the configured environment variables, you can follow these steps:\n",
    "\n",
    "### 1. Check the Application Logs\n",
    "\n",
    "After deploying your application and setting up the environment variables, the first thing to do is check the application's logs:\n",
    "\n",
    "- On Render.com, go to the dashboard of your web service.\n",
    "- Look for a logs or records section. This will show you the output of your application, including startup messages and errors.\n",
    "- Check these logs for errors or messages related to the database connection. If your application cannot connect to the database, you will likely see errors here.\n",
    "\n",
    "### 2. Perform Connectivity Tests\n",
    "\n",
    "If your FastAPI application exposes endpoints that perform read/write operations on the database, you can test these endpoints to ensure that the database connection is working properly:\n",
    "\n",
    "- Use a tool like Postman or simply a browser to make requests to your API endpoints that require access to the database.\n",
    "- Observe if the operations are completed successfully (for example, reading data, creating new records, updating or deleting existing records).\n",
    "\n",
    "### 3. Verify Application Behavior\n",
    "\n",
    "If your application has a user interface (frontend):\n",
    "\n",
    "- Interact with the application as a normal user would.\n",
    "- Perform actions that you know depend on the database and observe if they behave as expected.\n",
    "\n",
    "### 4. Check Security Configurations\n",
    "\n",
    "- Make sure that your database's security configurations allow connections from your application deployed on Render. This may include setting up allowed IPs or adjusting firewall rules.\n",
    "\n",
    "### 5. Use Diagnostic Tools\n",
    "\n",
    "- If you have access to database diagnostic or monitoring tools (like those provided by Render's database service or external tools), use them to verify if there are active connections and if queries are being executed.\n",
    "\n",
    "### 6. Consult Documentation and Support\n",
    "\n",
    "- If you encounter problems, consult the documentation of Render and FastAPI to see if there are specific configuration or troubleshooting steps you might have overlooked.\n",
    "- If problems persist, consider seeking help in community forums or Render's technical support.\n",
    "\n",
    "### 7. Verify Environment Variables\n",
    "\n",
    "- Make sure the environment variables are correctly configured in Render and that your application is using them as expected.\n",
    "\n",
    "By following these steps, you should get a good idea of whether your application is running correctly and if it's connecting to the database as expected."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0fddd63-1a07-412a-98b7-d9be93b27311",
   "metadata": {},
   "source": [
    "## Part 7: Load the frontend to Github"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc8b7b32-47b9-4f79-9cd1-244beb17e8d6",
   "metadata": {},
   "source": [
    "## Part 8: Deploy the frontend to Vercel"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3159215b-58c4-432a-8875-6cedce0cd391",
   "metadata": {},
   "source": [
    "Uploading your frontend to Vercel after uploading it to GitHub is a fairly straightforward process. Vercel integrates seamlessly with GitHub, making the deployment process easy. Here's how to do it step by step:\n",
    "\n",
    "### Step 1: Prepare Your GitHub Repository\n",
    "\n",
    "Before you start, make sure your frontend project is up to date on GitHub. This includes all necessary files to run your application, such as `package.json`, source code files, etc.\n",
    "\n",
    "### Step 2: Create a Vercel Account (if you don't have one yet)\n",
    "\n",
    "If you don't have a Vercel account yet, go to [Vercel.com](https://vercel.com/) and sign up. You can do this using your GitHub account, which facilitates integration.\n",
    "\n",
    "### Step 3: Connect Your GitHub Repository with Vercel\n",
    "\n",
    "1. **Log in to Vercel**: Log into your Vercel account.\n",
    "\n",
    "2. **Import Your Project**:\n",
    "   - In your Vercel dashboard, look for an option to \"Import Project\" or \"New Project\".\n",
    "   - Select \"Import from GitHub\". Vercel will ask for permission to access your GitHub repositories if it's your first time doing this.\n",
    "   - Choose the GitHub repository that contains your frontend project.\n",
    "\n",
    "### Step 4: Configure Your Project in Vercel\n",
    "\n",
    "Once you have selected your repository:\n",
    "\n",
    "1. **Configure Project Options**:\n",
    "   - Vercel will automatically detect that it's a frontend project (such as a React, Vue, Next.js project, etc.) and will suggest configurations.\n",
    "   - Configure the build and deployment options as necessary. This may include build commands, output directory, and environment variables.\n",
    "\n",
    "2. **Set Up Environment Variables** (if necessary):\n",
    "   - If your application requires environment variables (like API keys or URLs), add them in the project configuration on Vercel.\n",
    "\n",
    "### Step 5: Deploy Your Project\n",
    "\n",
    "- After configuring your project, click on \"Deploy\". Vercel will start the deployment process automatically.\n",
    "- You can follow the progress of the deployment in the Vercel dashboard. Once the deployment is complete, you will receive a URL where your application will be available.\n",
    "\n",
    "### Step 6: Future Updates\n",
    "\n",
    "- For future updates, simply push your changes to the GitHub repository. If you have continuous integrations enabled in Vercel, each push to the selected branch (such as `main` or `master`) will automatically initiate a new deployment.\n",
    "\n",
    "### Step 7: Verify Your Application\n",
    "\n",
    "- Once your application is deployed, visit the URL provided by Vercel to ensure everything is working as expected.\n",
    "\n",
    "And with that, your frontend should be live on Vercel, accessible via a public URL, and automatically updating with each change you push to your GitHub repository."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b9f1a90-b8c1-461c-b810-237727e3b023",
   "metadata": {},
   "source": [
    "## Enter the environment variables in Vercel: NEXT_PUBLIC_API_URL"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "857f8603-fb71-4c6b-927c-2e56b3708330",
   "metadata": {},
   "source": [
    "To ensure that your frontend deployed on Vercel connects with your backend hosted on Render.com, you need to update the `NEXT_PUBLIC_API_URL` environment variable to point to the URL of your backend service on Render.com. Here's how you can do it:\n",
    "\n",
    "### Find Your Backend URL on Render\n",
    "\n",
    "1. **Log in to Render**: Go to [Render.com](https://render.com/) and log into your account.\n",
    "2. **Locate Your Backend Service**: Look for the service you have set up for your FastAPI backend.\n",
    "3. **Copy the Service URL**: Render assigns a URL to each deployed service. Find this URL in the dashboard of your service on Render. Typically, it will be something like `https://your-backend.onrender.com`.\n",
    "\n",
    "### Update the Environment Variable in Vercel\n",
    "\n",
    "1. **Log in to Vercel**: Go to [Vercel.com](https://vercel.com/) and log into your account.\n",
    "2. **Navigate to Your Frontend Project**: Search for and select the frontend project where you need to update the environment variable.\n",
    "3. **Access Project Settings**: Look for a configuration or settings section of the project.\n",
    "4. **Edit the Environment Variables**:\n",
    "   - Find the `NEXT_PUBLIC_API_URL` variable and change its value to the URL of your backend service on Render, for example, `https://your-backend.onrender.com`.\n",
    "   - If the variable does not exist, add it with the name `NEXT_PUBLIC_API_URL` and the corresponding value for the URL of the Render service.\n",
    "\n",
    "### Considerations\n",
    "\n",
    "- **Recompilation**: When changing environment variables in Next.js projects, you may need to recompile your application for the changes to take effect.\n",
    "- **Public Variables in Next.js**: In Next.js, environment variables exposed to the browser must start with `NEXT_PUBLIC_`. Ensure this convention is maintained so your frontend application can access them.\n",
    "\n",
    "### Frontend Deployment\n",
    "\n",
    "- Once you have updated the environment variable in Vercel, your frontend application will automatically recompile and deploy with the new configuration.\n",
    "- Verify that your frontend is correctly connecting to the backend after deployment.\n",
    "\n",
    "By following these steps, your frontend on Vercel will be configured to communicate with your backend hosted on Render.com, allowing you to effectively handle requests between the frontend and backend."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0688aa4-5591-4c8b-902f-8fa74f339aab",
   "metadata": {},
   "source": [
    "## Update the CORS configuration in the backend\n",
    "In main.py, line 20:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49a2d940-f2d9-4594-95de-71804296dfc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# origins = [\n",
    "#     \"http://localhost:3000\",\n",
    "#     \"https://yourvercelname.vercel.app/\",\n",
    "# ]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8050e073-2ff5-4ecc-9ee3-6321f1480f82",
   "metadata": {},
   "source": [
    "Or you can instead do (this is not recommended for a real-world project):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6f0cce6a-a616-41f9-8262-1ee6399cdb86",
   "metadata": {},
   "outputs": [],
   "source": [
    "# origins = [\"*\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0eef441c-2c5f-4f01-95db-e5bb34012785",
   "metadata": {},
   "source": [
    "So the CORS configuration (line 25) will be:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "088164a1-35c9-40fd-a3d5-5e1cacac82c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# CORS configuration, needed for frontend development\n",
    "# app.add_middleware(\n",
    "#     CORSMiddleware,\n",
    "#     allow_origins=[\"*\"],\n",
    "#     allow_credentials=True,\n",
    "#     allow_methods=[\"*\"],\n",
    "#     allow_headers=[\"*\"],\n",
    "# )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5fbd113-1513-440a-a13c-399dd6b1c357",
   "metadata": {},
   "source": [
    "## If the data does not load, try Purge Cache in Vercel\n",
    "* In Project Settings > Data Cache > Purge Everything"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e663d4e-ba6b-46d6-b0c0-67d0e17bc232",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
