diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..120bd1401d51dd59127b173c426c65a96cd51571
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,141 @@
+# Created by https://www.toptal.com/developers/gitignore/api/rust,linux,windows,database,rust-analyzer,visualstudiocode,venv,dotenv,virtualenv,direnv,jenv,git
+# Edit at https://www.toptal.com/developers/gitignore?templates=rust,linux,windows,database,rust-analyzer,visualstudiocode,venv,dotenv,virtualenv,direnv,jenv,git
+
+### Database ###
+*.accdb
+*.db
+*.dbf
+*.mdb
+*.pdb
+*.sqlite3
+*.db-shm
+*.db-wal
+
+### direnv ###
+.direnv
+.envrc
+
+### dotenv ###
+.env
+
+### Git ###
+# Created by git for backups. To disable backups in Git:
+# $ git config --global mergetool.keepBackup false
+*.orig
+
+# Created by git when using merge tools for conflicts
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
+*_BACKUP_*.txt
+*_BASE_*.txt
+*_LOCAL_*.txt
+*_REMOTE_*.txt
+
+### JEnv ###
+# JEnv local Java version configuration file
+.java-version
+
+# Used by previous versions of JEnv
+.jenv-version
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Rust ###
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+
+### rust-analyzer ###
+# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules)
+rust-project.json
+
+
+### venv ###
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+.Python
+[Bb]in
+[Ii]nclude
+[Ll]ib
+[Ll]ib64
+[Ll]ocal
+[Ss]cripts
+pyvenv.cfg
+.venv
+pip-selfcheck.json
+
+### VirtualEnv ###
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/rust,linux,windows,database,rust-analyzer,visualstudiocode,venv,dotenv,virtualenv,direnv,jenv,git
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..8e619a44e0cab2494706ca6e9d4043a1c2155848
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "rust-analyzer.showUnlinkedFileNotification": false
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..a9cec78ebc19f493222554bc8e9bf65d4e5772cb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "bambangshop"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+rocket = { version = "0.5.0", features = ["json"] }
+dashmap = "5.5.3"
+lazy_static = "1.4.0"
+reqwest = { version = "0.12", features = ["json"] }
+getset = "0.1.2"
+dotenvy = "0.15.7"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3043f06d471f2933c5a98c71ad06b7d900d6812c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,83 @@
+# BambangShop Publisher App
+Tutorial and Example for Advanced Programming 2024 - Faculty of Computer Science, Universitas Indonesia
+
+---
+
+## About this Project
+In this repository, we have provided you a REST (REpresentational State Transfer) API project using Rocket web framework.
+
+This project consists of four modules:
+1.  `controller`: this module contains handler functions used to receive request and send responses.
+    In Model-View-Controller (MVC) pattern, this is the Controller part.
+2.  `model`: this module contains structs that serve as data containers.
+    In MVC pattern, this is the Model part.
+3.  `service`: this module contains structs with business logic methods.
+    In MVC pattern, this is also the Model part.
+4.  `repository`: this module contains structs that serve as databases and methods to access the databases.
+    You can use methods of the struct to get list of objects, or operating an object (create, read, update, delete).
+
+This repository provides a basic functionality that makes BambangShop work: ability to create, read, and delete `Product`s.
+This repository already contains a functioning `Product` model, repository, service, and controllers that you can try right away.
+
+As this is an Observer Design Pattern tutorial repository, you need to implement another feature: `Notification`.
+This feature will notify creation, promotion, and deletion of a product, to external subscribers that are interested of a certain product type.
+The subscribers are another Rocket instances, so the notification will be sent using HTTP POST request to each subscriber's `receive notification` address.
+
+## API Documentations
+
+You can download the Postman Collection JSON here: https://ristek.link/AdvProgWeek7Postman
+
+After you download the Postman Collection, you can try the endpoints inside "BambangShop Publisher" folder.
+This Postman collection also contains endpoints that you need to implement later on (the `Notification` feature).
+
+Postman is an installable client that you can use to test web endpoints using HTTP request.
+You can also make automated functional testing scripts for REST API projects using this client.
+You can install Postman via this website: https://www.postman.com/downloads/
+
+## How to Run in Development Environment
+1.  Set up environment variables first by creating `.env` file.
+    Here is the example of `.env` file:
+    ```bash
+    APP_INSTANCE_ROOT_URL="http://localhost:8000"
+    ```
+    Here are the details of each environment variable:
+    | variable              | type   | description                                                |
+    |-----------------------|--------|------------------------------------------------------------|
+    | APP_INSTANCE_ROOT_URL | string | URL address where this publisher instance can be accessed. |
+2.  Use `cargo run` to run this app.
+    (You might want to use `cargo check` if you only need to verify your work without running the app.)
+
+## Mandatory Checklists (Publisher)
+-   [ ] Clone https://gitlab.com/ichlaffterlalu/bambangshop to a new repository.
+-   **STAGE 1: Implement models and repositories**
+    -   [ ] Commit: `Create Subscriber model struct.`
+    -   [ ] Commit: `Create Notification model struct.`
+    -   [ ] Commit: `Create Subscriber database and Subscriber repository struct skeleton.`
+    -   [ ] Commit: `Implement add function in Subscriber repository.`
+    -   [ ] Commit: `Implement list_all function in Subscriber repository.`
+    -   [ ] Commit: `Implement delete function in Subscriber repository.`
+    -   [ ] Write answers of your learning module's "Reflection Publisher-1" questions in this README.
+-   **STAGE 2: Implement services and controllers**
+    -   [ ] Commit: `Create Notification service struct skeleton.`
+    -   [ ] Commit: `Implement subscribe function in Notification service.`
+    -   [ ] Commit: `Implement subscribe function in Notification controller.`
+    -   [ ] Commit: `Implement unsubscribe function in Notification service.`
+    -   [ ] Commit: `Implement unsubscribe function in Notification controller.`
+    -   [ ] Write answers of your learning module's "Reflection Publisher-2" questions in this README.
+-   **STAGE 3: Implement notification mechanism**
+    -   [ ] Commit: `Implement update method in Subscriber model to send notification HTTP requests.`
+    -   [ ] Commit: `Implement notify function in Notification service to notify each Subscriber.`
+    -   [ ] Commit: `Implement publish function in Program service and Program controller.`
+    -   [ ] Commit: `Edit Product service methods to call notify after create/delete.`
+    -   [ ] Write answers of your learning module's "Reflection Publisher-3" questions in this README.
+
+## Your Reflections
+This is the place for you to write reflections:
+
+### Mandatory (Publisher) Reflections
+
+#### Reflection Publisher-1
+
+#### Reflection Publisher-2
+
+#### Reflection Publisher-3
diff --git a/Rocket.toml b/Rocket.toml
new file mode 100644
index 0000000000000000000000000000000000000000..e98afca35809cfb27d3ef162528d758b291c78fc
--- /dev/null
+++ b/Rocket.toml
@@ -0,0 +1,7 @@
+[debug]
+address = "127.0.0.1"
+port = 8000
+
+[release]
+address = "0.0.0.0"
+port = 8000
diff --git a/src/controller/mod.rs b/src/controller/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..861f657d127990136aaeda07c46f9cb78a73ac78
--- /dev/null
+++ b/src/controller/mod.rs
@@ -0,0 +1,10 @@
+pub mod product;
+
+use rocket::fairing::AdHoc;
+
+pub fn route_stage() -> AdHoc {
+    return AdHoc::on_ignite("Initializing controller routes...", |rocket| async {
+        rocket
+            .mount("/product", routes![product::create, product::list, product::read, product::delete])
+    });
+}
diff --git a/src/controller/product.rs b/src/controller/product.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1cc538419d9a5a7f6b73e520417c04426fefbb3b
--- /dev/null
+++ b/src/controller/product.rs
@@ -0,0 +1,39 @@
+use rocket::response::status::Created;
+use rocket::serde::json::Json;
+
+use bambangshop::Result;
+use crate::model::product::Product;
+use crate::service::product::ProductService;
+
+
+#[post("/", data = "<product>")]
+pub fn create(product: Json<Product>) -> Result<Created<Json<Product>>> {
+    return match ProductService::create(product.into_inner()) {
+        Ok(f) => Ok(Created::new("/").body(Json::from(f))),
+        Err(e) => Err(e)
+    };
+}
+
+#[get("/")]
+pub fn list() -> Result<Json<Vec<Product>>> {
+    return match ProductService::list() {
+        Ok(f) => Ok(Json::from(f)),
+        Err(e) => Err(e)
+    };
+}
+
+#[get("/<id>")]
+pub fn read(id: usize) -> Result<Json<Product>> {
+    return match ProductService::read(id) {
+        Ok(f) => Ok(Json::from(f)),
+        Err(e) => Err(e)
+    };
+}
+
+#[delete("/<id>")]
+pub fn delete(id: usize) -> Result<Json<Product>> {
+    return match ProductService::delete(id) {
+        Ok(f) => Ok(Json::from(f)),
+        Err(e) => Err(e)
+    };
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..47e876a86aa022703f1010b4f9e8fbac33ce5d9e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,58 @@
+use lazy_static::lazy_static;
+use dotenvy::dotenv;
+use getset::Getters;
+use rocket::figment::{Figment, providers::{Serialized, Env}};
+use rocket::http::Status;
+use rocket::serde::json::Json;
+use rocket::serde::{Deserialize, Serialize};
+use rocket::response::status::Custom;
+use reqwest::{Client, ClientBuilder};
+
+lazy_static! {
+    pub static ref REQWEST_CLIENT: Client = ClientBuilder::new().build().unwrap();
+    pub static ref APP_CONFIG: AppConfig = AppConfig::generate();
+}
+
+#[derive(Debug, Deserialize, Serialize, Getters)]
+#[serde(crate = "rocket::serde")]
+pub struct AppConfig {
+    #[getset(get = "pub with_prefix")]
+    instance_root_url: String
+}
+
+impl Default for AppConfig {
+    fn default() -> AppConfig {
+        return AppConfig {
+            instance_root_url: String::from("http://localhost:8001")
+        }
+    }
+}
+
+impl AppConfig {
+    pub fn generate() -> AppConfig {
+        dotenv().ok();
+        return Figment::from(Serialized::defaults(AppConfig::default()))
+            .merge(Env::prefixed("APP_").global())
+            .extract().unwrap();
+    }
+}
+
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+pub type Error = Custom<Json<ErrorResponse>>;
+
+#[derive(Serialize, Debug, Clone, PartialEq)]
+#[serde(crate = "rocket::serde")]
+pub struct ErrorResponse {
+    pub status_code: Status,
+    pub message: String
+}
+
+pub fn compose_error_response(status_code: Status, message: String) -> Custom<Json<ErrorResponse>> {
+    return Custom(status_code, Json::from(
+        ErrorResponse {
+            status_code: status_code,
+            message: message,
+        }
+    ));
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cde1723ebb229183e0a3877f2c0e4a712674f92a
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,17 @@
+#[macro_use] extern crate rocket;
+
+pub mod controller;
+pub mod service;
+pub mod repository;
+pub mod model;
+
+use dotenvy::dotenv;
+use crate::controller::route_stage;
+
+#[launch]
+fn rocket() -> _ {
+    dotenv().ok();
+    rocket::build()
+        .manage(reqwest::Client::builder().build().unwrap())
+        .attach(route_stage())
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..68024e148492337f0e30a98004247f4755ad48b6
--- /dev/null
+++ b/src/model/mod.rs
@@ -0,0 +1 @@
+pub mod product;
diff --git a/src/model/product.rs b/src/model/product.rs
new file mode 100644
index 0000000000000000000000000000000000000000..260a68a633c03b4c9fc8aafc415c380b1befda65
--- /dev/null
+++ b/src/model/product.rs
@@ -0,0 +1,20 @@
+use rocket::serde::{Serialize, Deserialize};
+
+use bambangshop::APP_CONFIG;
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(crate = "rocket::serde")]
+pub struct Product {
+    #[serde(skip_deserializing)]
+    pub id: usize,
+    pub title: String,
+    pub description: String,
+    pub price: f64,
+    pub product_type: String,
+}
+
+impl Product {
+    pub fn get_url(&self) -> String {
+        return format!("{}/product/{}", APP_CONFIG.get_instance_root_url(), self.id);
+    }
+}
diff --git a/src/repository/mod.rs b/src/repository/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..68024e148492337f0e30a98004247f4755ad48b6
--- /dev/null
+++ b/src/repository/mod.rs
@@ -0,0 +1 @@
+pub mod product;
diff --git a/src/repository/product.rs b/src/repository/product.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bfe55c281ad15947aa1bdb39b74b334046709a01
--- /dev/null
+++ b/src/repository/product.rs
@@ -0,0 +1,39 @@
+use dashmap::DashMap;
+use lazy_static::lazy_static;
+use crate::model::product::Product;
+
+// Singleton of Database
+lazy_static! {
+    static ref PRODUCTS: DashMap<usize, Product> = DashMap::new();
+}
+
+pub struct ProductRepository;
+
+impl ProductRepository {
+    pub fn add(mut product: Product) -> Product {
+        product.id = PRODUCTS.len();
+        let product_value = product.clone();
+        PRODUCTS.insert(product_value.id, product_value);
+        return product;
+    }
+
+    pub fn list_all() -> Vec<Product> {
+        return PRODUCTS.iter().map(|f| f.value().clone()).collect();
+    }
+
+    pub fn get_by_id(id: usize) -> Option<Product> {
+        let result = PRODUCTS.get(&id);
+        if !result.is_none() {
+            return Some(result.unwrap().clone());
+        }
+        return None;
+    }
+
+    pub fn delete(id: usize) -> Option<Product> {
+        let result = PRODUCTS.remove(&id);
+        if !result.is_none() {
+            return Some(result.unwrap().1);
+        }
+        return None;
+    }
+}
diff --git a/src/service/mod.rs b/src/service/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..68024e148492337f0e30a98004247f4755ad48b6
--- /dev/null
+++ b/src/service/mod.rs
@@ -0,0 +1 @@
+pub mod product;
diff --git a/src/service/product.rs b/src/service/product.rs
new file mode 100644
index 0000000000000000000000000000000000000000..775bf09d1d171391547c61071a0f17c976131036
--- /dev/null
+++ b/src/service/product.rs
@@ -0,0 +1,45 @@
+use rocket::http::Status;
+use rocket::serde::json::Json;
+
+use bambangshop::{Result, compose_error_response};
+use crate::model::product::Product;
+use crate::repository::product::ProductRepository;
+
+pub struct ProductService;
+
+impl ProductService {
+    pub fn create(mut product: Product) -> Result<Product> {
+        product.product_type = product.product_type.to_uppercase();
+        let product_result: Product = ProductRepository::add(product);
+
+        return Ok(product_result);
+    }
+
+    pub fn list() -> Result<Vec<Product>> {
+        return Ok(ProductRepository::list_all());
+    }
+
+    pub fn read(id: usize) -> Result<Product> {
+        let product_opt: Option<Product> = ProductRepository::get_by_id(id);
+        if product_opt.is_none() {
+            return Err(compose_error_response(
+                Status::NotFound,
+                String::from("Product not found.")
+            ));
+        }
+        return Ok(product_opt.unwrap());
+    }
+
+    pub fn delete(id: usize) -> Result<Json<Product>> {
+        let product_opt: Option<Product> = ProductRepository::delete(id);
+        if product_opt.is_none() {
+            return Err(compose_error_response(
+                Status::NotFound,
+                String::from("Product not found.")
+            ));
+        }
+        let product: Product = product_opt.unwrap();
+
+        return Ok(Json::from(product));
+    }
+}