diff --git a/.gitignore b/.gitignore
index 1cd5fbcf8c2fe948a71c69d64fad98ebda7ad183..c4383a71d4bd5c122b7842e274fe368063114000 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@ db/
 .env
 .DS_Store
 src/server/html/
-.idea/
\ No newline at end of file
+.idea/
+src/public/assets/images/catalogs/posters
+src/public/assets/videos/catalogs/trailers
\ No newline at end of file
diff --git a/README.md b/README.md
index 74e59f30e0b01fc99b8b4c9a6b4a894012aa8141..50c334d8e219af794f2be0835381f5809e138b05 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,20 @@ menambahkan catalog anime dan drama.
 
 ## Daftar Isi
 
+- [Daftar Perubahan](#daftar-perubahan)
 - [Daftar Requirements](#daftar-requirements)
 - [Cara Instalasi](#cara-instalasi)
 - [Cara Menjalankan Server](#cara-menjalankan-server)
 - [Tangkapan Layar](#tangkapan-layar)
 - [Pembagian Tugas](#pembagian-tugas)
 
+## Daftar Perubahan
+
+- GET /catalog-request: Menampilkan view html
+- POST /api/v2/catalog-request: Mengirim request untuk membuat catalog request
+- DELETE /api/v2/catalog-request/poster/<poster-name>: Mengirim request untuk delete poster
+- DELETE /api/v2/catalog-request/trailer/<trailer-name>: Mengirim request untuk delete trailer
+
 ## Daftar Requirements
 
 - Docker
@@ -96,7 +104,7 @@ install sesuai dengan sistem operasi Anda.
 ### Server Side
 
 | Tugas              | NIM      |
-|--------------------|----------|
+| ------------------ | -------- |
 | Setup Awal Project | 13521150 |
 | Sign In            | 13521048 |
 | Sign Up            | 13521048 |
@@ -116,7 +124,7 @@ install sesuai dengan sistem operasi Anda.
 ### Client Side
 
 | Tugas              | NIM      |
-|--------------------|----------|
+| ------------------ | -------- |
 | Setup Awal Project | 13521150 |
 | Sign In            | 13521048 |
 | Sign Up            | 13521048 |
@@ -131,4 +139,4 @@ install sesuai dengan sistem operasi Anda.
 | Home               | 13521150 |
 | Watchlist Create   | 13521150 |
 | Watchlist Delete   | 13521150 |
-| Watchlist Edit     | 13521150 |
\ No newline at end of file
+| Watchlist Edit     | 13521150 |
diff --git a/src/migration/db.sql b/src/migration/db.sql
index 14fc6159cb1cb3697b2f5810931d6c8acbf82a7c..32a62ea6011acf1c3d3340868ef6dcc82e6f7ca7 100644
--- a/src/migration/db.sql
+++ b/src/migration/db.sql
@@ -1,11 +1,27 @@
-DO $$
-BEGIN
-    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role') THEN
-        CREATE TYPE role AS ENUM ('BASIC', 'ADMIN');
-    END IF;
-END
-$$;
-
+DO $$ BEGIN IF NOT EXISTS (
+    SELECT 1
+    FROM pg_type
+    WHERE typname = 'role'
+) THEN CREATE TYPE role AS ENUM ('BASIC', 'ADMIN');
+END IF;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN IF NOT EXISTS (
+    SELECT 1
+    FROM pg_type
+    WHERE typname = 'category'
+) THEN CREATE TYPE category AS ENUM ('ANIME', 'DRAMA', 'MIXED');
+END IF;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN IF NOT EXISTS (
+    SELECT 1
+    FROM pg_type
+    WHERE typname = 'visibility'
+) THEN CREATE TYPE visibility AS ENUM ('PRIVATE', 'PUBLIC');
+END IF;
+END $$;
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS users (
     id SERIAL PRIMARY KEY,
     uuid VARCHAR(36) NOT NULL UNIQUE,
@@ -13,29 +29,19 @@ CREATE TABLE IF NOT EXISTS users (
     password VARCHAR(255) NOT NULL,
     email VARCHAR(255) UNIQUE NOT NULL,
     role role DEFAULT 'BASIC' NOT NULL,
-
     created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
     updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
 );
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS sessions (
     id VARCHAR(16) PRIMARY KEY,
     user_id INT NOT NULL,
     expired TIMESTAMPTZ NOT NULL,
-
     created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
     updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
     FOREIGN KEY (user_id) REFERENCES users(id)
 );
-
-DO $$
-BEGIN
-    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'category') THEN
-        CREATE TYPE category AS ENUM ('ANIME', 'DRAMA', 'MIXED');
-    END IF;
-END
-$$;
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS catalogs (
     id SERIAL PRIMARY KEY,
     uuid VARCHAR(36) NOT NULL UNIQUE,
@@ -44,37 +50,11 @@ CREATE TABLE IF NOT EXISTS catalogs (
     poster VARCHAR(255) NOT NULL,
     trailer VARCHAR(255),
     category category NOT NULL,
-
     created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
     updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
-
     CHECK (category IN ('ANIME', 'DRAMA'))
 );
-
-CREATE OR REPLACE FUNCTION updated_at()
-RETURNS TRIGGER
-LANGUAGE PLPGSQL
-AS $$ 
-BEGIN
-    NEW.updated_at = NOW();
-    return NEW;
-END;
-$$;
-
-CREATE OR REPLACE TRIGGER t_user_updated_at BEFORE UPDATE 
-ON users FOR EACH ROW EXECUTE PROCEDURE updated_at();
-
-CREATE OR REPLACE TRIGGER t_catalog_updated_at BEFORE UPDATE 
-ON catalogs FOR EACH ROW EXECUTE PROCEDURE updated_at();
-
-DO $$
-BEGIN
-    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'visibility') THEN
-        CREATE TYPE visibility AS ENUM ('PRIVATE', 'PUBLIC');
-    END IF;
-END
-$$;
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS watchlists (
     id SERIAL NOT NULL PRIMARY KEY,
     uuid VARCHAR(36) NOT NULL UNIQUE,
@@ -85,147 +65,157 @@ CREATE TABLE IF NOT EXISTS watchlists (
     item_count integer DEFAULT 0 NOT NULL,
     like_count integer DEFAULT 0 NOT NULL,
     visibility visibility DEFAULT 'PRIVATE' NOT NULL,
-
     created_at timestamp DEFAULT now() NOT NULL,
     updated_at timestamp DEFAULT now() NOT NULL,
-
     FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
 );
-
-CREATE OR REPLACE TRIGGER t_watchlist_updated_at BEFORE UPDATE 
-ON watchlists FOR EACH ROW EXECUTE PROCEDURE updated_at();
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS watchlist_items(
     id SERIAL NOT NULL PRIMARY KEY,
     uuid VARCHAR(36) NOT NULL UNIQUE,
-
     rank INT NOT NULL,
     description VARCHAR(255),
     watchlist_id integer NOT NULL,
     catalog_id integer NOT NULL,
-
     created_at timestamp DEFAULT now() NOT NULL,
     updated_at timestamp DEFAULT now() NOT NULL,
-
     FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE,
     FOREIGN KEY (catalog_id) REFERENCES catalogs(id) ON DELETE CASCADE
 );
-
-CREATE OR REPLACE TRIGGER t_watchlist_items_updated_at BEFORE UPDATE 
-ON watchlists FOR EACH ROW EXECUTE PROCEDURE updated_at();
-
-CREATE OR REPLACE FUNCTION watchlist_item_count() 
-RETURNS TRIGGER
-LANGUAGE PLPGSQL
-AS $$
-BEGIN
-    IF (TG_OP = 'INSERT') THEN
-        UPDATE watchlists SET item_count = item_count + 1 WHERE id = NEW.watchlist_id;
-    ELSIF (TG_OP = 'DELETE') THEN
-        UPDATE watchlists SET item_count = item_count - 1 WHERE id = OLD.watchlist_id;
-    END IF;
-    RETURN NEW;
-END;
-$$;
-
-CREATE OR REPLACE TRIGGER t_watchlist_item_count AFTER INSERT OR DELETE
-ON watchlist_items FOR EACH ROW EXECUTE PROCEDURE watchlist_item_count();
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS watchlist_like (
     id SERIAL NOT NULL PRIMARY KEY,
     user_id integer NOT NULL,
     watchlist_id integer NOT NULL,
-
     FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
     FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE
 );
-
-
-CREATE OR REPLACE FUNCTION watchlist_like_count() 
-RETURNS TRIGGER
-LANGUAGE PLPGSQL
-AS $$
-BEGIN
-    IF (TG_OP = 'INSERT') THEN
-        UPDATE watchlists SET like_count = like_count + 1 WHERE id = NEW.watchlist_id;
-    ELSIF (TG_OP = 'DELETE') THEN
-        UPDATE watchlists SET like_count = like_count - 1 WHERE id = OLD.watchlist_id;
-    END IF;
-    RETURN NEW;
-END;
-$$;
-
-CREATE OR REPLACE TRIGGER t_watchlist_like_count AFTER INSERT OR DELETE
-ON watchlist_like FOR EACH ROW EXECUTE PROCEDURE watchlist_like_count();
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS watchlist_save (
     id SERIAL NOT NULL PRIMARY KEY,
     user_id integer NOT NULL,
     watchlist_id integer NOT NULL,
-
-    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, 
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
     FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE
 );
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS comments (
     id SERIAL NOT NULL PRIMARY KEY,
     uuid VARCHAR(36) NOT NULL UNIQUE,
     content VARCHAR(255) NOT NULL,
     user_id integer NOT NULL,
     watchlist_id integer NOT NULL,
-
     created_at timestamp DEFAULT now() NOT NULL,
-
     FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
     FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE
 );
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS tags (
     id SERIAL PRIMARY KEY,
     name VARCHAR(40) NOT NULL UNIQUE,
-
     created_at timestamp DEFAULT now() NOT NULL
 );
-
+--> statement-breakpoint
 CREATE TABLE IF NOT EXISTS watchlist_tag (
     watchlist_id integer NOT NULL,
     tag_id integer NOT NULL,
-
     PRIMARY KEY (watchlist_id, tag_id),
     FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE,
     FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
 );
-
-INSERT INTO users (uuid, name, password, email, role) VALUES ('72c5a265715fa26c', 'admin', '$2y$10$rAfDHA4M4ftn8K7Wx82wf.fFODD7PCE/t9CVnBwdLnTDBYjnq7ZnO', 'admin@drawl.com', 'ADMIN');
-INSERT INTO tags(name) VALUES ('ACTION'),
-                              ('ADVENTURE'),
-                              ('ANIMALS'),
-                              ('BUSINESS'),
-                              ('COMEDY'),
-                              ('CRIME'),
-                              ('DETECTIVE'),
-                              ('DOCUMENTARY'),
-                              ('DRAMA'),
-                              ('FAMILY'),
-                              ('FANTASY'),
-                              ('FOOD'),
-                              ('HISTORICAL'),
-                              ('HORROR'),
-                              ('LAW'),
-                              ('LIFE'),
-                              ('MANGA'),
-                              ('MEDICAL'),
-                              ('MATURE'),
-                              ('MYSTERY'),
-                              ('MUSIC'),
-                              ('MILITARY'),
-                              ('MELODRAMA'),
-                              ('PSYCHOLOGICAL'),
-                              ('ROMANCE'),
-                              ('SCHOOL'),
-                              ('SCI-FI'),
-                              ('SPORTS'),
-                              ('SUPERNATURAL'),
-                              ('THRILLER'),
-                              ('YOUTH')
-;
\ No newline at end of file
+--> statement-breakpoint
+CREATE OR REPLACE FUNCTION updated_at() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$ BEGIN NEW.updated_at = NOW();
+return NEW;
+END;
+$$;
+--> statement-breakpoint
+CREATE OR REPLACE FUNCTION watchlist_item_count() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$ BEGIN IF (TG_OP = 'INSERT') THEN
+UPDATE watchlists
+SET item_count = item_count + 1
+WHERE id = NEW.watchlist_id;
+ELSIF (TG_OP = 'DELETE') THEN
+UPDATE watchlists
+SET item_count = item_count - 1
+WHERE id = OLD.watchlist_id;
+END IF;
+RETURN NEW;
+END;
+$$;
+--> statement-breakpoint
+CREATE OR REPLACE FUNCTION watchlist_like_count() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$ BEGIN IF (TG_OP = 'INSERT') THEN
+UPDATE watchlists
+SET like_count = like_count + 1
+WHERE id = NEW.watchlist_id;
+ELSIF (TG_OP = 'DELETE') THEN
+UPDATE watchlists
+SET like_count = like_count - 1
+WHERE id = OLD.watchlist_id;
+END IF;
+RETURN NEW;
+END;
+$$;
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_user_updated_at BEFORE
+UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE updated_at();
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_catalog_updated_at BEFORE
+UPDATE ON catalogs FOR EACH ROW EXECUTE PROCEDURE updated_at();
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_watchlist_updated_at BEFORE
+UPDATE ON watchlists FOR EACH ROW EXECUTE PROCEDURE updated_at();
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_watchlist_items_updated_at BEFORE
+UPDATE ON watchlists FOR EACH ROW EXECUTE PROCEDURE updated_at();
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_watchlist_item_count
+AFTER
+INSERT
+    OR DELETE ON watchlist_items FOR EACH ROW EXECUTE PROCEDURE watchlist_item_count();
+--> statement-breakpoint
+CREATE OR REPLACE TRIGGER t_watchlist_like_count
+AFTER
+INSERT
+    OR DELETE ON watchlist_like FOR EACH ROW EXECUTE PROCEDURE watchlist_like_count();
+--> statement-breakpoint
+INSERT INTO users (uuid, name, password, email, role)
+VALUES (
+        '72c5a265715fa26c',
+        'admin',
+        '$2y$10$rAfDHA4M4ftn8K7Wx82wf.fFODD7PCE/t9CVnBwdLnTDBYjnq7ZnO',
+        'admin@drawl.com',
+        'ADMIN'
+    );
+--> statement-breakpoint
+INSERT INTO tags(name)
+VALUES ('ACTION'),
+    ('ADVENTURE'),
+    ('ANIMALS'),
+    ('BUSINESS'),
+    ('COMEDY'),
+    ('CRIME'),
+    ('DETECTIVE'),
+    ('DOCUMENTARY'),
+    ('DRAMA'),
+    ('FAMILY'),
+    ('FANTASY'),
+    ('FOOD'),
+    ('HISTORICAL'),
+    ('HORROR'),
+    ('LAW'),
+    ('LIFE'),
+    ('MANGA'),
+    ('MEDICAL'),
+    ('MATURE'),
+    ('MYSTERY'),
+    ('MUSIC'),
+    ('MILITARY'),
+    ('MELODRAMA'),
+    ('PSYCHOLOGICAL'),
+    ('ROMANCE'),
+    ('SCHOOL'),
+    ('SCI-FI'),
+    ('SPORTS'),
+    ('SUPERNATURAL'),
+    ('THRILLER'),
+    ('YOUTH');
+--> statement-breakpoint
\ No newline at end of file
diff --git a/src/public/assets/images/catalogs/posters/188f1a368142857e.webp b/src/public/assets/images/catalogs/posters/188f1a368142857e.webp
new file mode 100644
index 0000000000000000000000000000000000000000..b55ae97356eed638f243d72387578f4905229caf
Binary files /dev/null and b/src/public/assets/images/catalogs/posters/188f1a368142857e.webp differ
diff --git a/src/public/assets/images/catalogs/posters/no-poster.webp b/src/public/assets/images/catalogs/posters/no-poster.webp
deleted file mode 100644
index f5a0f34071dd9e57d1c1ced894189082f60c6140..0000000000000000000000000000000000000000
Binary files a/src/public/assets/images/catalogs/posters/no-poster.webp and /dev/null differ
diff --git a/src/public/assets/videos/catalogs/trailers/28eb5ae62ef05e3d.mp4 b/src/public/assets/videos/catalogs/trailers/28eb5ae62ef05e3d.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..0b2b5164c7e03f093a3a87dfbdcfed86cc6c18d0
Binary files /dev/null and b/src/public/assets/videos/catalogs/trailers/28eb5ae62ef05e3d.mp4 differ
diff --git a/src/public/assets/videos/catalogs/trailers/e99e29043515d496.mp4 b/src/public/assets/videos/catalogs/trailers/e99e29043515d496.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..99c1c2430523e47ea2c8a065c65278533fc26d0a
Binary files /dev/null and b/src/public/assets/videos/catalogs/trailers/e99e29043515d496.mp4 differ
diff --git a/src/public/css/catalog.css b/src/public/css/catalog.css
index e0a38b10e46d7b653deb5185d836ea94dfe22638..8e6d02cee41f1fa3fb0b80592fe7da9e0b46e8ae 100644
--- a/src/public/css/catalog.css
+++ b/src/public/css/catalog.css
@@ -1,3 +1,16 @@
+.search {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    flex-grow: 1;
+    width: 100%;
+    gap: 1rem;
+}
+
+.input-search {
+    flex-grow: 1;
+}
+
 form {
     display: flex;
     flex-wrap: wrap;
diff --git a/src/public/js/catalog/createRequest.js b/src/public/js/catalog/createRequest.js
new file mode 100644
index 0000000000000000000000000000000000000000..faaf3293697d7912ce7f9d90122a1064e0fe2edc
--- /dev/null
+++ b/src/public/js/catalog/createRequest.js
@@ -0,0 +1,125 @@
+const CATEGORY = ["ANIME", "DRAMA"];
+
+function validateInput(formData, update = false) {
+  if (!formData.get("title") || formData.get("title").trim() === "") {
+    return {
+      valid: false,
+      message: "Title is required.",
+    };
+  }
+  if (formData.get("title").length > 40) {
+    return {
+      valid: false,
+      message: "Title is too long. Maximum 40 chars.",
+    };
+  }
+  if (formData.get("description") && formData.get("description").length > 255) {
+    return {
+      valid: false,
+      message: "Description is too long. Maximum 255 chars.",
+    };
+  }
+  if (
+    !formData.get("category") ||
+    !CATEGORY.includes(formData.get("category").trim())
+  ) {
+    return {
+      valid: false,
+      message: "Category is invalid.",
+    };
+  }
+
+  if (!update && !formData.get("poster").name) {
+    return {
+      valid: false,
+      message: "Poster is required.",
+    };
+  }
+  return {
+    valid: true,
+  };
+}
+
+function createCatalogReq(form) {
+  const apiUrl = `/api/v2/catalog-request`;
+  const formData = new FormData(form);
+
+  const validate = validateInput(formData);
+
+  if (!validate.valid) {
+    showToast("Error", validate.message, "error");
+    return;
+  }
+
+  const xhttp = new XMLHttpRequest();
+  xhttp.open("POST", apiUrl, true);
+
+  xhttp.onreadystatechange = function () {
+    if (xhttp.readyState === 4) {
+      const response = JSON.parse(xhttp.responseText);
+      if (xhttp.status === 200 && response.status === 200) {
+        showToast("Success", `Request for catalog created`, "success");
+        setTimeout(() => {
+          window.location.href = `/catalog`;
+        }, [1000]);
+      } else {
+        try {
+          showToast("Error", response.message);
+        } catch (e) {
+          showToast("Error", "Something went wrong", "error");
+        }
+      }
+    }
+  };
+
+  xhttp.send(formData);
+}
+
+const form = document.querySelector("form#catalog-create-update");
+
+form.addEventListener("submit", function (event) {
+  event.preventDefault();
+
+  dialog(
+    "Request Catalog",
+    `Are you sure you want to request this catalog?`,
+    "request",
+    "request",
+    "Confirm",
+    () => {
+      createCatalogReq(form);
+    }
+  );
+});
+
+posterImg = document.querySelector("img#poster");
+if (posterImg) {
+  posterInput = document.querySelector("input#posterField");
+  posterInput.addEventListener("change", function (event) {
+    if (this.files[0]) {
+      var reader = new FileReader();
+
+      reader.readAsDataURL(this.files[0]);
+
+      reader.onloadend = function () {
+        posterImg.src = reader.result;
+      };
+    }
+  });
+}
+
+trailerVideo = document.querySelector("video#trailer");
+if (trailerVideo) {
+  trailerInput = document.querySelector("input#trailerField");
+  trailerInput.addEventListener("change", function (event) {
+    if (this.files[0]) {
+      var reader = new FileReader();
+
+      reader.readAsDataURL(this.files[0]);
+
+      reader.onloadend = function () {
+        trailerVideo.src = reader.result;
+      };
+    }
+  });
+}
diff --git a/src/public/js/catalog/createUpdate.js b/src/public/js/catalog/createUpdate.js
index a9986031bc7d281aea32489c966bcb7491205dcc..178fcf88e58abce890951a4e5f27e21f3c69cd70 100644
--- a/src/public/js/catalog/createUpdate.js
+++ b/src/public/js/catalog/createUpdate.js
@@ -41,7 +41,7 @@ function validateInput(formData, update = false) {
 }
 
 function createCatalog(form) {
-  const apiUrl = `/api/catalog/create`;
+  const apiUrl = `/api/catalog`;
   const formData = new FormData(form);
 
   const validate = validateInput(formData);
@@ -83,7 +83,7 @@ function updateCatalog(form) {
   const urlParts = window.location.pathname.split("/");
   const uuidIndex = urlParts.indexOf("catalog") + 1;
   const uuid = urlParts[uuidIndex];
-  const apiUrl = `/api/catalog/${uuid}/update`;
+  const apiUrl = `/api/catalog/${uuid}`;
   const formData = new FormData(form);
 
   const validate = validateInput(formData, true);
diff --git a/src/public/js/catalog/delete.js b/src/public/js/catalog/delete.js
index de2c8056536e030b53137e4ef05326d313a0b305..e8fbf561ee4522a322f5d96fcc9cb98f83da7c78 100644
--- a/src/public/js/catalog/delete.js
+++ b/src/public/js/catalog/delete.js
@@ -1,6 +1,6 @@
 function deleteCatalog(uuid, title) {
   const xhttp = new XMLHttpRequest();
-  xhttp.open("DELETE", `/api/catalog/${uuid}/delete`, true);
+  xhttp.open("DELETE", `/api/catalog/${uuid}`, true);
   xhttp.setRequestHeader("Content-Type", "application/json");
 
   xhttp.onreadystatechange = function () {
diff --git a/src/seed/seed.sql b/src/seed/seed.sql
index 8b62583090dd7f402747b70f44c583abb704cafd..864e302c971010d65a5dd1eee9ebb924e8d24301 100644
--- a/src/seed/seed.sql
+++ b/src/seed/seed.sql
@@ -1,3 +1,23 @@
+--> statement-breakpoint
+INSERT INTO users (uuid, name, password, email, role)
+VALUES (
+        '28f125abf7539b67',
+        'berry good',
+        '$2y$10$rAfDHA4M4ftn8K7Wx82wf.fFODD7PCE/t9CVnBwdLnTDBYjnq7ZnO',
+        'berrygood@gmail.com',
+        'BASIC'
+    );
+--> statement-breakpoint
+INSERT INTO catalogs (uuid, title, description, poster, trailer, category)
+VALUES (
+        '9511e980-c1ec-b3c8-53df-774bd4159aba',
+        'DEFAULT CATALOG',
+        'Hello',
+        '5a5ac4ad0c3a5e7c.webp',
+        'a3e992b0d939a896.mp4',
+        'ANIME'
+    );
+
 INSERT INTO catalogs (uuid, title, description, poster, trailer, category)
 SELECT
     md5(random()::text || clock_timestamp()::text)::uuid,
@@ -17,3 +37,15 @@ SELECT
     'a3e992b0d939a896.mp4',
     'DRAMA'
 FROM generate_series(1, 100) AS num;
+
+-- Generate and insert 10,000 user records
+INSERT INTO users (uuid, name, password, email, role, created_at, updated_at)
+SELECT
+    uuid_generate_v4() AS uuid,
+    'User' || gs AS name,
+    '$2y$10$rAfDHA4M4ftn8K7Wx82wf.fFODD7PCE/t9CVnBwdLnTDBYjnq7ZnO' AS password, -- Generating random password hashes
+    'user' || gs || '@example.com' AS email,
+    'BASIC' AS role,
+    NOW() - interval '1 year' * random() AS created_at,
+    NOW() - interval '1 year' * random() AS updated_at
+FROM generate_series(1, 10000) gs;
\ No newline at end of file
diff --git a/src/server/.env.example b/src/server/.env.example
index e22355013b0cb5006a33161d86fb2a915497536c..cef77833b8455b5dc1ddd0973e220814d8668ab5 100644
--- a/src/server/.env.example
+++ b/src/server/.env.example
@@ -1,5 +1,16 @@
 DB_NAME=
-DB_HOST=db
+DB_HOST=phpServiceDB
 DB_PORT=5432
 DB_USER=
-DB_PASSWORD=
\ No newline at end of file
+DB_PASSWORD=
+
+# On WSL find it using `hostname -I` command
+SPA_CLIENT_BASE_URL=
+SOAP_SERVICE_BASE_URL=
+REST_SERVICE_BASE_URL=
+
+# Max. 64-bit string hashed w/ bcrypt
+# Generate here: https://generate-random.org/api-key-generator?count=1&length=64&type=mixed-numbers&prefix=
+API_KEY=
+OUTBOUND_REST_API_KEY=
+OUTBOUND_SOAP_API_KEY=
\ No newline at end of file
diff --git a/src/server/app/Common/CustomResponse.php b/src/server/app/Common/CustomResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8799fba49ee5fca5974f4e81035036073393757
--- /dev/null
+++ b/src/server/app/Common/CustomResponse.php
@@ -0,0 +1,8 @@
+<?php
+
+class CustomResponse
+{
+    public int $status;
+    public string $message;
+    public $data;
+}
\ No newline at end of file
diff --git a/src/server/app/Common/ValidationResult.php b/src/server/app/Common/ValidationResult.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a58d6898084c8203238c268c48cd4c1e2a3e03d
--- /dev/null
+++ b/src/server/app/Common/ValidationResult.php
@@ -0,0 +1,7 @@
+<?php
+
+class ValidationResult
+{
+    public bool $success;
+    public string $message;
+}
diff --git a/src/server/app/Controller/CatalogController.php b/src/server/app/Controller/CatalogController.php
index fca0b5899ec27f13fd25175a97ecb220540b3bbd..da348871e8d75747f893875ec44a32a2bb2c16a1 100644
--- a/src/server/app/Controller/CatalogController.php
+++ b/src/server/app/Controller/CatalogController.php
@@ -15,6 +15,12 @@ require_once __DIR__ . '/../Model/CatalogCreateRequest.php';
 require_once __DIR__ . '/../Model/catalog/CatalogUpdateRequest.php';
 require_once __DIR__ . '/../Model/CatalogSearchRequest.php';
 
+require_once __DIR__ . '/../Utils/SOAPRequest.php';
+require_once __DIR__ . '/../Utils/GetRequestHeader.php';
+
+require_once __DIR__ . '/../Utils/UUIDGenerator.php';
+require_once __DIR__ . '/../Utils/FileUploader.php';
+
 class CatalogController
 {
     private CatalogService $catalogService;
@@ -34,6 +40,7 @@ class CatalogController
 
     public function index(): void
     {
+        $search = $_GET['search'] ?? "";
         $page = $_GET['page'] ?? 1;
         $category = $_GET['category'] ?? "MIXED";
 
@@ -45,10 +52,10 @@ class CatalogController
                 '/css/catalog.css',
             ],
             'js' => [
-                '/js/catalog/delete.js'
+                '/js/catalog/delete.js',
             ],
             'data' => [
-                'catalogs' => $this->catalogService->findAll($page, $category),
+                'catalogs' => $this->catalogService->findAll($page, $category, $search),
                 'category' => strtoupper(trim($category)),
                 'userRole' => $user ? $user->role : null
             ]
@@ -272,4 +279,132 @@ class CatalogController
             echo json_encode($response);
         }
     }
-}
\ No newline at end of file
+
+    // V2 methods
+
+    public function createCatalogFromRequest()
+    {
+        $json = file_get_contents('php://input');
+        $data = json_decode($json);
+
+        $request = new CatalogCreateRequest();
+        if (isset($data->category)) {
+            $request->category = $data->category;
+        }
+
+        $request->title = $data->title;
+        $request->description = $data->description;
+
+        if (isset($data->poster)) {
+            $request->poster = $data->poster;
+        }
+
+        if (isset($data->trailer)) {
+            $request->trailer = $data->trailer;
+        }
+
+        try {
+            $response = $this->catalogService->createFromRequest($request);
+            http_response_code(200);
+            $response = [
+                "status" => 200,
+                "message" => "Successfully created catalog",
+                "data" => [
+                    "uuid" => $response->catalog->uuid,
+                    "title" => $response->catalog->title,
+                ]
+            ];
+
+            echo json_encode($response);
+        } catch (ValidationException $exception) {
+            http_response_code(400);
+            $response = [
+                "status" => 400,
+                "message" => $exception->getMessage(),
+            ];
+
+            echo json_encode($response);
+        } catch (\Exception $exception) {
+            http_response_code(500);
+            $response = [
+                "status" => 500,
+                "message" => "Something went wrong.",
+            ];
+
+            echo json_encode($response);
+        }
+    }
+
+    // public function getCatalogRequest()
+    // {
+    //     $json = file_get_contents('php://input');
+    //     $data = json_decode($json);
+    //     $token = GetRequestHeader::getHeader("token", 1);
+
+    //     $page = $data->page ?? "";
+    //     $pagesize = $data->pagesize ?? "";
+
+
+    //     $headers = array("token:{$token}");
+    //     $body = [
+    //         "page" => $page,
+    //         "pagesize" => $pagesize,
+    //     ];
+
+    //     $soapRequest = new SOAPRequest("catalog-request", "GetCatalog", $headers, [], $body);
+    //     $response = $soapRequest->post();
+
+    //     echo json_encode($response);
+    // }
+
+    // public function catalogRequestCallback()
+    // {
+    //     $json = file_get_contents('php://input');
+    //     $data = json_decode($json);
+
+    //     $response = new CustomResponse();
+    //     $response->status = 200;
+    //     $response->message = 'Success';
+    //     $response->data = $data;
+
+    //     echo json_encode($response);
+    // }
+
+    public function getCatalogs()
+    {
+        $title = $_GET["title"] ?? "";
+        $page = $_GET["page"] ?? "1";
+        $amount = $_GET["amount"] ?? "10";
+
+
+        $catalogSearchRequest = new CatalogSearchRequest();
+        $catalogSearchRequest->title = $title;
+        $catalogSearchRequest->page = $page;
+        $catalogSearchRequest->pageSize = $amount;
+
+        $catalogs = $this->catalogService->search($catalogSearchRequest);
+
+        $response = new CustomResponse();
+        $response->status = 200;
+        $response->message = "Success";
+        $response->data = $catalogs->catalogs;
+
+        echo json_encode($response);
+    }
+
+    public function getCatalogByUUID(string $uuid)
+    {
+        $catalog = $this->catalogService->findByUUID($uuid);
+
+        $response = new CustomResponse();
+        $response->status = 200;
+        $response->message = "Success";
+        $response->data = [
+            "title" => $catalog->title,
+            "description" => $catalog->description,
+            "poster" => $catalog->poster
+        ];
+
+        echo json_encode($response);
+    }
+}
diff --git a/src/server/app/Controller/CatalogRequestController.php b/src/server/app/Controller/CatalogRequestController.php
new file mode 100644
index 0000000000000000000000000000000000000000..753974b981829359f9fb2021d60a7ef9781ab1e2
--- /dev/null
+++ b/src/server/app/Controller/CatalogRequestController.php
@@ -0,0 +1,106 @@
+<?php
+
+require_once __DIR__ . '/../App/View.php';
+require_once __DIR__ . '/../Config/Database.php';
+require_once __DIR__ . '/../Exception/ValidationException.php';
+
+require_once __DIR__ . '/../Service/CatalogService.php';
+require_once __DIR__ . '/../Service/SessionService.php';
+
+require_once __DIR__ . '/../Repository/CatalogRepository.php';
+require_once __DIR__ . '/../Repository/UserRepository.php';
+require_once __DIR__ . '/../Repository/SessionRepository.php';
+
+require_once __DIR__ . '/../Model/CatalogCreateRequest.php';
+require_once __DIR__ . '/../Model/catalog/CatalogUpdateRequest.php';
+require_once __DIR__ . '/../Model/CatalogSearchRequest.php';
+
+require_once __DIR__ . '/../Utils/SOAPRequest.php';
+require_once __DIR__ . '/../Utils/GetRequestHeader.php';
+
+require_once __DIR__ . '/../Utils/UUIDGenerator.php';
+require_once __DIR__ . '/../Utils/FileUploader.php';
+
+class CatalogRequestController
+{
+    private CatalogService $catalogService;
+    private SessionService $sessionService;
+
+    public function __construct()
+    {
+        $connection = Database::getConnection();
+        $catalogRepository = new CatalogRepository($connection);
+        $this->catalogService = new CatalogService($catalogRepository);
+        $sessionRepository = new SessionRepository($connection);
+        $userRepository = new UserRepository($connection);
+        $this->sessionService = new SessionService($sessionRepository, $userRepository);
+    }
+    public function request(): void
+    {
+        View::render('catalog/form', [
+            'title' => 'Request Catalog',
+            'styles' => [
+                '/css/catalog-form.css',
+            ],
+            'js' => [
+                '/js/catalog/createRequest.js'
+            ],
+            'type' => 'create'
+        ], $this->sessionService);
+    }
+
+    public function create()
+    {
+        $posterUploader = new FileUploader('Poster', 'assets/images/catalogs/posters/');
+        $trailerUploader = new FileUploader('Trailer', 'assets/videos/catalogs/trailers/');
+        $trailerUploader->allowedExtTypes = ["mp4"];
+        $trailerUploader->allowedMimeTypes = ["video/mp4"];
+        $trailerUploader->maxFileSize = 100000000;
+
+        if (isset($_FILES['poster']) && $_FILES['poster']['error'] == UPLOAD_ERR_OK) {
+            $postername = $posterUploader->uploadFie($_FILES['poster'], $_POST['title']);
+        }
+
+        if (isset($_FILES['trailer']) && $_FILES['trailer']['error'] == UPLOAD_ERR_OK) {
+            $trailername = $trailerUploader->uploadFie($_FILES['trailer'], $_POST['title']);
+        }
+
+        $body = [
+            "uuid" => UUIDGenerator::uuid4(),
+            "title" => isset($_POST['title']) ? $_POST['title'] : "",
+            "description" => isset($_POST['description']) ? $_POST['description'] : "",
+            "poster" => isset($postername) ? $postername : "",
+            "trailer" => isset($trailername) ? $trailername : "",
+            "category" => isset($_POST['category']) ? $_POST['category'] : "ANIME",
+        ];
+
+
+        $soapRequest = new SOAPRequest("catalog-request", "CatalogCreateRequest", [], [], $body);
+        $response = $soapRequest->post();
+        echo json_encode($response);
+    }
+
+    public function deletePoster($poster)
+    {
+        if (isset($poster)) {
+            unlink($_SERVER['DOCUMENT_ROOT'] . '/assets/images/catalogs/posters/' . $poster);
+        }
+
+        echo json_encode([
+            "status" => 200,
+            "message" => "Successfully delete catalog request",
+        ]);
+    }
+
+    public function deleteTrailer($trailer)
+    {
+        if (isset($trailer)) {
+            unlink($_SERVER['DOCUMENT_ROOT'] . '/assets/videos/catalogs/trailers/' . $trailer);
+        }
+
+        echo json_encode([
+            "status" => 200,
+            "message" => "Successfully delete catalog request",
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/src/server/app/Controller/UserController.php b/src/server/app/Controller/UserController.php
index 54a9f7361f2e000b3a8428d449690ca104ac3609..8bf380345a8e672a2b0c838e45410b98ddb98291 100644
--- a/src/server/app/Controller/UserController.php
+++ b/src/server/app/Controller/UserController.php
@@ -14,6 +14,8 @@ require_once __DIR__ . '/../Model/UserSignUpRequest.php';
 require_once __DIR__ . '/../Model/UserSignInRequest.php';
 require_once __DIR__ . '/../Model/UserEditRequest.php';
 require_once __DIR__ . '/../Model/session/SessionCreateRequest.php';
+require_once __DIR__ . '/../Model/user/SignInV2Request.php';
+require_once __DIR__ . '/../Model/user/GetUserInfoRequest.php';
 
 class UserController
 {
@@ -32,8 +34,11 @@ class UserController
 
     public function signUp(): void
     {
+        $redirectTo = $_GET['redirect_to'] ?? null;
+
         View::render('user/signUp', [
             'title' => 'Sign Up',
+            'redirectTo' => $redirectTo,
             'styles' => [
                 '/css/signUp.css',
             ],
@@ -72,17 +77,25 @@ class UserController
         $request->password = $_POST['password'];
         $request->confirmPassword = $_POST['passwordConfirm'];
 
+        $redirectTo = $_GET["redirect_to"] ?? null;
+
         try {
             $this->userService->signUp($request);
 
-            View::redirect('/signin');
+            View::redirect($redirectTo && $redirectTo != '' ? $redirectTo : '/signin');
         } catch (ValidationException $exception) {
             View::render('user/signUp', [
                 'title' => 'Sign Up',
                 'error' => $exception->getMessage(),
+                'redirectTo' => $redirectTo,
                 'styles' => [
                     '/css/signUp.css',
                 ],
+                'data' => [
+                    'email' => $request->email,
+                    'password' => $request->password,
+                    'confirmPassword' => $request->confirmPassword
+                ]
             ], $this->sessionService);
         }
     }
@@ -247,39 +260,64 @@ class UserController
     public function delete(): void
     {
         $currentUser = $this->sessionService->current();
+        $response = new CustomResponse();
 
         try {
-
             if (!$currentUser) {
                 throw new ValidationException("Unauthorized.", 401);
             }
-            $this->userService->deleteBySession($currentUser->email);
-            $this->userService->deleteByEmail($currentUser->email);
-            http_response_code(200);
 
-            $response = [
-                "status" => 200,
-                "message" => "Successfully delete user",
-            ];
+            $this->userService->deleteUser($currentUser->email, $currentUser->uuid);
+
+            http_response_code(200);
+            $response->status = 200;
+            $response->message = "User successfully deleted";
 
-            echo json_encode($response);
         } catch (ValidationException $exception) {
             http_response_code($exception->getCode() ?? 400);
 
-            $response = [
-                "status" => $exception->getCode() ?? 400,
-                "message" => $exception->getMessage(),
-            ];
-
-            echo json_encode($response);
+            $response->status = $exception->getCode() ?? 400;
+            $response->message = $exception->getMessage();
         } catch (\Exception $exception) {
-            http_response_code(500);
-            $response = [
-                "status" => 500,
-                "message" => "Something went wrong.",
-            ];
+            if ($exception->getCode() == 500) {
+                http_response_code($exception->getCode());
 
-            echo json_encode($response);
+                $response->status = $exception->getCode();
+                $response->message = $exception->getMessage();
+            }
+
+            http_response_code(500);
+            $response->status = $exception->getCode();
+            $response->message = $exception->getMessage();
         }
+
+        echo json_encode($response);
+    }
+
+    // V2 Methods
+    public function signInV2(): void
+    {
+        $json = file_get_contents('php://input');
+        $data = json_decode($json);
+
+        $request = new SignInV2Request();
+        $request->email = $data->email ?? "";
+        $request->password = $data->password ?? "";
+
+        $response = $this->userService->signInV2($request);
+
+        echo json_encode($response);
+    }
+
+    public function getUserInfo(): void
+    {
+        $userId = $_GET["userId"] ?? null;
+
+        $request = new GetUserInfoRequest();
+        $request->userId = $userId;
+
+        $response = $this->userService->getUserInfo($request);
+
+        echo json_encode($response);
     }
 }
\ No newline at end of file
diff --git a/src/server/app/Middleware/AuthPageMiddleware.php b/src/server/app/Middleware/AuthPageMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..922dba564859d7faca428f5d9b14bba877e1b39f
--- /dev/null
+++ b/src/server/app/Middleware/AuthPageMiddleware.php
@@ -0,0 +1,31 @@
+<?php
+
+require_once __DIR__ . '/../App/Middleware.php';
+require_once __DIR__ . '/../Service/SessionService.php';
+require_once __DIR__ . '/../Config/Database.php';
+
+require_once __DIR__ . '/../Repository/UserRepository.php';
+require_once __DIR__ . '/../Repository/SessionRepository.php';
+
+class AuthPageMiddleware implements Middleware
+{
+    private SessionService $sessionService;
+
+    public function __construct()
+    {
+        $connection = Database::getConnection();
+
+        $userRepository = new UserRepository($connection);
+        $sessionRepository = new SessionRepository($connection);
+        $this->sessionService = new SessionService($sessionRepository, $userRepository);
+    }
+
+    public function run(): void
+    {
+        $user = $this->sessionService->current();
+        if ($user != null) {
+            header("Location: /");
+            exit();
+        }
+    }
+}
diff --git a/src/server/app/Middleware/ExtUserAuthMiddleware.php b/src/server/app/Middleware/ExtUserAuthMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..99c4ff9d53a04024d294b66eaf46dfcfada5c9d9
--- /dev/null
+++ b/src/server/app/Middleware/ExtUserAuthMiddleware.php
@@ -0,0 +1,32 @@
+<?php
+
+require_once __DIR__ . '/../Common/CustomResponse.php';
+
+class ExtUserAuthMiddleware
+{
+    public function __construct()
+    {
+    }
+
+    public function run(): void
+    {
+        $headers = getallheaders();
+        $apiKey = "";
+        foreach ($headers as $headerName => $headerValue) {
+            if (strtolower($headerName) === 'authorization') {
+                $apiKey = explode(' ', $headerValue)[1];
+            }
+        }
+
+        if (!password_verify($apiKey, getenv('API_KEY'))) {
+            http_response_code(401);
+            $response = new CustomResponse();
+            $response->status = 401;
+            $response->message = "Unauthorized";
+            $response->data = $apiKey;
+
+            echo json_encode($response);
+            exit();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/server/app/Model/user/GetUserInfoRequest.php b/src/server/app/Model/user/GetUserInfoRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..957609f990ead8e167218aca2ffc93478579eb61
--- /dev/null
+++ b/src/server/app/Model/user/GetUserInfoRequest.php
@@ -0,0 +1,6 @@
+<?php
+
+class GetUserInfoRequest
+{
+    public ?string $userId;
+}
\ No newline at end of file
diff --git a/src/server/app/Model/user/SignInV2Request.php b/src/server/app/Model/user/SignInV2Request.php
new file mode 100644
index 0000000000000000000000000000000000000000..416ae77dabfea8a585c3395755a1f5703ec034ad
--- /dev/null
+++ b/src/server/app/Model/user/SignInV2Request.php
@@ -0,0 +1,7 @@
+<?php
+
+class SignInV2Request
+{
+    public ?string $email;
+    public ?string $password;
+}
\ No newline at end of file
diff --git a/src/server/app/Service/CatalogService.php b/src/server/app/Service/CatalogService.php
index f340805663344e6e679335c39b987cb5feb592ff..03fe243f5b6bc1b004d580ad6edd84fcca24a6c2 100644
--- a/src/server/app/Service/CatalogService.php
+++ b/src/server/app/Service/CatalogService.php
@@ -29,12 +29,14 @@ class CatalogService
         $this->trailerUploader->maxFileSize = 100000000;
     }
 
-    public function findAll(int $page = 1, string $category = "MIXED"): array
+    public function findAll(int $page = 1, string $category = "MIXED", string $search = ""): array
     {
         $query = $this->catalogRepository->query();
         if ($category != "MIXED") {
             $category = strtoupper(trim($category));
-            $query = $query->whereEquals('category', $category);
+            $query = $query->whereEquals('category', $category)->andWhereContains('title', $search);
+        } else {
+            $query = $query->whereContains('title', $search);
         }
         $projection = ['id', 'uuid', 'title', 'category', 'description', 'poster'];
         $catalogs = $query->get($projection, $page, 10);
@@ -147,11 +149,17 @@ class CatalogService
 
             if ($request->poster && $request->poster['error'] == UPLOAD_ERR_OK) {
                 $postername = $this->posterUploader->uploadFie($request->poster, $catalog->title);
+                if ($catalog->poster != null) {
+                    unlink($_SERVER['DOCUMENT_ROOT'] . '/assets/images/catalogs/posters/' . $catalog->poster);
+                }
                 $catalog->poster = $postername;
             }
 
             if ($request->trailer && $request->trailer['error'] == UPLOAD_ERR_OK) {
                 $trailername = $this->trailerUploader->uploadFie($request->trailer, $catalog->title);
+                if ($catalog->trailer != null) {
+                    unlink($_SERVER['DOCUMENT_ROOT'] . '/assets/videos/catalogs/trailers/' . $catalog->trailer);
+                }
                 $catalog->trailer = $trailername;
             }
 
@@ -171,6 +179,68 @@ class CatalogService
         }
     }
 
+    public function createFromRequest(CatalogCreateRequest $request)
+    {
+        $this->validateCatalogCreateFromRequest($request);
+
+        try {
+            Database::beginTransaction();
+
+            $catalog = new Catalog();
+
+            $catalog->uuid = UUIDGenerator::uuid4();
+            $catalog->title = trim($request->title);
+            $catalog->description = trim($request->description);
+
+            $catalog->poster = $request->poster;
+            $catalog->trailer = $request->trailer;
+            $catalog->category = strtoupper(trim($request->category));
+
+            $this->catalogRepository->save($catalog);
+
+            $response = new CatalogCreateResponse();
+            $response->catalog = $catalog;
+
+            Database::commitTransaction();
+            return $response;
+        } catch (FileUploaderException $exception) {
+            Database::rollbackTransaction();
+            throw new ValidationException($exception->getMessage());
+        } catch (\Exception $exception) {
+            Database::rollbackTransaction();
+            throw $exception;
+        }
+    }
+
+    private function validateCatalogCreateFromRequest(CatalogCreateRequest $request)
+    {
+        if (
+            $request->title == null || trim($request->title) == ""
+        ) {
+            throw new ValidationException("Title cannot be blank.");
+        }
+
+        if (strlen($request->title) > 40) {
+            throw new ValidationException("Title cannot be more than 40 characters.");
+        }
+
+        if (strlen($request->description) > 255) {
+            throw new ValidationException("Description cannot be more than 255 characters.");
+        }
+
+        if ($request->category == null || trim($request->category) == "") {
+            throw new ValidationException("Category cannot be blank.");
+        }
+
+        if ($request->category != "ANIME" && $request->category != "DRAMA") {
+            throw new ValidationException("Category must be either ANIME or DRAMA.");
+        }
+
+        if ($request->poster == null || trim($request->poster) == "") {
+            throw new ValidationException("Poster cannot be blank.");
+        }
+    }
+
     private function validateCatalogUpdateRequest(CatalogUpdateRequest $request)
     {
         if ($request->uuid == null || trim($request->uuid) == "") {
diff --git a/src/server/app/Service/UserService.php b/src/server/app/Service/UserService.php
index f3f768014bead8cdcd6e7dcc393e3819b7939d40..6bf9c628c1e995525549859195f4ff3862f864ff 100644
--- a/src/server/app/Service/UserService.php
+++ b/src/server/app/Service/UserService.php
@@ -12,6 +12,9 @@ require_once __DIR__ . '/../Model/UserSignInResponse.php';
 require_once __DIR__ . '/../Model/UserEditRequest.php';
 require_once __DIR__ . '/../Model/UserEditResponse.php';
 
+require_once __DIR__ . '/../Common/ValidationResult.php';
+require_once __DIR__ . '/../Common/CustomResponse.php';
+
 
 class UserService
 {
@@ -117,6 +120,14 @@ class UserService
             throw new ValidationException("Email, password, and confirm password cannot be blank.");
         }
 
+        if (strlen($request->password) < 8) {
+            throw new ValidationException("Password is too short, minimum 8 characters");
+        }
+
+        if (strlen($request->password) > 255) {
+            throw new ValidationException("Password is too long, maximum 255 characters");
+        }
+
         if ($request->password != $request->confirmPassword) {
             throw new ValidationException("Make sure both passwords are typed the same.");
         }
@@ -199,4 +210,166 @@ class UserService
     {
         $this->userRepository->deleteBySession($email);
     }
+
+
+    // V2 Methods
+    public function signInV2(SignInV2Request $request): CustomResponse
+    {
+        $response = new CustomResponse();
+
+        $validateResult = $this->validateSignInV2($request);
+        if (!$validateResult->success) {
+            $response->status = 400;
+            $response->message = $validateResult->message;
+
+            http_response_code($response->status);
+
+            return $response;
+        }
+
+        $user = $this->userRepository->findOne('email', $request->email);
+        if ($user == null) {
+            $response->status = 400;
+            $response->message = "Invalid email or password";
+
+            http_response_code($response->status);
+
+            return $response;
+        }
+
+        if (!password_verify($request->password, $user->password)) {
+            $response->status = 400;
+            $response->message = "Invalid email or password";
+
+            http_response_code($response->status);
+
+            return $response;
+        }
+
+        $response->status = 200;
+        $response->message = "Sign In Success";
+        $response->data = [
+            "uuid" => $user->uuid,
+            "email" => $user->email,
+            "name" => $user->name,
+            "role" => $user->role,
+        ];
+
+        http_response_code($response->status);
+
+        return $response;
+    }
+
+    public function getUserInfo(GetUserInfoRequest $request): CustomResponse
+    {
+        $response = new CustomResponse();
+
+        $validateResult = $this->validateGetUserInfo($request);
+        if (!$validateResult->success) {
+            $response->status = 400;
+            $response->message = $validateResult->message;
+
+            http_response_code($response->status);
+
+            return $response;
+        }
+
+        $user = $this->userRepository->findOne('uuid', $request->userId);
+
+        if ($user == null) {
+            $response->status = 404;
+            $response->message = "User not found";
+
+            http_response_code($response->status);
+
+            return $response;
+        }
+
+        $response->status = 200;
+        $response->message = "Success";
+        $response->data = [
+            "uuid" => $user->uuid,
+            "email" => $user->email,
+            "name" => $user->name,
+            "role" => $user->role,
+        ];
+
+        http_response_code($response->status);
+
+        return $response;
+    }
+
+    private function validateSignInV2(SignInV2Request $request): ValidationResult
+    {
+        $result = new ValidationResult();
+        if ($request->email == null || trim($request->email) == "") {
+            $result->success = false;
+            $result->message = "Email is required";
+            return $result;
+        }
+        if ($request->password == null || trim($request->password) == "") {
+            $result->success = false;
+            $result->message = "Password is required";
+            return $result;
+        }
+
+        $result->success = true;
+        $result->message = "";
+
+        return $result;
+    }
+
+    private function validateGetUserInfo(GetUserInfoRequest $request): ValidationResult
+    {
+        $result = new ValidationResult();
+
+        if ($request->userId == null || trim($request->userId) == "") {
+            $result->success = false;
+            $result->message = "User ID is required";
+            return $result;
+        }
+
+        $result->success = true;
+        $result->message = "";
+
+        return $result;
+    }
+
+    public function deleteUser(string $email, string $uuid)
+    {
+        try {
+            Database::beginTransaction();
+            $this->deleteBySession($email);
+            $this->deleteByEmail($email);
+
+            // delete profile on REST service
+            $baseUrl = getenv('REST_SERVICE_BASE_URL');
+            $url = "{$baseUrl}/profile/{$uuid}";
+            $curl = curl_init($url);
+            $httpHeaders = array(
+                "api_key: " . getenv('OUTBOUND_REST_API_KEY'),
+            );
+            curl_setopt($curl, CURLOPT_URL, $url);
+            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $httpHeaders);
+
+            $response = curl_exec($curl);
+            $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+            curl_close($curl);
+
+            if (!$response) {
+                throw new Exception("Something went wrong, please try again later", 500);
+            }
+
+            if ($httpCode != "200") {
+                throw new Exception("Something went wrong, please try again later", 500);
+            }
+
+            Database::commitTransaction();
+        } catch (Exception $exception) {
+            Database::rollbackTransaction();
+            throw $exception;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/server/app/Utils/GetRequestHeader.php b/src/server/app/Utils/GetRequestHeader.php
new file mode 100644
index 0000000000000000000000000000000000000000..45edf1c58986f87d18a21c48dc2cf1b929fb6525
--- /dev/null
+++ b/src/server/app/Utils/GetRequestHeader.php
@@ -0,0 +1,17 @@
+<?php
+
+class GetRequestHeader
+{
+    public static function getHeader(string $name, int $position): string
+    {
+        $headers = getallheaders();
+        $header = "";
+        foreach ($headers as $headerName => $headerValue) {
+            if (strtolower($headerName) === $name) {
+                $header = explode(' ', $headerValue)[$position - 1];
+            }
+        }
+
+        return $header;
+    }
+}
\ No newline at end of file
diff --git a/src/server/app/Utils/SOAPRequest.php b/src/server/app/Utils/SOAPRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..393504c724dabb5e0a0ee7f1dfdf7e3e7a381257
--- /dev/null
+++ b/src/server/app/Utils/SOAPRequest.php
@@ -0,0 +1,96 @@
+<?php
+
+require_once __DIR__ . '/../Common/CustomResponse.php';
+
+require_once __DIR__ . '/../Utils/SOAPResponse.php';
+
+class SOAPRequest
+{
+    public string $url;
+    private string $endpoint;
+    private array $headers;
+    private string $operationName;
+    private array $soapHeaders;
+    private array $soapBody;
+    private string $serviceName;
+
+    public function __construct($endpoint, $operationName, $headers, $soapHeaders, $soapBody, $serviceName = "http://Services.soapService.org/")
+    {
+        $this->url = (getenv("SOAP_SERVICE_BASE_URL") ?? "http://host.docker.internal:8083") . "/" . $endpoint;
+        $this->endpoint = $endpoint;
+        $this->operationName = $operationName;
+        $this->headers = $headers;
+        $this->soapHeaders = $soapHeaders;
+        $this->soapBody = $soapBody;
+        $this->serviceName = $serviceName;
+    }
+
+    public function post(): CustomResponse
+    {
+        $curl = curl_init($this->url);
+
+        $httpHeaders = array_merge(
+            array(
+                "Content-Type: text/xml",
+                "token:" . getenv("OUTBOUND_SOAP_API_KEY")
+            ),
+        );
+        $body = <<<BODY
+            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="{$this->serviceName}">
+                {$this->parseHeader()}
+                {$this->parseBody()}
+            </soapenv:Envelope>
+        BODY;
+
+        curl_setopt($curl, CURLOPT_URL, $this->url);
+        curl_setopt($curl, CURLOPT_POST, true);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $httpHeaders);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        $response = curl_exec($curl);
+        $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+        if (!$response) {
+            $finalResponse = new CustomResponse();
+            http_response_code(500);
+            $finalResponse->status = 500;
+            $finalResponse->message = "Something went wrong, please try again later";
+            curl_close($curl);
+            return $finalResponse;
+        }
+
+        curl_close($curl);
+        if ($httpCode == "500") {
+            return SOAPResponse::parseFault($response);
+        } else {
+            return SOAPResponse::parseSuccess($response);
+        }
+    }
+
+    private function parseHeader(): string
+    {
+        $xml = new DOMDocument();
+        $root = $xml->appendChild($xml->createElement("soapenv:Header"));
+
+        foreach ($this->soapHeaders as $key => $value) {
+            $root->appendChild($xml->createElement($key, $value));
+        }
+
+        return $xml->saveXML($xml->documentElement);
+    }
+
+    private function parseBody(): string
+    {
+        $xml = new DOMDocument();
+        $root = $xml->appendChild($xml->createElement("soapenv:Body"));
+        $operation = $root->appendChild($xml->createElement("ser:{$this->operationName}"));
+
+        foreach ($this->soapBody as $key => $value) {
+            $operation->appendChild($xml->createElement($key, $value));
+        }
+
+        return $xml->saveXML($xml->documentElement);
+    }
+}
\ No newline at end of file
diff --git a/src/server/app/Utils/SOAPResponse.php b/src/server/app/Utils/SOAPResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..4c8000d96853a4601f1cdf8b9ba9a2a9864b6daf
--- /dev/null
+++ b/src/server/app/Utils/SOAPResponse.php
@@ -0,0 +1,43 @@
+<?php
+
+
+require_once __DIR__ . '/../Common/CustomResponse.php';
+
+class SOAPResponse
+{
+    public static function parseFault(string $xml): CustomResponse
+    {
+        $p = xml_parser_create();
+        xml_parse_into_struct($p, $xml, $vals, $index);
+        xml_parser_free($p);
+
+        $response = new CustomResponse();
+        $response->status = (int) $vals[$index["FAULTCODE"][0]]["value"];
+        $response->message = $vals[$index["FAULTSTRING"][0]]["value"];
+
+        http_response_code($response->status);
+        return $response;
+    }
+
+    public static function parseSuccess(string $xml): CustomResponse
+    {
+        $p = xml_parser_create();
+        xml_parse_into_struct($p, $xml, $vals, $index);
+        xml_parser_free($p);
+
+        $response = new CustomResponse();
+        $response->status = (int) $vals[$index["STATUS"][0]]["value"];
+        $response->message = $vals[$index["MESSAGE"][0]]["value"];
+        $response->data = [];
+
+        for ($i = 0; $i < count($index["DATA"]); $i += 2) {
+            $temp = [];
+            for ($j = $index["DATA"][$i] + 1; $j < $index["DATA"][$i + 1]; $j++) {
+                $temp[strtolower($vals[$j]["tag"])] = isset($vals[$j]["value"]) ? $vals[$j]["value"] : "";
+            }
+            $response->data[] = $temp;
+        }
+
+        return $response;
+    }
+}
\ No newline at end of file
diff --git a/src/server/app/View/catalog/index.php b/src/server/app/View/catalog/index.php
index a6df824829344d67024db762795792956b11baeb..94f13ad522e6ba946c03ef6dce151d1a756c2ee9 100644
--- a/src/server/app/View/catalog/index.php
+++ b/src/server/app/View/catalog/index.php
@@ -31,6 +31,11 @@ function pagination(int $currentPage, int $totalPage)
 <main>
     <section class="search-filter">
         <form action="/catalog">
+            <div class="search">
+                <?php require PUBLIC_PATH . 'assets/icons/search.php'; ?>
+                <input type="text" name="search" placeholder="Search title" class="input-default input-search"
+                    value="<?= trim($_GET['search'] ?? '') ?? '' ?>" />
+            </div>
             <div class="input">
                 <label>Category</label>
                 <?php selectCategory($model['data']['category'] ?? ""); ?>
@@ -47,6 +52,14 @@ function pagination(int $currentPage, int $totalPage)
                 Add Catalog
             </a>
         <?php endif; ?>
+        <?php if ($model['data']['userRole'] && $model['data']['userRole'] !== "ADMIN"): ?>
+            <a href="/catalog-request" class="btn btn-bold">
+                <span class="icon-new">
+                    <?php require PUBLIC_PATH . 'assets/icons/plus.php' ?>
+                </span>
+                Request Catalog
+            </a>
+        <?php endif; ?>
     </section>
     <?php if (count($model['data']['catalogs']['items']) == 0): ?>
         <div class="no-item__container">
@@ -57,7 +70,7 @@ function pagination(int $currentPage, int $totalPage)
             </div>
         </div>
     <?php endif; ?>
-    <section class="content">
+    <section class="content list__catalog">
         <?php foreach ($model['data']['catalogs']['items'] ?? [] as $catalog): ?>
             <?php catalogCard($catalog, $model['data']['userRole'] && $model['data']['userRole'] === "ADMIN"); ?>
         <?php endforeach; ?>
diff --git a/src/server/app/View/user/signUp.php b/src/server/app/View/user/signUp.php
index a9e507ea5650688a549ca6ccd404e140312a2e45..60445bfc3680a4ab7c860ce83b97e69b0d6a68f5 100644
--- a/src/server/app/View/user/signUp.php
+++ b/src/server/app/View/user/signUp.php
@@ -8,7 +8,7 @@ function alert($title, $message)
 ?>
 
 <div class="signup-container row">
-    <img src="/assets/images/Tomorrow.webp" alt="Sign Up Image" class="signup-poster" />
+    <img src="/assets/images/Tomorrow.webp" alt="Sign Up Image" class="signup-poster"/>
     <div class="right-side">
         <div class="main-container">
             <div class="welcome-text">
@@ -20,26 +20,28 @@ function alert($title, $message)
                 <?php alert('Failed to Sign up', $model['error']); ?>
             <?php endif; ?>
 
-            <form class="inputs" action="/signup" method="post">
+            <form class="inputs" action=<?= "/signup?redirect_to=" . $model["redirectTo"] ?> method="post">
                 <div class="parameter">
                     <label for="email" class="input-required">Email</label>
                     <input type="email" name="email" id="email" class="input-default" required
-                        placeholder="Enter email">
+                           placeholder="Enter email" value=<?= $model["data"]["email"] ?? "" ?>>
                 </div>
                 <div class="parameter">
                     <label for="password" class="input-required">Password</label>
                     <input type="password" name="password" id="password" class="input-default" required
-                        placeholder="Enter password" />
+                           placeholder="Enter password" value=<?= $model["data"]["password"] ?? "" ?>>
                 </div>
                 <div class="parameter">
                     <label for="passwordConfirm" class="input-required">Confirm Password</label>
                     <input type="password" name="passwordConfirm" id="passwordConfirm" class="input-default" required
-                        placeholder="Enter confirm password" />
+                           placeholder="Enter confirm password" value=<?= $model["data"]["confirmPassword"] ?? "" ?>>
                 </div>
                 <button class="btn-bold" type="submit">
                     Sign Up
                 </button>
-                <p>Already have an account? <a href="/signin" class="signin-link">Sign in</a></p>
+                <p>Already have an account? <a href="/signin" class="signin-link">Sign in to Drawl</a> or <a
+                            href="<?= getenv("SPA_CLIENT_BASE_URL") . '/auth/login' ?>" class="signin-link">Sign in to
+                        DQ</a></p>
             </form>
         </div>
     </div>
diff --git a/src/server/routes/view.php b/src/server/routes/view.php
index 850d114035dbdc88c0174227ae6e11552fc51d30..bea3b2cda0125f5995537e07fa42630552d8718e 100644
--- a/src/server/routes/view.php
+++ b/src/server/routes/view.php
@@ -7,11 +7,14 @@ require_once __DIR__ . "/../app/Controller/CatalogController.php";
 require_once __DIR__ . '/../app/Controller/WatchlistController.php';
 require_once __DIR__ . '/../app/Controller/ErrorPageController.php';
 require_once __DIR__ . '/../app/Controller/BookmarkController.php';
+require_once __DIR__ . "/../app/Controller/CatalogRequestController.php";
 
 require_once __DIR__ . '/../app/Middleware/UserAuthMiddleware.php';
+require_once __DIR__ . '/../app/Middleware/AuthPageMiddleware.php';
 require_once __DIR__ . '/../app/Middleware/AdminAuthMiddleware.php';
 require_once __DIR__ . '/../app/Middleware/UserAuthApiMiddleware.php';
 require_once __DIR__ . '/../app/Middleware/AdminAuthApiMiddleware.php';
+require_once __DIR__ . '/../app/Middleware/ExtUserAuthMiddleware.php';
 
 
 // Register routes
@@ -20,23 +23,39 @@ Router::add('GET', '/', HomeController::class, 'index', []);
 Router::add("GET", "/api/watchlists", HomeController::class, 'watchlists', []);
 
 // User controllers
-Router::add('GET', '/signup', UserController::class, 'signUp', []);
+Router::add('GET', '/signup', UserController::class, 'signUp', [AuthPageMiddleware::class]);
 Router::add('POST', '/signup', UserController::class, 'postSignUp', []);
-Router::add('GET', '/signin', UserController::class, 'signIn', []);
+Router::add('GET', '/signin', UserController::class, 'signIn', [AuthPageMiddleware::class]);
 Router::add('POST', '/signin', UserController::class, 'postSignIn', []);
+
 Router::add('POST', '/api/auth/logout', UserController::class, 'logout', [UserAuthMiddleware::class]);
-Router::add('DELETE', '/api/auth/delete', UserController::class, 'delete', [UserAuthMiddleware::class]);
+Router::add('DELETE', '/api/auth/delete', UserController::class, 'delete', []);
 Router::add('PUT', '/api/auth/update', UserController::class, 'update', [UserAuthMiddleware::class]);
 
+Router::add('POST', '/api/v2/auth/signin', UserController::class, 'signInV2', [ExtUserAuthMiddleware::class]);
+Router::add('GET', '/api/v2/auth/user', UserController::class, 'getUserInfo', [ExtUserAuthMiddleware::class]);
+
+
 // Catalog controllers
 Router::add('GET', '/catalog', CatalogController::class, 'index', []);
 Router::add('GET', '/catalog/create', CatalogController::class, 'create', [AdminAuthMiddleware::class]);
 Router::add('GET', '/catalog/([A-Za-z0-9\-]*)', CatalogController::class, 'detail', []);
 Router::add('GET', '/catalog/([A-Za-z0-9\-]*)/edit', CatalogController::class, 'edit', [AdminAuthMiddleware::class]);
-Router::add('POST', '/api/catalog/create', CatalogController::class, 'postCreate', [AdminAuthMiddleware::class]);
+
+Router::add('POST', '/api/catalog', CatalogController::class, 'postCreate', [AdminAuthMiddleware::class]);
 Router::add('GET', '/api/catalog', CatalogController::class, "search", [UserAuthApiMiddleware::class]);
-Router::add("DELETE", "/api/catalog/([A-Za-z0-9\-]*)/delete", CatalogController::class, "delete", [AdminAuthMiddleware::class]);
-Router::add("POST", '/api/catalog/([A-Za-z0-9\-]*)/update', CatalogController::class, 'update', [AdminAuthMiddleware::class]);
+Router::add("DELETE", "/api/catalog/([A-Za-z0-9\-]*)", CatalogController::class, "delete", [AdminAuthMiddleware::class]);
+Router::add("POST", '/api/catalog/([A-Za-z0-9\-]*)', CatalogController::class, 'update', [AdminAuthMiddleware::class]);
+
+Router::add("POST", "/api/v2/catalog-from-request", CatalogController::class, "createCatalogFromRequest", [ExtUserAuthMiddleware::class]);
+Router::add("GET", "/api/v2/catalogs", CatalogController::class, "getCatalogs", [ExtUserAuthMiddleware::class]);
+Router::add("GET", "/api/v2/catalog/([A-Za-z0-9\-]*)", CatalogController::class, "getCatalogByUUID", [ExtUserAuthMiddleware::class]);
+
+// Catalog request
+Router::add('GET', '/catalog-request', CatalogRequestController::class, 'request', [UserAuthMiddleware::class]);
+Router::add("POST", "/api/v2/catalog-request", CatalogRequestController::class, "create", [UserAuthMiddleware::class]);
+Router::add("DELETE", "/api/v2/catalog-request/poster/([A-Za-z0-9\-\.]*)", CatalogRequestController::class, "deletePoster", [ExtUserAuthMiddleware::class]);
+Router::add("DELETE", "/api/v2/catalog-request/trailer/([A-Za-z0-9\-\.]*)", CatalogRequestController::class, "deleteTrailer", [ExtUserAuthMiddleware::class]);
 
 // Watchlist controllers
 Router::add("GET", "/watchlist/create", WatchlistController::class, 'create', [UserAuthMiddleware::class]);
@@ -50,7 +69,7 @@ Router::add("GET", "/api/watchlist/item", WatchlistController::class, 'item', [U
 Router::add("POST", "/api/watchlist/like", WatchlistController::class, "like", [UserAuthApiMiddleware::class]);
 Router::add("POST", "/api/watchlist/save", WatchlistController::class, "bookmark", [UserAuthApiMiddleware::class]);
 
-
+// Profile
 Router::add('GET', '/profile', UserController::class, 'showEditProfile', [UserAuthMiddleware::class]);
 Router::add('GET', '/profile/bookmark', BookmarkController::class, 'self', [UserAuthMiddleware::class]);
 Router::add('GET', '/profile/watchlist', WatchlistController::class, 'self', [UserAuthMiddleware::class]);