-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #238 from Sahil-Connect/SAH-119
[SAH-119]: Nest.js Server
- Loading branch information
Showing
31 changed files
with
24,071 additions
and
10,706 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
name: Docker image build and publish for Server | ||
on: | ||
workflow_dispatch: | ||
inputs: | ||
path_to_dockerfile: | ||
description: Path to the dockerfile (default = 'Dockerfile') | ||
default: "infra/docker/Dockerfile.server" | ||
type: string | ||
docker_build_dir: | ||
description: Docker build directory (default = '.') | ||
default: "." | ||
type: string | ||
image_tag: | ||
description: Tag to apply to images. | ||
type: string | ||
default: sahil-server | ||
lifecycle_policy_file: | ||
description: Path to the lifecycle policy JSON file (default = 'policy.json') | ||
default: "policy.json" | ||
type: string | ||
backend_s3_bucket: | ||
description: Name of the S3bucket for Terraform backend | ||
default: "sahil-terraform-state-bucket" | ||
type: string | ||
backend_iam_role: | ||
description: Name of the Terraform backend assumable IAM Role | ||
default: "workload-assumable-role" | ||
type: string | ||
github_iam_role: | ||
description: Name of the IAM Role for adding access to ECR repo | ||
default: "github-actions-role" | ||
type: string | ||
aws_account_id: | ||
description: AWS Account ID | ||
default: "060795911441" | ||
type: string | ||
aws_region: | ||
description: Target AWS Region | ||
default: "eu-west-1" | ||
type: string | ||
backend_dynamodb_table: | ||
description: DynamoDB table for State lock | ||
default: "sahil-terraform-table-locks" | ||
type: string | ||
|
||
# concurrency required to avoid terraform lock contention during ECR provisioning | ||
concurrency: ci-${{ github.repository }}-server-docker-pipeline | ||
|
||
jobs: | ||
docker: | ||
runs-on: ubuntu-latest | ||
|
||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
outputs: | ||
image_tag: ${{ steps.build-publish.outputs.image_tag }} | ||
version_tag: ${{ steps.build-publish.outputs.version_tag }} | ||
full_image: ${{ steps.build-publish.outputs.full_image }} | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@v2 | ||
with: | ||
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/sahil-deployment-role | ||
aws-region: ${{ inputs.aws_region }} | ||
|
||
- name: Setup Terraform | ||
uses: hashicorp/setup-terraform@v2 | ||
with: | ||
terraform_wrapper: false | ||
|
||
- name: prepare ECR repo name based on the Github repository | ||
shell: bash | ||
run: | | ||
set -eux | ||
# lowercase the name | ||
repo="${GITHUB_REPOSITORY,,}" | ||
# replace / with _ | ||
echo "ECR_REPO_NAME=${repo//\//_}" >> $GITHUB_ENV | ||
- name: TF init | ||
shell: bash | ||
run: | | ||
set -eux | ||
terraform init -upgrade -reconfigure \ | ||
-backend-config='skip_metadata_api_check=true' \ | ||
-backend-config='skip_region_validation=true' \ | ||
-backend-config='skip_credentials_validation=true' \ | ||
-backend-config='region=${{ inputs.aws_region }}' \ | ||
-backend-config='bucket=${{ inputs.backend_s3_bucket }}' \ | ||
-backend-config='key=docker-ecr/terraform-${{ env.ECR_REPO_NAME }}.tfstate' \ | ||
-backend-config='dynamodb_table=${{ inputs.backend_dynamodb_table }}' \ | ||
-backend-config='assume_role={ role_arn = "arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.backend_iam_role }}" }' | ||
working-directory: infra/terraform | ||
|
||
- name: Create ECR repo [TF apply] | ||
shell: bash | ||
run: | | ||
set -eux | ||
terraform apply \ | ||
-var 'repository_name=${{ env.ECR_REPO_NAME }}' \ | ||
-var 'lifecycle_policy=${{ inputs.lifecycle_policy_file }}' \ | ||
-var 'iam_role=arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.github_iam_role }}' \ | ||
-var 'aws_account_id=${{ inputs.aws_account_id }}' \ | ||
-auto-approve | ||
working-directory: infra/terraform | ||
|
||
- name: Login to Amazon ECR | ||
id: login-ecr | ||
uses: aws-actions/amazon-ecr-login@v2 | ||
with: | ||
registries: ${{ inputs.aws_account_id }} | ||
|
||
- name: Build, tag, and push image to Amazon ECR | ||
id: build-publish | ||
shell: bash | ||
env: | ||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} | ||
ECR_REPOSITORY: ${{ env.ECR_REPO_NAME }} | ||
IMAGE_TAG: ${{ inputs.image_tag }} | ||
run: | | ||
# Get the current latest image digest (if it exists) | ||
PREVIOUS_IMAGE_MANIFEST=$(aws ecr batch-get-image \ | ||
--repository-name $ECR_REPOSITORY \ | ||
--image-ids imageTag=$IMAGE_TAG \ | ||
--output text \ | ||
--query 'images[].imageManifest' || echo "") | ||
# Build the new image | ||
docker build "${{ inputs.docker_build_dir }}" -f "${{ inputs.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" | ||
# Push the new image as latest | ||
docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" | ||
# If there was a previous image, tag it as v1, v2, etc. | ||
if [ ! -z "$PREVIOUS_IMAGE_MANIFEST" ]; then | ||
# Get the current highest version number | ||
CURRENT_VERSION=$(aws ecr describe-images \ | ||
--repository-name $ECR_REPOSITORY \ | ||
--query 'imageDetails[].imageTags[?starts_with(@, `v`)]' \ | ||
--output text | grep -o 'v[0-9]*' | sed 's/v//' | sort -n | tail -1) | ||
# Calculate next version number | ||
if [ -z "$CURRENT_VERSION" ]; then | ||
NEW_VERSION="v1" | ||
else | ||
NEW_VERSION="v$((CURRENT_VERSION + 1))" | ||
fi | ||
# Tag the previous image with the new version | ||
aws ecr put-image \ | ||
--repository-name $ECR_REPOSITORY \ | ||
--image-tag $NEW_VERSION \ | ||
--image-manifest "$PREVIOUS_IMAGE_MANIFEST" | ||
echo "Previous image tagged as $NEW_VERSION" | ||
echo "version_tag=$NEW_VERSION" >> $GITHUB_OUTPUT | ||
fi | ||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT | ||
echo "full_image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { useState } from 'react' | ||
import { Button, Modal, Drawer } from 'ui' | ||
import { HiOutlineShoppingCart, HiOutlineSparkles } from 'react-icons/hi2' | ||
|
||
interface Item { | ||
id: string | ||
name: string | ||
quantity: number | ||
unit: string | ||
price: number | ||
} | ||
|
||
interface Supplier { | ||
name: string | ||
rating: number | ||
totalOrders: number | ||
onTimeDelivery: number | ||
} | ||
|
||
interface PromptOrderSuggestionProps { | ||
suggestion: { | ||
items: Item[] | ||
supplier: Supplier | ||
deliveryDate: string | ||
totalCost: number | ||
} | ||
onCompare: () => void | ||
} | ||
|
||
// Mock suppliers data | ||
const mockSuppliers = [ | ||
{ | ||
id: 1, | ||
name: "Global Foods Ltd", | ||
rating: 4.8, | ||
totalOrders: 1500, | ||
onTimeDelivery: 98, | ||
price: 42.50, | ||
deliveryDate: "2025-01-10", | ||
minOrderQuantity: 25 | ||
}, | ||
{ | ||
id: 2, | ||
name: "Fresh Direct", | ||
rating: 4.6, | ||
totalOrders: 1200, | ||
onTimeDelivery: 95, | ||
price: 40.00, | ||
deliveryDate: "2025-01-12", | ||
minOrderQuantity: 20 | ||
}, | ||
{ | ||
id: 3, | ||
name: "Premium Suppliers", | ||
rating: 4.9, | ||
totalOrders: 2000, | ||
onTimeDelivery: 99, | ||
price: 45.00, | ||
deliveryDate: "2025-01-09", | ||
minOrderQuantity: 30 | ||
} | ||
] | ||
|
||
interface SupplierCompareDrawerProps { | ||
currentSupplier: Supplier | ||
} | ||
|
||
function SupplierCompareDrawer({ currentSupplier }: SupplierCompareDrawerProps) { | ||
return ( | ||
<Drawer id="supplier-compare" CTA="Compare" className="justify-between space-y-4"> | ||
<div className="space-y-1 mb-6"> | ||
<h3 className="font-bold text-xl">Compare Suppliers</h3> | ||
<p className="opacity-80"> | ||
Compare different suppliers and choose the best option for your order. | ||
</p> | ||
</div> | ||
|
||
<div className="grid gap-4"> | ||
{mockSuppliers.map((supplier) => ( | ||
<div | ||
key={supplier.id} | ||
className="card card-bordered p-4 relative hover:shadow-md transition-shadow" | ||
> | ||
<div className="flex justify-between items-start"> | ||
<div> | ||
<h3 className="font-medium text-lg">{supplier.name}</h3> | ||
<div className="mt-2 space-y-1 text-sm text-gray-600"> | ||
<p className="flex items-center"> | ||
<span className="mr-1">⭐</span> | ||
{supplier.rating} ({supplier.totalOrders} orders) | ||
</p> | ||
<p className="flex items-center"> | ||
<HiOutlineSparkles className="mr-1 h-4 w-4 text-green-500" /> | ||
{supplier.onTimeDelivery}% On-time delivery | ||
</p> | ||
</div> | ||
</div> | ||
<div className="text-right"> | ||
<p className="text-lg font-semibold">${supplier.price}</p> | ||
<p className="text-sm text-gray-500">Min. order: {supplier.minOrderQuantity} units</p> | ||
</div> | ||
</div> | ||
<div className="mt-4"> | ||
<p className="text-sm text-gray-600">Delivery by: {supplier.deliveryDate}</p> | ||
</div> | ||
{supplier.name === currentSupplier.name && ( | ||
<div className="absolute top-2 right-2"> | ||
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full"> | ||
Current Supplier | ||
</span> | ||
</div> | ||
)} | ||
<div className="mt-4"> | ||
<Button className="w-full" variant={supplier.name === currentSupplier.name ? "outline" : "primary"}> | ||
{supplier.name === currentSupplier.name ? 'Current Selection' : 'Select Supplier'} | ||
</Button> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
</Drawer> | ||
) | ||
} | ||
|
||
export function PromptOrderSuggestion({ suggestion, onCompare }: PromptOrderSuggestionProps) { | ||
const [isEditModalOpen, setIsEditModalOpen] = useState(false) | ||
const [editedItem, setEditedItem] = useState<Item | null>(null) | ||
|
||
const handleEditItem = (item: Item) => { | ||
setEditedItem(item) | ||
setIsEditModalOpen(true) | ||
} | ||
|
||
const handleSaveEdit = () => { | ||
console.log('Saving edited item:', editedItem) | ||
setIsEditModalOpen(false) | ||
} | ||
|
||
return ( | ||
<div className="p-6"> | ||
<h3 className="text-xl font-semibold mb-6">Suggested Order</h3> | ||
<p className="text-gray-500 text-sm mb-4">Based on your request, here is what we suggest:</p> | ||
|
||
<div className="bg-gray-50 rounded-lg p-4 mb-6"> | ||
<h4 className="text-lg font-medium mb-2">Supplier Information</h4> | ||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> | ||
<div> | ||
<p className="text-sm text-gray-500">Supplier</p> | ||
<p className="font-medium">{suggestion.supplier.name}</p> | ||
</div> | ||
<div> | ||
<p className="text-sm text-gray-500">Rating</p> | ||
<p className="font-medium"> {suggestion.supplier.rating}</p> | ||
</div> | ||
<div> | ||
<p className="text-sm text-gray-500">Total Orders</p> | ||
<p className="font-medium">{suggestion.supplier.totalOrders}</p> | ||
</div> | ||
<div> | ||
<p className="text-sm text-gray-500">On-Time Delivery</p> | ||
<p className="font-medium">{suggestion.supplier.onTimeDelivery}%</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div className="mb-4"> | ||
<p className="text-sm text-gray-500 mb-2">Delivery Date</p> | ||
<p className="font-medium">{suggestion.deliveryDate}</p> | ||
</div> | ||
|
||
<table className="w-full mb-6"> | ||
<thead className="bg-gray-50"> | ||
<tr> | ||
<th className="text-left p-2">Item</th> | ||
<th className="text-left p-2">Quantity</th> | ||
<th className="text-right p-2">Price</th> | ||
<th className="text-right p-2">Actions</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{suggestion.items.map((item) => ( | ||
<tr key={item.id} className="border-b"> | ||
<td className="p-2">{item.name}</td> | ||
<td className="p-2">{item.quantity} {item.unit}</td> | ||
<td className="text-right p-2">${item.price.toFixed(2)}</td> | ||
<td className="text-right p-2"> | ||
<Button | ||
variant="outline" | ||
size="sm" | ||
onClick={() => handleEditItem(item)} | ||
> | ||
Edit | ||
</Button> | ||
</td> | ||
</tr> | ||
))} | ||
<tr className="font-medium"> | ||
<td colSpan={2} className="p-2 text-right">Total:</td> | ||
<td className="p-2 text-right">${suggestion.totalCost.toFixed(2)}</td> | ||
<td></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
<div className="flex justify-between"> | ||
<SupplierCompareDrawer currentSupplier={suggestion.supplier} /> | ||
<Button> | ||
<HiOutlineShoppingCart className="h-4 w-4 mr-2" /> | ||
Place Order | ||
</Button> | ||
</div> | ||
|
||
|
||
</div> | ||
) | ||
} |
Oops, something went wrong.