0% found this document useful (0 votes)
15 views73 pages

LabFile PixelPalette (21bcs092)

Uploaded by

batman.snaps
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views73 pages

LabFile PixelPalette (21bcs092)

Uploaded by

batman.snaps
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 73

SHRI MATA VAISHNO DEVI UNIVERSITY

KATRA
JAMMU & KASHMIR – 182 320

(School of Computer Science & Engineering)


Session 2023-24

SOFTWARE ENGINEERING LAB


(CSP 3091)
LAB REPORT FILE
by
Swastik Sharma
21BCS092
6th SEM

Submitted to: Mr. Anuj Mahajan

0
INDEX
S No. Title Page No
1. Multiple Test cases generation for single function 3
and for multiple functions in a program/ file.
3. JUNIT - Testing peer's code and maintaining Test 5
Report/ log
4. JUNIT - Using other annotations and functions 7
available in JUNIT
5. Design Patterns 8
a) Singleton (Creational)
b) Decorator (Structural)
c) Behavioural (Observer)
d) Fly Weight (Structural)
6. Use Case Diagram for Pixel Palette 20
7. SRS Document 23
8. DFDs 33
9. Sequence Diagram for Pixel Palette 34
10. Activity Diagram for Pixel Palette 35
11. Function Oriented Design for Pixel Palette 36
12. Documented Code of Pixel Palette 38
13. Test Case & Test Plan 72

1
1. JUNIT - Multiple Test cases generation for single function and for multiple functions in a
program/ file.

2
3
3. JUNT - Testing peer's code and maintaining Test Report/ log

4
5
4. JUNIT - Using other annotations and functions available in JUNIT

6
5. Design Patterns

//1.FACTORY DESIGN PATTERN

// Product Interface
interface Product {
void display();
}

// Concrete Products
class ConcreteProductA implements Product {
@Override
public void display() {
System.out.println("This is Concrete Product A.");
}
}

class ConcreteProductB implements Product {


@Override
public void display() {
System.out.println("This is Concrete Product B.");
}
}

// Factory Interface
interface Factory {
Product factoryMethod();
}

// Concrete Factories
class ConcreteFactoryA implements Factory {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}

class ConcreteFactoryB implements Factory {


7
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}

// Client Code
public class FactoryMethodExample {
public static void main(String[] args) {
Factory factoryA = new ConcreteFactoryA();
Product productA = factoryA.factoryMethod();
productA.display();

Factory factoryB = new ConcreteFactoryB();


Product productB = factoryB.factoryMethod();
productB.display();
}
}
==================================================================

//2.BUILDER DESIGN PATTERN

// Server Side Code


final class Student {

// final instance fields


private final int id;
private final String name;
private final String address;

public Student(Builder builder)


{
this.id = builder.id;
this.name = builder.name;
this.address = builder.address;
}

// Static class Builder


public static class Builder {
8
/// instance fields
private int id;
private String name;
private String address;

public static Builder newInstance()


{
return new Builder();
}

private Builder() {}

// Setter methods
public Builder setId(int id)
{
this.id = id;
return this;
}
public Builder setName(String name)
{
this.name = name;
return this;
}
public Builder setAddress(String address)
{
this.address = address;
return this;
}

// build method to deal with outer class


// to return outer instance
public Student build()
{
return new Student(this);
}
}

@Override
9
public String toString()
{
return "id = " + this.id + ", name = " + this.name +
", address = " + this.address;
}
}

// Client Side Code


class StudentReceiver {

// volatile student instance to ensure visibility


// of shared reference to immutable objects
private volatile Student student;

public StudentReceiver()
{

Thread t1 = new Thread(new Runnable() {


@Override
public void run()
{
student = Student.Builder.newInstance()
.setId(1)
.setName("Ram")
.setAddress("Noida")
.build();
}
});

Thread t2 = new Thread(new Runnable() {


@Override
public void run()
{
student = Student.Builder.newInstance()
.setId(2)
.setName("Shyam")
.setAddress("Delhi")
.build();
}
10
});

t1.start();
t2.start();
}

public Student getStudent()


{
return student;
}
}

// Driver class
public class BuilderDemo {
public static void main(String args[])
{
StudentReceiver sr = new StudentReceiver();
System.out.println(sr.getStudent());
}
}

==================================================================

// 3. PROTOTYPE DESIGN PATTERN


interface Shape {
Shape clone(); // Make a copy of itself
void draw(); // Draw the shape
}

// Concrete prototype
class Circle implements Shape {
private String color;

// When you create a circle, you give it a color.


public Circle(String color) {
this.color = color;
11
}

// This creates a copy of the circle.


@Override
public Shape clone() {
return new Circle(this.color);
}

// This is how a circle draws itself.


@Override
public void draw() {
System.out.println("Drawing a " + color + " circle.");
}
}

// Client code
class ShapeClient {
private Shape shapePrototype;

// When you create a client, you give it a prototype (a


shape).
public ShapeClient(Shape shapePrototype) {
this.shapePrototype = shapePrototype;
}

// This method creates a new shape using the prototype.


public Shape createShape() {
return shapePrototype.clone();
}
}

// Main class
public class PrototypeExample {
public static void main(String[] args) {
// Create a concrete prototype (a red circle).
Shape circlePrototype = new Circle("red");

// Create a client and give it the prototype.


ShapeClient client = new ShapeClient(circlePrototype);
12
// Use the prototype to create a new shape (a red
circle).
Shape redCircle = client.createShape();

// Draw the newly created red circle.


redCircle.draw();
}
}

OUTPUT
Drawing a red circle.

==================================================================

// 4.SINGLETON DESIGN PATTERN

// Helper class
class Singleton {
// Static variable reference of single_instance
// of type Singleton
private static Singleton single_instance = null;

// Declaring a variable of type String


public String s;

// Constructor
// Here we will be creating private constructor
// restricted to this class itself
private Singleton()
{
s = "Hello I am a string part of Singleton
class";
}

// Static method
// Static method to create instance of Singleton class

13
public static synchronized Singleton getInstance()
{
if (single_instance == null)
single_instance = new Singleton();

return single_instance;
}
}

// Class 2
// Main class
class GFG {
// Main driver method
public static void main(String args[])
{
// Instantiating Singleton class with variable x
Singleton x = Singleton.getInstance();

// Instantiating Singleton class with variable y


Singleton y = Singleton.getInstance();

// Instantiating Singleton class with variable z


Singleton z = Singleton.getInstance();

// Printing the hash code for above variable as


// declared
System.out.println("Hashcode of x is "
+ x.hashCode());
System.out.println("Hashcode of y is "
+ y.hashCode());
System.out.println("Hashcode of z is "
+ z.hashCode());

// Condition check
if (x == y && y == z) {

// Print statement
System.out.println(

14
"Three objects point to the same memory
location on the heap i.e, to the same object");
}

else {
// Print statement
System.out.println(
"Three objects DO NOT point to the same
memory location on the heap");
}
}
}
OUTPUT

==================================================================

//5.DECORATOR DESIGN PATTERN

// Interface named Shape


public interface Shape {

// Method inside interface


void draw();
}

// Class 1
// Class 1 will be implementing the Shape interface

// Rectangle.java
public class Rectangle implements Shape {

// Overriding the method


@Override public void draw()
15
{
// /Print statement to execute when
// draw() method of this class is called
// later on in the main() method
System.out.println("Shape: Rectangle");
}
}

// Class 2
// Abstract class
// ShapeDecorator.java
public abstract class ShapeDecorator implements Shape {

// Protected variable
protected Shape decoratedShape;

// Method 1
// Abstract class method
public ShapeDecorator(Shape decoratedShape)
{
// This keyword refers to current object itself
this.decoratedShape = decoratedShape;
}

// Method 2 - draw()
// Outside abstract class
public void draw() { decoratedShape.draw(); }
}

// Class 3
// Concrete class extending the abstract class
// RedShapeDecorator.java
public class RedShapeDecorator extends ShapeDecorator {

public RedShapeDecorator(Shape decoratedShape)


{
super(decoratedShape);
16
}

@Override public void draw()


{
decoratedShape.draw();
setRedBorder(decoratedShape);
}

private void setRedBorder(Shape decoratedShape)


{
// Display message whenever function is called
System.out.println("Border Color: Red");
}
}

// Main class
public class DecoratorPatternDemo {

// Main driver method


public static void main(String[] args)
{
// Creating an object of Shape interface
// inside the main() method
Shape circle = new Circle();

Shape redCircle
= new RedShapeDecorator(new Circle());

Shape redRectangle
= new RedShapeDecorator(new Rectangle());

// Display message
System.out.println("Circle with normal border");

// Calling the draw method over the


// object calls as created in
// above classes
17
// Call 1
circle.draw();

// Display message
System.out.println("\nCircle of red border");

// Call 2
redCircle.draw();

// Display message
System.out.println("\nRectangle of red border");

// Call 3
redRectangle.draw();
}
}

OUTPUT

6. Use Case Diagrams


18
19
20
7. SRS Document

Software Requirements
Specification

for

PIXEL PALETTE
Version 1.0 approved

Prepared by

Arnav Raina, 21bcs019

Swastik Sharma, 21bcs092

Utkersh Uppal,21bcs098

13-03-2024

21
Contents
1. INTRODUCTION 3
1.1 Document Purpose 3
1.2 Product Scope 3
1.3 Definitions, Acronyms And Abbrevations 3
1.4 Refrences And Refrences 3
1.5 Overview 3
2. OVERALL DESCRIPTION 4
2.1 Product Perspective 4
2.2 Product Functionalities 4
2.3 User Characterstics 4
2.4 General Constraints 5
2.5 Assumptions And Dependencies 5
3.SPECIFIC REQUIREMENTS 6
3.1. External Interface Requirements 6
3.1.1 User Interfaces 6
3.1.2 Hardware Interfaces 6
3.1.3 Software Interfaces 6
3.2 Functional Requirements 6
3.2.1 Authentication 6
3.2.2 Image Upload 6
3.3 Use Case Model 6
3.3.1 Use Case 1: User Authentication 6
3.3.2 Use Case 2: Community Image Showcase 7
3.3.3 Use Case 3: Advanced Image Search 7
3.3.4 Use Case 4: Image Processing functionalities 8
3.3.5 Use Case 5: Payment Integration 8
3.4 Performance Requirements 9
3.5 Design constraints 9
3.5 Other Requirements 10
4. APPENDICES 12

22
1. INTRODUCTION
1.1 DOCUMENT PURPOSE
The purpose of this Software Requirements Specification (SRS) document is to provide a
detailed description of the requirements for the development of an AI image SaaS platform. This
platform will offer advanced image processing capabilities, secure payment infrastructure,
advanced image search functionalities, and support for various AI features.

1.2 PRODUCT SCOPE


The software will be developed as a web application using modern web technologies. It will
include features such as authentication, image processing functionalities, payment integration,
and user management.

1.3 DEFINITIONS, ACRONYMS AND ABBREVIATIONS


● SaaS: Software as a Service
● AI: Artificial Intelligence
● UI: User Interface
● UX: User Experience
● CRUD: Create, Read, Update, Delete

1.4 REFERENCES AND ACKNOWLEDGMENTS


● TypeScript Documentation
● MongoDB Documentation
● Clerk Documentation
● Cloudinary Documentation
● Stripe Documentation
● Shadcn Documentation

1.5 OVERVIEW
The AI Image SaaS platform is an innovative solution designed to meet the diverse image
processing needs of users ranging from hobbyists and professionals to businesses and
researchers. Leveraging cutting-edge technologies such as Next.js, TypeScript, MongoDB,
Clerk, Cloudinary, Stripe, Shadcn, and TailwindCSS, the platform offers a comprehensive suite
of features for image transformation, customization, and enhancement.

23
2. OVERALL DESCRIPTION
2.1 PRODUCT PERSPECTIVE
The AI image SaaS platform(pixel palette) will serve as a standalone web application, offering
various image processing functionalities and payment integration. It will interact with external
services such as Stripe for payment processing and Cloudinary for image storage and
processing.:
● Home Page: Allows anyone to browse and view images without requiring login.
● Upload Page: Enables users to upload images.
● User Sign-In Page: Provides login functionality for users .
● Signup Page: Allows new users to register.

2.2 PRODUCT FUNCTIONALITIES


The platform will include the following features:
● Authentication and Authorization
● Community Image Showcase
● Advanced Image Search
● Image Restoration
● Image Recolouring
● Image Generative Fill
● Object Removal
● Background Removal
● Download Transformed Images
● Transformed Image Details
● Transformation Management
● Credits System
● Profile Page
● Credits Purchase
● Responsive UI/UX

2.3 USER CHARACTERSTICS


The users of the system include:
● Regular Users: Individuals who upload images for processing and utilize the platform's
features.

2.4 GENERAL CONSTRAINTS


The platform will be built using Next.js and TypeScript, utilizing MongoDB for data storage,
Clerk for authentication, Cloudinary for image processing and storage, Stripe for payment
integration, and TailwindCSS for styling.

24
2.5 ASSUMPTIONS AND DEPENDENCIES
● Assumptions:
● Users have a basic understanding of how to navigate and interact with web
applications.
● Users have access to a stable internet connection.
● Dependencies:
● Express services are relied upon for user authentication, data storage, and
backend functionality.
● Next.js framework is used for frontend development, requiring adherence to its
conventions and best practices.

25
3.Specific Requirements
3.1. External Interface Requirements
3.1.1 User Interfaces
● Home Page: Allows anyone to view images without logging in.
● Upload Page: Enables users to upload images.
● Sign-In Page: Allows both users to log in.
● Sign-Up Page: Enables new user registration.
● Admin-page: Checks the user data stored in MongoDB

3.1.2 Hardware Interfaces


The application runs on standard web browsers and does not require any specific hardware
interfaces.

3.1.3 Software Interfaces


● Next.js: Frontend and backend framework for building the web application.
● Express and MongoDB: Database and storage of the user data

3.2 Functional Requirements


3.2.1 Authentication
● Users must sign in to access their respective functionalities.
● New users can register through the sign-up page.

3.2.2 Image Upload


● Users can upload photos to edit through the upload functionality.

3.3 Use Case Model


3.3.1 Use Case 1: User Authentication
● Actor: User
● Description: Authenticate user credentials to access the application functionalities.
● Preconditions: User must have registered credentials.

● Steps:
1. User navigates to the sign-in page.

26
2. User enters username and password.
3. System verifies the credentials.
4. If authentication is successful, user is redirected to the respective dashboard.
● Exception Scenarios:
● If the user enters an incorrect username or password:
● The system displays an error message indicating invalid credentials.
● User is prompted to retry authentication.

3.3.2 Use Case 2: Community Image Showcase


● Actor: User
● Description: Users can explore transformations made by other through a showcase with
easy navigation
● Preconditions: For viewing the details, the user must be signed in.
● Steps:
● User navigates to the community image showcase page.
● User browses through the showcased images using pagination.
● User clicks on an image to view its details.

● Exception Scenarios:
● If the user is not signed in:
o The system prompts the user to sign in before accessing the community
image showcase

3.3.3 Use Case 3: Advanced Image Search


● Actor: User
● Description: Verify uploaded images for validity.
● Preconditions: User must be logged in.

● Steps:
1. User navigates to the advanced image search page.
2. User enters keywords or selects objects to search for.
3. System retrieves relevant images based on the search criteria.
4. User views the search results

● Exception Scenarios:

27
● If the search criteria yield no results:
o The system displays a message indicating no matching images were found.

3.3.4 Use Case 4: Image Processing functionalities


● Actor: User
● Description: The platform offers various image processing functionalities including
restoration, recolouring, generative fill, object removal, and background removal
● Preconditions: User must be authenticated.
● Steps:
● User uploads an image to the platform.
● User selects the desired image processing functionality.
● System processes the image according to the selected functionality.
● User reviews and approves the processed image.
● User can download the processed image.
● Exception Scenarios:
● If the uploaded image format is unsupported:
o The system displays an error message and prompts the user to upload a
supported image format.
● If the image processing fails due to technical issues:
o The system notifies the user of the failure and suggests retrying later

3.3.5 Use Case 5: Payment Integration


● Actor: User
● Description: Users can purchase credits for image transformations securely via Stripe.
● Preconditions: User must be authenticated.
● Steps:
● User navigates to the credits purchase page.
● User selects the desired credit package and clicks on the purchase button.
● System redirects the user to the Stripe payment gateway.
● User enters payment details and completes the transaction.
● System updates the user's credit balance.

● Exception Scenarios:
● If the payment transaction fails:
o The system prompts the user to retry the payment or try an alternative payment
method.
● If there's a communication error with the Stripe payment gateway:
28
o The system displays a message indicating the issue and advises the user to try
again later.

3.4 Performance Requirements


● The application should be responsive and provide a seamless user experience. The
system should respond to user actions within 2 seconds.
● Upload and download processes should be efficient, with minimal latency.
● Image processing should be completed within 10 seconds for standard-sized images.

3.5 Design constraints


Design constraints for the AI image SaaS platform encompass various factors that limit the
design choices and implementation approach. These constraints ensure the platform meets its
objectives while considering technical, regulatory, and usability aspects. Here are some
formulated design constraints for the project:

1. Technology Stack Compatibility: The platform must be developed using the specified
technologies (Next.js, TypeScript, MongoDB, Clerk, Cloudinary, Stripe, Shadcn, TailwindCSS) to
ensure compatibility, integration, and optimal performance.

2. Scalability: The architecture must support scalability to accommodate a growing user base
and increasing image processing demands. It should be capable of handling concurrent user
requests efficiently.

3. Security Compliance: The platform must adhere to industry-standard security practices and
comply with relevant regulations (e.g., GDPR) to protect user data, ensure secure
authentication, and handle payment transactions securely.

5. Data Privacy: User data, including images and personal information, must be handled with
strict confidentiality and protected against unauthorized access, manipulation, or disclosure.

6. Payment Gateway Integration: The platform must integrate seamlessly with the Stripe
payment gateway to facilitate secure credit purchases for image transformations.

7. Third-Party API Dependencies: Integration with external APIs (e.g., Cloudinary for image
storage and processing) should be reliable and robust, considering potential API rate limits,
downtime, or changes in API functionality.

29
8. User Experience (UX): The platform's user interface should be intuitive, responsive, and
optimized for usability to enhance user satisfaction and engagement.

3.5 Other Requirements


The hardware requirements for the AI Image based on SaaS platform are:
1. Server Infrastructure
● High-performance servers capable of handling concurrent user requests and image
processing tasks efficiently.
● CPU: Multi-core processors with high clock speeds to ensure fast image processing.
● RAM: Sufficient memory to handle multiple user sessions and image processing
operations simultaneously.
● Storage: SSD storage for fast data access, with ample capacity to store user-uploaded
images and processed data.
● Network: High-speed internet connection to ensure smooth data transfer between clients
and servers.

2. Database Server
● Dedicated database server with sufficient CPU, RAM, and storage resources to handle
data storage and retrieval.
● Database Management System (DBMS): MongoDB for storing user data, image
metadata, and system configurations.

4. Backup and Redundancy


● Regular data backups to prevent data loss in case of server failures or disasters.
● Redundant server setup with load balancing to distribute incoming traffic and ensure high
availability.

5. Development and Testing Environment


● Development servers for testing and debugging application code.
● Virtualization or containerization technology (e.g., Docker) for creating isolated
development environments.

6. Monitoring and Analytics


● Monitoring tools for tracking server performance, resource utilization, and application
health.
● Analytics tools for analyzing user behavior, traffic patterns, and system usage to optimize
performance and user experience.

30
8. Hardware Security
● Physical security measures for protecting server hardware from unauthorized access or
tampering.
● Network security protocols (e.g., firewalls, intrusion detection/prevention systPixel
Palette) to safeguard against cyber threats and attacks.

By meeting these hardware requirements, the AI Image SaaS platform can ensure reliable
performance, scalability, and security for its users.

31
8. Data Flow Diagrams

32
9. Sequence Diagrams

33
10. Activity Diagram

34
35
11. Function Oriented Design for Pixel Palette

36
12. Documented Code of Pixel Palette

File name: api/webhooks/clerk/route.ts

import { clerkClient } from "@clerk/nextjs"; // Import Clerk client for


interacting with Clerk user data
import { WebhookEvent } from "@clerk/nextjs/server"; // Import WebhookEvent type
from Clerk
import { headers } from "next/headers"; // Import headers function from Next.js
import { NextResponse } from "next/server"; // Import NextResponse for response
manipulation
import { Webhook } from "svix"; // Import Webhook class from Svix library

import { createUser, deleteUser, updateUser } from "@/lib/actions/user.actions";


// Import user management functions from user actions file

// **POST request handler function**


export async function POST(req: Request) {
// **Webhook secret retrieval**
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Ensure webhook secret is present in environment variables


if (!WEBHOOK_SECRET) {
throw new Error(
"Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local"
);
}

// **Get headers from request**


const headerPayload = headers();
const svix_id = headerPayload.get("svix-id");
const svix_timestamp = headerPayload.get("svix-timestamp");
const svix_signature = headerPayload.get("svix-signature");

// Check for presence of all required Svix headers


if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response("Error occured -- no svix headers", {
status: 400,
});
}

// **Get request body**


const payload = await req.json();
const body = JSON.stringify(payload);

// **Create Svix instance with secret**


const wh = new Webhook(WEBHOOK_SECRET);

37
let evt: WebhookEvent;

// **Verify webhook payload with headers**


try {
evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error("Error verifying webhook:", err);
return new Response("Error occured", {
status: 400,
});
}

// **Extract user ID and event type from verified data**


const { id } = evt.data;
const eventType = evt.type;

// **Handle user creation event**


if (eventType === "user.created") {
const {
id,
email_addresses,
image_url,
first_name,
last_name,
username,
} = evt.data;

const user = {
clerkId: id,
email: email_addresses[0].email_address,
username: username!,
firstName: first_name,
lastName: last_name,
photo: image_url,
};

const newUser = await createUser(user);

// Set public metadata for the newly created user in Clerk


if (newUser) {
await clerkClient.users.updateUserMetadata(id, {
publicMetadata: {
userId: newUser._id,
},
38
});
}

return NextResponse.json({ message: "OK", user: newUser });


}

// **Handle user update event**


if (eventType === "user.updated") {
const { id, image_url, first_name, last_name, username } = evt.data;

const user = {
firstName: first_name,
lastName: last_name,
username: username!,
photo: image_url,
};

const updatedUser = await updateUser(id, user);

return NextResponse.json({ message: "OK", user: updatedUser });


}

// **Handle user deletion event**


if (eventType === "user.deleted") {
const { id } = evt.data;

const deletedUser = await deleteUser(id!);

return NextResponse.json({ message: "OK", user: deletedUser });


}

console.log(`Webhook with and ID of ${id} and type of ${eventType}`);


console.log("Webhook body:", body);

return new Response("", { status: 200 });


}

39
File name: api/webhooks/stripe/route.ts

// Import necessary modules


import { createTransaction } from "@/lib/actions/transaction.action"; // Function
to create a transaction record
import { NextResponse } from "next/server"; // Next.js response object for
serverless functions
import stripe from "stripe"; // Stripe library for interacting with Stripe API

// Function to handle POST requests to this route


export async function POST(request: Request) {
// Get the raw body of the request (contains Stripe webhook data)
const body = await request.text();

// Extract the Stripe signature from the request headers


const sig = request.headers.get("stripe-signature") as string;

// Retrieve the Stripe webhook secret from the environment variable


const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;

// Variable to store the parsed Stripe event object


let event;

try {
// Attempt to construct a Stripe event object from the request body,
signature, and secret
event = stripe.webhooks.constructEvent(body, sig, endpointSecret);
} catch (err) {
// Handle errors during event construction
console.error("Error constructing Stripe event:", err); // Log the error for
debugging
return NextResponse.json({ message: "Webhook error", error: err }); // Return
error response
}

// Extract the event type from the parsed event object


const eventType = event.type;

// Handle different event types (currently only "checkout.session.completed" is


implemented)
if (eventType === "checkout.session.completed") {
// Extract relevant data from the event object for the completed checkout
session
const { id, amount_total, metadata } = event.data.object;

// Create a transaction object to store payment details


const transaction = {
stripeId: id,

40
amount: amount_total ? amount_total / 100 : 0, // Convert cents to dollars
(assuming amount_total is in cents)
plan: metadata?.plan || "", // Extract plan details from metadata (or set to
empty string)
credits: Number(metadata?.credits) || 0, // Extract credit details from
metadata (or set to 0)
buyerId: metadata?.buyerId || "", // Extract buyer ID from metadata (or set
to empty string)
createdAt: new Date(), // Set transaction creation date
};

// Call the createTransaction function to persist the transaction data


const newTransaction = await createTransaction(transaction);

// Return a successful response with the created transaction object


return NextResponse.json({ message: "OK", transaction: newTransaction });
}

// For other event types or if no event type matches, return a generic 200
response
return new Response("", { status: 200 });
}

41
File: Checkout.tsx

// This line is required for React components to access server-side data during
the initial render on the server
"use client";

import { loadStripe } from "@stripe/stripe-js";


import { useEffect, useState } from "react"; // Import useState for potential
future use in component state

import { useToast } from "@/components/ui/use-toast";


import { checkoutCredits } from "@/lib/actions/transaction.action";

import { Button } from "../ui/button";

interface CheckoutProps {
plan: string;
amount: number;
credits: number;
buyerId: string;
}

const Checkout = ({ plan, amount, credits, buyerId }: CheckoutProps) => {


const { toast } = useToast();

// Load Stripe.js library using the Stripe publishable key from the environment
variable
useEffect(() => {
loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
}, []);

useEffect(() => {
// Check for query parameters in the URL that indicate success or cancellation
of a Stripe checkout
const query = new URLSearchParams(window.location.search);
if (query.get("success")) {
toast({
title: "Order placed!",
description: "You will receive an email confirmation",
duration: 5000,
className: "success-toast",
});
}

if (query.get("canceled")) {
toast({
title: "Order canceled!",
description: "Continue to shop around and checkout when you're ready",

42
duration: 5000,
className: "error-toast",
});
}
}, []);

const onCheckout = async () => {


const transaction = {
plan,
amount,
credits: credits,
buyerId,
};

// Call the checkoutCredits action function to initiate the checkout process,


likely involving Stripe.js
await checkoutCredits(transaction);
};

return (
<form action={onCheckout} method="POST">
<section>
<Button
type="submit"
role="link"
className="w-full rounded-full bg-purple-gradient bg-cover"
>
Buy Credit
</Button>
</section>
</form>
);
};

export default Checkout;

43
File: Collection.tsx

// Importing necessary modules and components


"use client";
import Image from "next/image";
import Link from "next/link";
import { useSearchParams, useRouter } from "next/navigation";
import { CldImage } from "next-cloudinary";

// Importing UI components and utilities


import {
Pagination,
PaginationContent,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { transformationTypes } from "@/constants";
import { IImage } from "@/lib/database/models/image.model";
import { formUrlQuery } from "@/lib/utils";

import { Button } from "../ui/button";

// Importing the Search component


import { Search } from "./Search";

// Defining the Collection component


export const Collection = ({
hasSearch = false,
images,
totalPages = 1,
page,
}: {
images: IImage[];
totalPages?: number;
page: number;
hasSearch?: boolean;
}) => {
const router = useRouter();
const searchParams = useSearchParams();

// Function to handle pagination


const onPageChange = (action: string) => {
const pageValue = action === "next" ? Number(page) + 1 : Number(page) - 1;

// Constructing the new URL based on pagination action


const newUrl = formUrlQuery({
searchParams: searchParams.toString(),
key: "page",
value: pageValue,

44
});

// Updating the URL with the new page value


router.push(newUrl, { scroll: false });
};

return (
<>
{/* Rendering the collection heading and Search component if enabled */}
<div className="collection-heading">
<h2 className="h2-bold text-dark-600">Recent Edits</h2>
{hasSearch && <Search />}
</div>

{/* Rendering the collection list or empty message */}


{images.length > 0 ? (
<ul className="collection-list">
{images.map((image) => (
<Card image={image} key={image._id} />
))}
</ul>
) : (
<div className="collection-empty">
<p className="p-20-semibold">Empty List</p>
</div>
)}

{/* Rendering pagination if totalPages > 1 */}


{totalPages > 1 && (
<Pagination className="mt-10">
<PaginationContent className="flex w-full">
{/* Pagination Previous button */}
<Button
disabled={Number(page) <= 1}
className="collection-btn"
onClick={() => onPageChange("prev")}
>
<PaginationPrevious className="hover:bg-transparent
hover:text-white" />
</Button>

{/* Current page indicator */}


<p className="flex-center p-16-medium w-fit flex-1">
{page} / {totalPages}
</p>

{/* Pagination Next button */}


<Button
className="button w-32 bg-purple-gradient bg-cover text-white"
45
onClick={() => onPageChange("next")}
disabled={Number(page) >= totalPages}
>
<PaginationNext className="hover:bg-transparent hover:text-white" />
</Button>
</PaginationContent>
</Pagination>
)}
</>
);
};

// Card component for rendering individual image cards


const Card = ({ image }: { image: IImage }) => {
return (
<li>
{/* Link to the transformation page */}
<Link href={`/transformations/${image._id}`} className="collection-card">
{/* Image component */}
<CldImage
src={image.publicId}
alt={image.title}
width={image.width}
height={image.height}
{...image.config}
loading="lazy"
className="h-52 w-full rounded-[10px] object-cover"
sizes="(max-width: 767px) 100vw, (max-width: 1279px) 50vw, 33vw"
/>
{/* Image title and transformation icon */}
<div className="flex-between">
<p className="p-20-semibold mr-3 line-clamp-1 text-dark-600">
{image.title}
</p>
<Image
src={`/assets/icons/${
transformationTypes[
image.transformationType as TransformationTypeKey
].icon
}`}
alt={image.title}
width={24}
height={24}
/>
</div>
</Link>
</li>
);
};
46
File: MediaUploader.tsx

// Import necessary dependencies and components


import React from 'react'
import { useToast } from '../ui/use-toast'
import { CldImage, CldUploadWidget } from 'next-cloudinary'
import Image from 'next/image'
import { getImageSize, dataUrl } from "@/lib/utils"
import { PlaceholderValue } from "next/dist/shared/lib/get-img-props";

// Define type for props passed to MediaUploader component


type MediaUploaderProps = {
onValueChange: (value: string) => void, // Function to handle value change
setImage: React.Dispatch<any>; // Function to set image state
publicId: string; // Public ID of the image
image: any; // Image object
type: string; // Type of image
}

// MediaUploader functional component


const MediaUploader = ({ onValueChange, setImage, image, publicId, type }:
MediaUploaderProps) => {
// Custom hook to display toasts
const { toast } = useToast()

// Function to handle successful image upload


const onUploadSuccesHandler = (result: any) => {
// Update image state with uploaded image details
setImage((prevState: any) => ({
...prevState,
publicId: result?.info?.public_id,
width: result?.info?.width,
height: result?.info?.height,
secureURL: result?.info?.secure_url
}))

// Trigger value change callback with public ID of the uploaded image


onValueChange(result?.info?.public_id)

// Display success toast


toast({
title: "Image uploaded succesfully",
description: "1 credit was deducted from you account",
duration: 5000,
className: "success-toast"
})
}

// Function to handle upload errors

47
const onUploadErrorHandler = (result: any) => {
// Display error toast
toast({
title: "Something went wrong!!",
description: "Please try again",
duration: 5000,
className: "error-toast"
})
}

// Render MediaUploader component


return (
<CldUploadWidget uploadPreset="jsm_pixel_palette"
options={{
multiple: false,
resourceType: "image"
}}
onSuccess={onUploadSuccesHandler}
onError={onUploadErrorHandler}
>
{({ open }) => (
<div className="flex flex-col gap-4">
<h3 className="h3-bold text-dark-600">
Original
</h3>

{/* Conditionally render image or upload CTA based on whether


publicId is available */}
{publicId ? (
<>
<div className="cursor-pointer overflow-hidden
rounded-[10px]">
{/* Render Cloudinary image */}
<CldImage
width={getImageSize(type, image, "width")}
height={getImageSize(type, image, "height")}
src={publicId}
alt="image"
sizes={"(max-width: 767px) 100vw, 50vw"}
placeholder={dataUrl as PlaceholderValue}
className="media-uploader_cldImage"
/>
</div>
</>
) : (
<div className="media-uploader_cta" onClick={() =>
open()}>
<div className="media-uploader_cta-image">
{/* Render add image icon */}
48
<Image
src="/assets/icons/add.svg"
alt="Add Image"
width={24}
height={24}
/>
</div>
<p className="p-14-medium">Click here to upload
image</p>
</div>
)}
</div>
)}

</CldUploadWidget>
)
}

// Export MediaUploader component as default


export default MediaUploader

49
File: Search.tsx

"use client";

// Importing necessary modules from Next.js and React


import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

// Importing custom utility functions


import { Input } from "@/components/ui/input";
import { formUrlQuery, removeKeysFromQuery } from "@/lib/utils";

// Define the Search component


export const Search = () => {
// Get the router object to manage navigation
const router = useRouter();
// Get the current search parameters from the URL
const searchParams = useSearchParams();
// Define state to manage the search query
const [query, setQuery] = useState("");

useEffect(() => {
// Debounce functionality to reduce unnecessary URL updates
const delayDebounceFn = setTimeout(() => {
// If there's a query, add it to the URL
if (query) {
const newUrl = formUrlQuery({
searchParams: searchParams.toString(),
key: "query",
value: query,
});
// Push the updated URL to the browser's history
router.push(newUrl, { scroll: false });
} else {
// If query is empty, remove the 'query' parameter from the URL
const newUrl = removeKeysFromQuery({
searchParams: searchParams.toString(),
keysToRemove: ["query"],
});
// Push the updated URL to the browser's history
router.push(newUrl, { scroll: false });
}
}, 300);

// Cleanup function to clear the timeout


return () => clearTimeout(delayDebounceFn);
}, [router, searchParams, query]); // Dependencies for useEffect

50
// Render the search input field and icon
return (
<div className="search">
{/* Search icon */}
<Image
src="/assets/icons/search.svg"
alt="search"
width={24}
height={24}
/>

{/* Search input field */}


<Input
className="search-field"
placeholder="Search"
// Update the search query state on input change
onChange={(e) => setQuery(e.target.value)}
/>
</div>
);
};

51
File: TransformationForm.tsx

"use client"

// Importing necessary modules and components


import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from 'react-hook-form'
import { z } from "zod"
import { aspectRatioOptions, creditFee, defaultValues, transformationTypes } from
"@/constants"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { CustomField } from "./CustomField"
import { ReactNode, useEffect, useState, useTransition } from "react"
import { AspectRatioKey, debounce, deepMergeObjects } from "@/lib/utils"
import MediaUploader from "./MediaUploader"
import { updateCredits } from "@/lib/actions/user.actions"
import TransformedImage from "./TransformedImage"
import { getCldImageUrl } from "next-cloudinary"
import { addImage, updateImage } from "@/lib/actions/image.actions"
import { useRouter } from "next/navigation"
import { InsufficientCreditsModal } from "./insufficientCreditsModal"

// Define form schema using Zod for validation


export const formSchema = z.object({
title: z.string(),
aspectRatio: z.string().optional(),
color: z.string().optional(),
prompt: z.string().optional(),
publicId: z.string(),
})

// TransformationForm component

52
const TransformationForm = ({ action, data = null, userId, type, creditBalance,
config = null }: TransformationFormProps) => {

// States for managing form and component state


const transformationType = transformationTypes[type]
const [image, setImage] = useState(data)
const [newTransformation, setNewTransformation] = useState<Transformations |
null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const [isTransforming, setIsTransforming] = useState(false)
const [transformationConfig, settransformationConfig] = useState(config)
const [isPending, startTransition] = useTransition()
const router = useRouter()

// Set initial form values based on data


const initialValues = data && action === 'Update' ? {
title: data?.title,
aspectRatio: data?.aspectRatio,
color: data?.color,
prompt: data?.prompt,
publicId: data?.publicId,
} : defaultValues

// Initialize form using useForm hook from react-hook-form


const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: initialValues
})

// Handle form submission


async function onSubmit(values: z.infer<typeof formSchema>) {
setIsSubmitting(true);

// Check if there is image data available


if (data || image) {
// Generate transformation URL using Cloudinary
const transformationUrl = getCldImageUrl({
width: image?.width,
height: image?.height,
src: image?.publicId,
...transformationConfig
})

// Prepare image data


const imageData = {
title: values.title,
publicId: image?.publicId,
transformationType: type,
width: image?.width,
53
height: image?.height,
config: transformationConfig,
secureURL: image?.secureURL,
transformationURL: transformationUrl,
aspectRatio: values.aspectRatio,
prompt: values.prompt,
color: values.color
}

// Adding a new image


if (action === "Add") {
try {
const newImage = await addImage({
image: imageData,
userId,
path: '/'
})

if (newImage) {
form.reset()
setImage(data)
router.push(`/transformations/${newImage._id}`)
}

} catch (error) {
console.log(error)
}
}

// Updating an existing image


if (action === 'Update') {
try {
const updatedImage = await updateImage({
image: {
...imageData,
_id: data._id
},
userId,
path: `/transformations/${data._id}`
})

if (updatedImage) {
router.push(`/transformations/${updatedImage._id}`)
}
} catch (error) {
console.log(error);
}
}
}
54
setIsSubmitting(false)
}

// Handler for selecting aspect ratio


const onSelectFieldHandler = (value: string,
onChangeField: (value: string) => void) => {
const imageSize = aspectRatioOptions[value as AspectRatioKey]

setImage((prevState: any) => ({


...prevState,
aspectRatio: imageSize.aspectRatio,
width: imageSize.width,
height: imageSize.height,
}))

setNewTransformation(transformationType.config);

return onChangeField(value)
}

// Handler for input change


const onInputChangeHandler = (fieldName: string, value: string, type: string,
onChangeField: (value: string) => void) => {

// debounce function will wait for a specified time and then pass on the
values to be processed
debounce(() => {
setNewTransformation((prevState: any) => ({
...prevState,
[type]: {
...prevState?.[type],
[fieldName === 'prompt' ? 'prompt' : 'to']: value
}
}))
}, 1000)();

return onChangeField(value)
}

// Handler for transformation


const onTransformHandler = async () => {
setIsTransforming(true)

settransformationConfig(deepMergeObjects(newTransformation,
transformationConfig))

setNewTransformation(null)

// Use useTransition to update credits without blocking UI


55
startTransition(async () => {
await updateCredits(userId, creditFee)
})
}

// Effect for restoring image and remove background feature


useEffect(() => {
if(image && (type === 'restore' || type === 'removeBackground')) {
setNewTransformation(transformationType.config)
}
}, [image, transformationType.config, type])

// Render the component


return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">

{creditBalance < Math.abs(creditFee) &&


<InsufficientCreditsModal/>}

{/* Render image title field */}


<CustomField control={form.control}
name="title"
formLabel="Image Title"
className="w-full"
render={({ field }) => <Input {...field}
className="input-field" />}
/>

{/* Render aspect ratio selection if transformation type is 'fill'


*/}
{type === 'fill' && (
<CustomField control={form.control}
name="aspectRatio"
formLabel="Aspect Ratio"
className="w-full"
render={({ field }) =>
<Select
onValueChange={(value: string) =>
onSelectFieldHandler(value, field.onChange)}
value={field.value}
>
<SelectTrigger className="select-field">

<SelectValue placeholder="Select size" />


</SelectTrigger>
<SelectContent>
{Object.keys(aspectRatioOptions).map((key) =>
(
56
<SelectItem key={key} value={key}
className="select-item">
{aspectRatioOptions[key as
AspectRatioKey].label}
</SelectItem>
))}
</SelectContent>
</Select>

/>
)}

{/* Object removal functionality */}

{(type === 'remove' || type === 'recolor') && (


<div className="prompt-field">
<CustomField
control={form.control}
name="prompt"
formLabel={
type === 'remove' ? 'Object to remove' : 'Object
to recolor'
}
className="w-full"
render={({ field }) => (
<Input
value={field.value}
className="input-field"
onChange={(e) => onInputChangeHandler(
'prompt',
e.target.value,
type,
field.onChange
)}
/>
)}
/>

{type === 'recolor' && (


<CustomField
control={form.control}
name="color"
formLabel="Replacement Color"
className="w-full"
render={({ field }) => (
<Input
57
value={field.value}
className="input-field"
onChange={(e) => onInputChangeHandler(
'color',
e.target.value,
'recolor',
field.onChange
)}
/>
)}
/>
)}

</div>
)}

<div className="media-uploader-field">
<CustomField
control={form.control}
name="publicId"
className="flex size-full flex-col"
render={({ field }) => (
<MediaUploader
onValueChange={field.onChange}
setImage={setImage}
publicId={field.value}
image={image}
type={type}
/>
)}
/>

<TransformedImage
image={image}
type={type}
title={form.getValues().title}
isTransforming={isTransforming}
setIsTransforming={setIsTransforming}
transformationConfig={transformationConfig}
/>
</div>

<div className="flex flex-col gap-4">


<Button
type="button"
className="submit-button capitalize"
disabled={isTransforming || newTransformation === null}
onClick={onTransformHandler}
58
>
{isTransforming ? 'Transforming...' : 'Apply
Transformation'}
</Button>
<Button
type="submit"
className="submit-button capitalize"
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Save Image'}
</Button>
</div>
</form>
</Form>
)
}

export default TransformationForm

59
File: TransformedImage.tsx

// Import necessary modules and functions


"use client"
import { debounce, download } from '@/lib/utils'
import React from 'react'
import Image from 'next/image'
import { CldImage, getCldImageUrl } from 'next-cloudinary'
import { getImageSize, dataUrl } from "@/lib/utils"
import { PlaceholderValue } from 'next/dist/shared/lib/get-img-props'

// Define the TransformedImage component


const TransformedImage = ({ image, type, title, transformationConfig,
isTransforming, setIsTransforming, hasDownload = false }:
TransformedImageProps) => {

// Function to handle download button click


const downloadHandler = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>)
=> {
e.preventDefault();

// Generate download link and initiate download


download(getCldImageUrl({
width: image?.width,
height: image?.height,
src: image?.publicId,
...transformationConfig
}), title)
}

return (
<div className='flex flex-col gap-4'>
{/* Title and download button */}
<div className='flex-between '>
<h3 className='h3-bold text-dark-600'>Transformed</h3>
{hasDownload && (
<button className='download-btn'
onClick={downloadHandler}
>
<Image
src="/assets/icons/download.svg"
alt="Download"
width={24}
height={24}
className="pb-[6px]"
/>
</button>
)}
</div>

60
{/* Check if image exists and transformation configuration is provided
*/}
{image?.publicId && transformationConfig ? (
<div className='relative'>
{/* Render Cloudinary Image component */}
<CldImage
width={getImageSize(type, image, "width")}
height={getImageSize(type, image, "height")}
src={image?.publicId}
alt={image.title}
sizes={"(max-width: 767px) 100vw, 50vw"}
placeholder={dataUrl as PlaceholderValue}
className="transformed-image"
// Callback for image load event
onLoad={()=>{
setIsTransforming && setIsTransforming(false)
}}
// Callback for image error event
onError={()=>{
// Debounce the error handling function
debounce(()=>{
setIsTransforming && setIsTransforming(false)
},8000)()
}}
{...transformationConfig}
/>
{/* Render transforming loader if image is transforming */}
{isTransforming && (
<div className="transforming-loader">
<Image
src="/assets/icons/spinner.svg"
width={50}
height={50}
alt="spinner"
/>
<p className="text-white/80">Please wait...</p>
</div>
)}
</div>
) : (
// Render placeholder if image or transformation config is missing
<div className='transformed-placeholder'>
Transformed Image
</div>
)}
</div>
)
}

61
// Export the TransformedImage component
export default TransformedImage

File: sidebar.tsx

// Importing necessary modules and components


import { navLinks } from '@/constants'
import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Button } from '../ui/button'

// Sidebar component definition


const Sidebar = () => {
// Using usePathname hook to get current pathname
const pathname = usePathname();

return (
// Sidebar container
<aside className="sidebar">
<div className="flex size-full flex-col gap-4">
{/* Logo */}
<Link href="/" className="sidebar-logo">
<Image src="/assets/images/Clerk_pixel.jpeg" alt="logo"
width={300} height={28} />
</Link>

{/* Sidebar navigation */}


<nav className="sidebar-nav">
{/* Displaying navigation links for signed-in users */}
<SignedIn>
<ul className="sidebar-nav_elements">
{/* Mapping through navigation links and rendering
them */}
{navLinks.slice(0, 6).map((link) => {
const isActive = link.route === pathname

return (
// Rendering individual navigation link
<li key={link.route}
className={`sidebar-nav_element group ${isActive ? 'bg-purple-gradient text-white'
: 'text-gray-700'
}`}>
<Link className="sidebar-link"
href={link.route}>
<Image

62
src={link.icon}
alt="logo"
width={24}
height={24}
className={`${isActive &&
'brightness-200'}`}
/>
{link.label}
</Link>
</li>
)
})}
</ul>

{/* Additional navigation links for user administration


*/}
<ul className="sidebar-nav_elements">
{navLinks.slice(6).map((link) => {
const isActive = link.route === pathname

return (
<li key={link.route}
className={`sidebar-nav_element group ${isActive ? 'bg-purple-gradient text-white'
: 'text-gray-700'
}`}>
<Link className="sidebar-link"
href={link.route}>
<Image
src={link.icon}
alt="logo"
width={24}
height={24}
className={`${isActive &&
'brightness-200'}`}
/>
{link.label}
</Link>
</li>
)
})}

{/* User button for profile management */}


<li className="flex-center cursor-pointer gap-2 p-4">
<UserButton afterSignOutUrl='/' showName />
</li>
</ul>
</SignedIn>

{/* Displaying login button for signed-out users */}


63
<SignedOut>
<Button asChild className="button bg-purple-gradient
bg-cover">
<Link href="/sign-in">Login</Link>
</Button>
</SignedOut>
</nav>
</div>
</aside>
)
}

// Exporting Sidebar component as default


export default Sidebar

File: image.actions.ts

"use server"

// Import necessary modules and functions


import { revalidatePath } from "next/cache";
import { connectToDatabase } from "../database/mongoose";
import { handleError } from "../utils";
import User from "../database/models/user.model";
import Image from "../database/models/image.model";
import { redirect } from "next/navigation";
import { v2 as cloudinary } from 'cloudinary';

// Function to populate user data in a query


const populateUser = (query: any) => query.populate({
path: 'author',
model: User,
select: '_id firstName lastName clerkId'
});

// Function to add an image to the database


export async function addImage({image,userId,path}: AddImageParams){
try {
await connectToDatabase();

const author = await User.findById(userId); // Find user by id in users


collection

if(!author){
throw new Error("User not found in the database!!");
}

64
// Create a new image document in the database
const newImage = await Image.create({
...image,
author: author._id,
});

revalidatePath(path); // Invalidate cache of this path

return JSON.parse(JSON.stringify(newImage));

} catch (error) {
handleError(error);
}
}

// Function to update an image in the database


export async function updateImage({image,userId,path}: UpdateImageParams){
try {
await connectToDatabase();

const imageToUpdate = await Image.findById(image._id);

if(!imageToUpdate || imageToUpdate.author.toHexString()!== userId){


throw new Error("Unauthorized or Image not Found!!");
}

// Update the image document in the database


const updatedImage = await Image.findByIdAndUpdate(
imageToUpdate._id,
image,
{ new: true }
);

revalidatePath(path); // Invalidate cache of this path

return JSON.parse(JSON.stringify(updatedImage));

} catch (error) {
handleError(error);
}
}

// Function to delete an image from the database


export async function deleteImage(imageId: string){
try {
await connectToDatabase();

// Find and delete the image document by its id


await Image.findByIdAndDelete(imageId);
65
} catch (error) {
handleError(error);
}
finally{
redirect('/'); // Redirect to home page after deletion
}
}

// Function to get an image by its id


export async function getImageById(imageId: string){
try {
await connectToDatabase();

// Find and populate the user data associated with the image
const image = await populateUser(await Image.findById(imageId));

if(!image) throw new Error("No image with that ID");

return JSON.parse(JSON.stringify(image));

} catch (error) {
handleError(error);
}
}

// Function to get all images


export async function getAllImages({ limit = 9, page = 1, searchQuery = '' }: {
limit?: number;
page: number;
searchQuery?: string;
}) {
try {
await connectToDatabase();

// Configure cloudinary for image storage


cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
secure: true,
});

let expression = 'folder=pixel_palette';

// If there's a search query, add it to the cloudinary expression


if (searchQuery) {
expression += ` AND ${searchQuery}`;
}
66
// Execute cloudinary search to get resources
const { resources } = await cloudinary.search
.expression(expression)
.execute();

const resourceIds = resources.map((resource: any) => resource.public_id);

let query = {};

// If there's a search query, filter images by publicId


if(searchQuery) {
query = {
publicId: {
$in: resourceIds
}
};
}

const skipAmount = (Number(page) -1) * limit;

// Get images from the database, populate user data, and apply pagination
const images = await populateUser(Image.find(query))
.sort({ updatedAt: -1 })
.skip(skipAmount)
.limit(limit);

const totalImages = await Image.find(query).countDocuments();

return {
data: JSON.parse(JSON.stringify(images)),
totalPage: Math.ceil(totalImages / limit),
};
} catch (error) {
handleError(error);
}
}

// Function to get images by user


export async function getUserImages({
limit = 9,
page = 1,
userId,
}: {
limit?: number;
page: number;
userId: string;
}) {
try {
67
await connectToDatabase();

const skipAmount = (Number(page) - 1) * limit;

// Get images by a specific user, populate user data, and apply pagination
const images = await populateUser(Image.find({ author: userId }))
.sort({ updatedAt: -1 })
.skip(skipAmount)
.limit(limit);

const totalImages = await Image.find({ author: userId }).countDocuments();

return {
data: JSON.parse(JSON.stringify(images)),
totalPages: Math.ceil(totalImages / limit),
};
} catch (error) {
handleError(error);
}
}

File: transaction.action.ts

"use server"
import { redirect } from 'next/navigation'; // Importing the redirect function
from the next/navigation module
import Stripe from "stripe"; // Importing the Stripe library for payment
processing
import { handleError } from '../utils'; // Importing a utility function for error
handling
import { connectToDatabase } from '../database/mongoose'; // Importing a function
to connect to the database
import Transaction from '../database/models/transaction.model'; // Importing the
Transaction model from the database
import { updateCredits } from './user.actions'; // Importing a function to update
user credits from user.actions

// Function to handle checkout of credits


export async function checkoutCredits(transaction: CheckoutTransactionParams) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // Creating a new
instance of the Stripe object

const amount = Number(transaction.amount) * 100; // Calculating the total


amount in cents

// Creating a new Stripe checkout session

68
const session = await stripe.checkout.sessions.create({
line_itPixel Palette: [
{
price_data: {
currency: 'usd',
unit_amount: amount,
product_data: {
name: transaction.plan, // Name of the product or plan
}
},
quantity: 1 // Quantity of the product
}
],
metadata: {
plan: transaction.plan, // Metadata including plan name
credits: transaction.credits, // Credits associated with the transaction
buyerId: transaction.buyerId, // ID of the buyer
},
mode: 'payment', // Mode of the session (payment in this case)
success_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/profile`, // URL to
redirect on successful payment
cancel_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/`, // URL to redirect on
canceled payment
});

redirect(session.url!); // Redirecting to the checkout session URL


}

// Function to create a new transaction


export async function createTransaction(transaction: CreateTransactionParams) {
try {
await connectToDatabase(); // Connecting to the database

// Creating a new transaction with a buyerId


const newTransaction = await Transaction.create({
...transaction, buyer: transaction.buyerId
});

await updateCredits(transaction.buyerId, transaction.credits); // Updating


user credits

return JSON.parse(JSON.stringify(newTransaction)); // Returning the newly


created transaction
} catch (error) {
handleError(error); // Handling any errors that occur during the process
}
}

69
File: user.actions.ts

"use server"
import { redirect } from 'next/navigation'; // Importing the redirect function
from the next/navigation module
import Stripe from "stripe"; // Importing the Stripe library for payment
processing
import { handleError } from '../utils'; // Importing a utility function for error
handling
import { connectToDatabase } from '../database/mongoose'; // Importing a function
to connect to the database
import Transaction from '../database/models/transaction.model'; // Importing the
Transaction model from the database
import { updateCredits } from './user.actions'; // Importing a function to update
user credits from user.actions

// Function to handle checkout of credits


export async function checkoutCredits(transaction: CheckoutTransactionParams) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // Creating a new
instance of the Stripe object

const amount = Number(transaction.amount) * 100; // Calculating the total


amount in cents

// Creating a new Stripe checkout session


const session = await stripe.checkout.sessions.create({
line_itPixel Palette: [
{
price_data: {
currency: 'usd',
unit_amount: amount,
product_data: {
name: transaction.plan, // Name of the product or plan
}
},
quantity: 1 // Quantity of the product
}
],
metadata: {
plan: transaction.plan, // Metadata including plan name
credits: transaction.credits, // Credits associated with the transaction
buyerId: transaction.buyerId, // ID of the buyer
},
mode: 'payment', // Mode of the session (payment in this case)
success_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/profile`, // URL to
redirect on successful payment
cancel_url: `${process.env.NEXT_PUBLIC_SERVER_URL}/`, // URL to redirect on
canceled payment
});

70
redirect(session.url!); // Redirecting to the checkout session URL
}

// Function to create a new transaction


export async function createTransaction(transaction: CreateTransactionParams) {
try {
await connectToDatabase(); // Connecting to the database

// Creating a new transaction with a buyerId


const newTransaction = await Transaction.create({
...transaction, buyer: transaction.buyerId
});

await updateCredits(transaction.buyerId, transaction.credits); // Updating


user credits

return JSON.parse(JSON.stringify(newTransaction)); // Returning the newly


created transaction
} catch (error) {
handleError(error); // Handling any errors that occur during the process
}
}

71
13. Test Case & Test Plan

S.n Test Case Expected Result Actual Result Pass/ Fail


o
1 User can sign up on the Account is successfully Account created Pass
portal created successfully

2 User can log into their User is successfully User logged in Pass
account logged in successfully

3 User can view the Credits and user images Credits and images Pass
transformations displayed on the user displayed
performed and the profile
remaining credits on the
user profile

4 Image search The search bar should Correct results Pass


functionality works as display the results of the displayed
required topics that user searches

5 User can perform All the functionalities All image Pass


various image should work as transformations work
transformations on the intended right
images

6 Credits can be bought by The user should be User gets redirected Pass
the user to perform directed to the credits successfully and
various transformations page where various credits packs are
credit packs should be visible on the page
visible on the credits
page

7 User can complete the The payment gateway Payment gateway Pass
payment through stripe through stripe should world correctly
work correctly

72

You might also like