From d39b24a8a295107fe92898572c15ef00b938cc7d Mon Sep 17 00:00:00 2001
From: thuan2172001 <thuan2172001@gmail.com>
Date: Wed, 13 Mar 2024 22:22:38 +0700
Subject: [PATCH] fix: docker flow

---
 .github/workflows/ci.yaml                     |  2 -
 .github/workflows/docker.yml                  | 17 +++++
 Cargo.lock                                    | 73 +++++++++----------
 Cargo.toml                                    |  2 +-
 Dockerfile                                    |  1 +
 src/adapter/Cargo.toml                        |  8 +-
 src/adapter/src/repositories/grpc/config.rs   |  6 ++
 src/adapter/src/repositories/grpc/mod.rs      |  2 +
 .../repositories/grpc/models}/gpt_answer.rs   | 17 ++---
 .../src/repositories/grpc/models/mod.rs       |  1 +
 src/adapter/src/repositories/mod.rs           |  1 +
 .../repositories/postgres/models/question.rs  | 32 +++++---
 .../src/repositories/postgres/question_db.rs  | 14 ++--
 src/grpc/Cargo.toml                           | 16 +---
 src/grpc/build.rs                             |  2 +-
 src/grpc/src/grpc_server/mod.rs               |  1 -
 src/grpc/src/interfaces/gpt_answer.rs         |  3 +
 src/grpc/src/interfaces/mod.rs                |  1 +
 src/grpc/src/lib.rs                           |  3 +-
 src/grpc_server/Cargo.toml                    | 66 +++++++++++++++++
 src/grpc_server/build.rs                      | 57 +++++++++++++++
 src/grpc_server/config/00-default.toml        |  5 ++
 .../src/controllers}/gpt_answer.rs            | 21 +++---
 .../src/controllers}/mod.rs                   |  0
 src/grpc_server/src/lib.rs                    |  2 +
 src/grpc_server/src/main.rs                   | 62 ++++++++++++++++
 src/grpc_server/src/options.rs                | 34 +++++++++
 src/public/Cargo.toml                         |  2 +-
 src/public/src/controllers/question.rs        | 27 ++++---
 src/public/src/main.rs                        |  6 +-
 30 files changed, 370 insertions(+), 114 deletions(-)
 create mode 100644 .github/workflows/docker.yml
 create mode 100644 src/adapter/src/repositories/grpc/config.rs
 create mode 100644 src/adapter/src/repositories/grpc/mod.rs
 rename src/{grpc/src/grpc_client => adapter/src/repositories/grpc/models}/gpt_answer.rs (65%)
 create mode 100644 src/adapter/src/repositories/grpc/models/mod.rs
 delete mode 100644 src/grpc/src/grpc_server/mod.rs
 create mode 100644 src/grpc/src/interfaces/gpt_answer.rs
 create mode 100644 src/grpc/src/interfaces/mod.rs
 create mode 100644 src/grpc_server/Cargo.toml
 create mode 100644 src/grpc_server/build.rs
 create mode 100644 src/grpc_server/config/00-default.toml
 rename src/{grpc/src/grpc_server => grpc_server/src/controllers}/gpt_answer.rs (62%)
 rename src/{grpc/src/grpc_client => grpc_server/src/controllers}/mod.rs (100%)
 create mode 100644 src/grpc_server/src/lib.rs
 create mode 100644 src/grpc_server/src/main.rs
 create mode 100644 src/grpc_server/src/options.rs

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8760d68..2ab124b 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -65,7 +65,6 @@ jobs:
     runs-on: ubuntu-latest
     if: github.event_name == 'push' && github.ref == 'refs/heads/main'
     needs: [cargo-check, fmt-check, test-and-coverage]
-    # needs: [fmt-check]
     steps:
       - name: Check out from Git
         uses: actions/checkout@v4
@@ -103,7 +102,6 @@ jobs:
     runs-on: ubuntu-latest
     if: github.event_name == 'push' && github.ref == 'refs/heads/main'
     needs: [cargo-check, fmt-check, test-and-coverage]
-    # needs: [fmt-check]
     steps:
       - name: Check out from Git
         uses: actions/checkout@v4
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..1b3580b
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,17 @@
+name: Docker build
+
+on:
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  build-docker-image:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out from Git
+        uses: actions/checkout@v4
+
+      - name: Build docker image
+        run: |
+          docker build .
diff --git a/Cargo.lock b/Cargo.lock
index 5db657a..5c7640e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,10 +11,12 @@ dependencies = [
  "deadpool-diesel",
  "diesel",
  "diesel_migrations",
+ "grpc_interface",
  "rust_core",
  "serde",
  "testcontainers-modules",
  "tokio",
+ "tonic",
 ]
 
 [[package]]
@@ -104,9 +106,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
 [[package]]
 name = "async-stream"
@@ -147,15 +149,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
-[[package]]
-name = "autotools"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aef8da1805e028a172334c3b680f93e71126f2327622faef2ec3d893c0a4ad77"
-dependencies = [
- "cc",
-]
-
 [[package]]
 name = "axum"
 version = "0.6.20"
@@ -337,7 +330,7 @@ dependencies = [
  "deadpool-diesel",
  "diesel",
  "diesel_migrations",
- "grpc_client",
+ "grpc_interface",
  "openssl",
  "opentelemetry",
  "rand",
@@ -391,7 +384,7 @@ dependencies = [
  "rust-ini",
  "serde",
  "serde_json",
- "toml 0.8.10",
+ "toml 0.8.11",
  "yaml-rust",
 ]
 
@@ -828,16 +821,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
-name = "grpc_client"
+name = "grpc_interface"
 version = "0.0.1"
 dependencies = [
- "once_cell",
  "prost",
- "prost-build",
- "protobuf-src",
+ "tonic",
+ "tonic-build",
+]
+
+[[package]]
+name = "grpc_server"
+version = "0.0.1"
+dependencies = [
+ "clap",
+ "common",
+ "grpc_interface",
+ "opentelemetry",
+ "prost",
+ "readonly",
  "rust_core",
+ "serde",
+ "serde_json",
+ "tokio",
  "tonic",
  "tonic-build",
+ "tracing",
 ]
 
 [[package]]
@@ -1584,9 +1592,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
 dependencies = [
  "unicode-ident",
 ]
@@ -1645,15 +1653,6 @@ dependencies = [
  "prost",
 ]
 
-[[package]]
-name = "protobuf-src"
-version = "1.1.0+21.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7ac8852baeb3cc6fb83b93646fb93c0ffe5d14bf138c945ceb4b9948ee0e3c1"
-dependencies = [
- "autotools",
-]
-
 [[package]]
 name = "quote"
 version = "1.0.35"
@@ -2072,18 +2071,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2246,14 +2245,14 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.10"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
+checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e"
 dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.22.6",
+ "toml_edit 0.22.7",
 ]
 
 [[package]]
@@ -2280,9 +2279,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.6"
+version = "0.22.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
+checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992"
 dependencies = [
  "indexmap 2.2.5",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index 49a5ae3..25646c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,3 @@
 [workspace]
-members = ["src/public"]
+members = ["src/public", "src/grpc_server"]
 resolver = "2"
diff --git a/Dockerfile b/Dockerfile
index aa0966e..7baa79a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,7 @@
 FROM clux/muslrust:stable AS chef
 USER root
 RUN cargo install cargo-chef
+
 WORKDIR /app
 
 FROM clux/muslrust:stable AS bunyan
diff --git a/src/adapter/Cargo.toml b/src/adapter/Cargo.toml
index 021a25f..d04f690 100644
--- a/src/adapter/Cargo.toml
+++ b/src/adapter/Cargo.toml
@@ -42,4 +42,10 @@ version = "1.36.0"
 features = ["full"]
 
 [dependencies.anyhow]
-version = "1.0.80"
\ No newline at end of file
+version = "1.0.80"
+
+[dependencies.tonic]
+version = "0.11.0"
+
+[dependencies.grpc_interface]
+path = "../grpc"
diff --git a/src/adapter/src/repositories/grpc/config.rs b/src/adapter/src/repositories/grpc/config.rs
new file mode 100644
index 0000000..2bba061
--- /dev/null
+++ b/src/adapter/src/repositories/grpc/config.rs
@@ -0,0 +1,6 @@
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct ClientConfig {
+    pub url: String,
+}
diff --git a/src/adapter/src/repositories/grpc/mod.rs b/src/adapter/src/repositories/grpc/mod.rs
new file mode 100644
index 0000000..c00f297
--- /dev/null
+++ b/src/adapter/src/repositories/grpc/mod.rs
@@ -0,0 +1,2 @@
+pub mod models;
+pub mod config;
\ No newline at end of file
diff --git a/src/grpc/src/grpc_client/gpt_answer.rs b/src/adapter/src/repositories/grpc/models/gpt_answer.rs
similarity index 65%
rename from src/grpc/src/grpc_client/gpt_answer.rs
rename to src/adapter/src/repositories/grpc/models/gpt_answer.rs
index b07a665..4e42922 100644
--- a/src/grpc/src/grpc_client/gpt_answer.rs
+++ b/src/adapter/src/repositories/grpc/models/gpt_answer.rs
@@ -1,11 +1,9 @@
 use rust_core::common::errors::CoreError;
 use tonic::transport::Channel;
 
-use gpt_answer::gpt_answer_service_client::GptAnswerServiceClient;
-
-mod gpt_answer {
-    tonic::include_proto!("gpt_answer");
-}
+use grpc_interface::interfaces::gpt_answer::gpt_answer::{
+    gpt_answer_service_client::GptAnswerServiceClient, GetAnswerPayload,
+};
 
 pub struct GptAnswerGrpcClient {
     client: GptAnswerServiceClient<Channel>,
@@ -17,10 +15,9 @@ impl GptAnswerGrpcClient {
         Self { client }
     }
 
-    pub async fn get_instance() -> Result<Self, CoreError> {
-        let uri = "http://0.0.0.0:50051";
+    pub async fn get_instance(uri: &'static str) -> Result<Self, CoreError> {
         let channel = Channel::from_static(uri).connect().await.map_err(|err| {
-            println!("Error connecting to GPT: {:?}", err);
+            eprintln!("Error connecting to GPT: {:?}", err);
             CoreError::InternalError
         })?;
 
@@ -29,12 +26,12 @@ impl GptAnswerGrpcClient {
     }
 
     pub async fn get_answer(&mut self, question: &str) -> Result<String, CoreError> {
-        let request = tonic::Request::new(gpt_answer::GetAnswerPayload {
+        let request = tonic::Request::new(GetAnswerPayload {
             question: question.to_string(),
         });
 
         let response = self.client.get_answer(request).await.map_err(|err| {
-            println!("Error getting answer from GPT: {:?}", err);
+            eprintln!("Error getting answer from GPT: {:?}", err);
             CoreError::InternalError
         })?;
 
diff --git a/src/adapter/src/repositories/grpc/models/mod.rs b/src/adapter/src/repositories/grpc/models/mod.rs
new file mode 100644
index 0000000..21cbc95
--- /dev/null
+++ b/src/adapter/src/repositories/grpc/models/mod.rs
@@ -0,0 +1 @@
+pub mod gpt_answer;
\ No newline at end of file
diff --git a/src/adapter/src/repositories/mod.rs b/src/adapter/src/repositories/mod.rs
index 812478a..8976b0a 100644
--- a/src/adapter/src/repositories/mod.rs
+++ b/src/adapter/src/repositories/mod.rs
@@ -1,3 +1,4 @@
 pub mod in_memory;
 pub mod postgres;
 pub mod repository_test;
+pub mod grpc;
\ No newline at end of file
diff --git a/src/adapter/src/repositories/postgres/models/question.rs b/src/adapter/src/repositories/postgres/models/question.rs
index 2b3a234..93e804c 100644
--- a/src/adapter/src/repositories/postgres/models/question.rs
+++ b/src/adapter/src/repositories/postgres/models/question.rs
@@ -1,8 +1,10 @@
-use std::time::SystemTime;
+use std::{
+    io::{Error, ErrorKind},
+    time::SystemTime,
+};
 
 use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
-use rust_core::common::errors::CoreError;
-use rust_core::entities::question::QuestionEntity;
+use rust_core::entities::question::{QuestionEntity, QuestionId};
 use serde::Serialize;
 
 #[derive(Debug, Queryable, Serialize, Selectable, Insertable, AsChangeset, Identifiable)]
@@ -20,23 +22,33 @@ pub struct QuestionModel {
     pub created_on: SystemTime,
 }
 
-impl QuestionModel {
-    pub fn from(entity: QuestionEntity) -> Result<Self, CoreError> {
+impl TryFrom<QuestionEntity> for QuestionModel {
+    type Error = Error;
+
+    fn try_from(entity: QuestionEntity) -> Result<QuestionModel, Self::Error> {
+        let id = entity
+            .id
+            .0
+            .parse()
+            .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid ID"))?;
+
         Ok(QuestionModel {
-            id: entity.id.to_string().parse()?,
+            id,
             title: entity.title,
             content: entity.content,
             tags: entity.tags.map(|v| v.into_iter().map(Some).collect()),
             created_on: SystemTime::now(),
         })
     }
+}
 
-    pub fn to_entity(self) -> Result<QuestionEntity, CoreError> {
-        Ok(QuestionEntity {
-            id: self.id.to_string().parse()?,
+impl Into<QuestionEntity> for QuestionModel {
+    fn into(self) -> QuestionEntity {
+        QuestionEntity {
+            id: QuestionId(self.id.to_string()),
             title: self.title,
             content: self.content,
             tags: self.tags.map(|v| v.into_iter().flatten().collect()),
-        })
+        }
     }
 }
diff --git a/src/adapter/src/repositories/postgres/question_db.rs b/src/adapter/src/repositories/postgres/question_db.rs
index 6099855..9b3b40d 100644
--- a/src/adapter/src/repositories/postgres/question_db.rs
+++ b/src/adapter/src/repositories/postgres/question_db.rs
@@ -37,7 +37,8 @@ impl QuestionPort for QuestionDBRepository {
             .await
             .unwrap()
             .interact(move |conn| {
-                let question = QuestionModel::from(question).unwrap();
+                let question =
+                    QuestionModel::try_from(question).map_err(|_| CoreError::InternalError)?;
                 let response = insert_into(questions)
                     .values(&question)
                     .get_result::<QuestionModel>(conn)
@@ -46,7 +47,7 @@ impl QuestionPort for QuestionDBRepository {
                         _ => CoreError::InternalError,
                     })
                     .unwrap();
-                Ok(response.to_entity().unwrap())
+                Ok(response.into())
             })
             .await
             .unwrap()
@@ -58,7 +59,8 @@ impl QuestionPort for QuestionDBRepository {
             .await
             .unwrap()
             .interact(move |conn| {
-                let question = QuestionModel::from(question)?;
+                let question =
+                    QuestionModel::try_from(question).map_err(|_| CoreError::InternalError)?;
                 let response = update(questions.filter(id.eq(question.id)))
                     .set(&question)
                     .get_result::<QuestionModel>(conn)
@@ -66,7 +68,7 @@ impl QuestionPort for QuestionDBRepository {
                         diesel::result::Error::NotFound => CoreError::NotFound,
                         _ => CoreError::InternalError,
                     })?
-                    .to_entity()?;
+                    .into();
 
                 Ok(response)
             })
@@ -109,7 +111,7 @@ impl QuestionPort for QuestionDBRepository {
                         diesel::result::Error::NotFound => CoreError::NotFound,
                         _ => CoreError::InternalError,
                     })?
-                    .to_entity()?;
+                    .into();
 
                 Ok(response)
             })
@@ -136,7 +138,7 @@ impl QuestionPort for QuestionDBRepository {
 
                 Ok(question_list
                     .into_iter()
-                    .map(|l| l.to_entity().unwrap())
+                    .map(|l| l.into())
                     .collect::<Vec<_>>())
             })
             .await
diff --git a/src/grpc/Cargo.toml b/src/grpc/Cargo.toml
index f5aa19b..5f16274 100644
--- a/src/grpc/Cargo.toml
+++ b/src/grpc/Cargo.toml
@@ -4,7 +4,7 @@ test = []
 example = []
 
 [package]
-name = "grpc_client"
+name = "grpc_interface"
 edition = "2021"
 version = "0.0.1"
 autobins = true
@@ -15,28 +15,16 @@ autobenches = true
 [dependencies.tonic]
 version = "0.11.0"
 
-[dependencies.once_cell]
-version = "1.8.0"
-
 [dependencies.prost]
 version = "0.12.3"
 
-[dependencies.prost-build]
-version = "0.12.3"
-
-[dependencies.protobuf-src]
-version = "1.1.0"
-
-[dependencies.rust_core]
-path = "../core"
-
 [build-dependencies]
 tonic-build = "0.11.0"
 
 
 [lib]
 path = "src/lib.rs"
-name = "grpc_client"
+name = "grpc_interface"
 test = true
 doctest = true
 bench = true
diff --git a/src/grpc/build.rs b/src/grpc/build.rs
index 44d03b1..a019efa 100644
--- a/src/grpc/build.rs
+++ b/src/grpc/build.rs
@@ -1,4 +1,4 @@
 fn main() -> Result<(), Box<dyn std::error::Error>> {
-    tonic_build::compile_protos("proto/gpt_answer.proto")?;
+    tonic_build::compile_protos("proto/")?;
     Ok(())
 }
diff --git a/src/grpc/src/grpc_server/mod.rs b/src/grpc/src/grpc_server/mod.rs
deleted file mode 100644
index bec2060..0000000
--- a/src/grpc/src/grpc_server/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod gpt_answer;
diff --git a/src/grpc/src/interfaces/gpt_answer.rs b/src/grpc/src/interfaces/gpt_answer.rs
new file mode 100644
index 0000000..ef738af
--- /dev/null
+++ b/src/grpc/src/interfaces/gpt_answer.rs
@@ -0,0 +1,3 @@
+pub mod gpt_answer {
+    tonic::include_proto!("gpt_answer");
+}
diff --git a/src/grpc/src/interfaces/mod.rs b/src/grpc/src/interfaces/mod.rs
new file mode 100644
index 0000000..21cbc95
--- /dev/null
+++ b/src/grpc/src/interfaces/mod.rs
@@ -0,0 +1 @@
+pub mod gpt_answer;
\ No newline at end of file
diff --git a/src/grpc/src/lib.rs b/src/grpc/src/lib.rs
index acb46d2..7f397c5 100644
--- a/src/grpc/src/lib.rs
+++ b/src/grpc/src/lib.rs
@@ -1,2 +1 @@
-pub mod grpc_client;
-pub mod grpc_server;
+pub mod interfaces;
\ No newline at end of file
diff --git a/src/grpc_server/Cargo.toml b/src/grpc_server/Cargo.toml
new file mode 100644
index 0000000..d6a048b
--- /dev/null
+++ b/src/grpc_server/Cargo.toml
@@ -0,0 +1,66 @@
+bin = []
+bench = []
+test = []
+example = []
+
+[package]
+name = "grpc_server"
+edition = "2021"
+version = "0.0.1"
+autobins = true
+autoexamples = true
+autotests = true
+autobenches = true
+
+[dependencies.tonic]
+version = "0.11.0"
+
+[dependencies.prost]
+version = "0.12.3"
+
+[dependencies.rust_core]
+path = "../core"
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+
+[dependencies.clap]
+version = "4.4.7"
+features = ["derive"]
+
+[dependencies.tokio]
+version = "1.36.0"
+features = ["full"]
+
+[dependencies.opentelemetry]
+version = "0.22.0"
+
+[dependencies]
+serde_json = "1.0"
+readonly = "0.2.12"
+tracing = "0.1"
+
+[dependencies.common]
+path = "../common"
+
+[dependencies.grpc_interface]
+path = "../grpc"
+
+[build-dependencies]
+tonic-build = "0.11.0"
+
+
+[lib]
+path = "src/lib.rs"
+name = "grpc_server"
+test = true
+doctest = true
+bench = true
+doc = true
+plugin = false
+proc-macro = false
+harness = true
+edition = "2021"
+required-features = []
+crate-type = ["rlib"]
diff --git a/src/grpc_server/build.rs b/src/grpc_server/build.rs
new file mode 100644
index 0000000..3c01894
--- /dev/null
+++ b/src/grpc_server/build.rs
@@ -0,0 +1,57 @@
+use std::{borrow::Cow, env, process::Command};
+
+/// Generate the `cargo:` key output
+pub fn generate_cargo_keys() {
+    let output = Command::new("git")
+        .args(["rev-parse", "--short", "HEAD"])
+        .output();
+
+    let commit = match output {
+        Ok(o) if o.status.success() => {
+            let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned();
+            Cow::from(sha)
+        }
+        Ok(o) => {
+            println!("cargo:warning=Git command failed with status: {}", o.status);
+            Cow::from("unknown")
+        }
+        Err(err) => {
+            println!("cargo:warning=Failed to execute git command: {}", err);
+            Cow::from("unknown")
+        }
+    };
+
+    println!("cargo:rustc-env=APP_VERSION={}", get_version(&commit))
+}
+
+fn get_platform() -> String {
+    let env_dash = if env::var("CARGO_CFG_TARGET_ENV").unwrap().is_empty() {
+        ""
+    } else {
+        "-"
+    };
+
+    format!(
+        "{}-{}{}{}",
+        env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
+        env::var("CARGO_CFG_TARGET_OS").unwrap(),
+        env_dash,
+        env::var("CARGO_CFG_TARGET_ENV").unwrap_or(String::from("")),
+    )
+}
+
+fn get_version(impl_commit: &str) -> String {
+    let commit_dash = if impl_commit.is_empty() { "" } else { "-" };
+
+    format!(
+        "{}{}{}-{}",
+        std::env::var("CARGO_PKG_VERSION").unwrap_or_default(),
+        commit_dash,
+        impl_commit,
+        get_platform(),
+    )
+}
+
+pub fn main() {
+    generate_cargo_keys();
+}
diff --git a/src/grpc_server/config/00-default.toml b/src/grpc_server/config/00-default.toml
new file mode 100644
index 0000000..1f4048f
--- /dev/null
+++ b/src/grpc_server/config/00-default.toml
@@ -0,0 +1,5 @@
+service_name = "rust-grpc-server"
+exporter_endpoint = "http://localhost:7281"
+
+[servers.gpt_answer_service]
+url = "0.0.0.0:50051"
\ No newline at end of file
diff --git a/src/grpc/src/grpc_server/gpt_answer.rs b/src/grpc_server/src/controllers/gpt_answer.rs
similarity index 62%
rename from src/grpc/src/grpc_server/gpt_answer.rs
rename to src/grpc_server/src/controllers/gpt_answer.rs
index 61f9c61..7aae3d4 100644
--- a/src/grpc/src/grpc_server/gpt_answer.rs
+++ b/src/grpc_server/src/controllers/gpt_answer.rs
@@ -1,10 +1,11 @@
-use gpt_answer::gpt_answer_service_server::{GptAnswerService, GptAnswerServiceServer};
-use gpt_answer::{GetAnswerPayload, GetAnswerResponse};
+use grpc_interface::interfaces::gpt_answer::gpt_answer::gpt_answer_service_server::{
+    GptAnswerService, GptAnswerServiceServer,
+};
+use grpc_interface::interfaces::gpt_answer::gpt_answer::{GetAnswerPayload, GetAnswerResponse};
+use rust_core::common::errors::CoreError;
 use tonic::{transport::Server, Request, Response, Status};
 
-mod gpt_answer {
-    tonic::include_proto!("gpt_answer");
-}
+use crate::options::Options;
 
 #[derive(Debug, Default)]
 pub struct GptAnswerServer;
@@ -24,9 +25,11 @@ impl GptAnswerService for GptAnswerServer {
     }
 }
 
-pub async fn init_gpt_answer_server() {
-    let result = "0.0.0.0:50051".parse().map_err(|err| {
-        println!("Error: {:?}", err);
+pub async fn init_gpt_answer_server(options: Options) {
+    let gpt_answer_config = options.servers.gpt_answer_service.clone().unwrap();
+    let result = gpt_answer_config.url.parse().map_err(|err| {
+        eprintln!("Error: {:?}", err);
+        CoreError::InternalError
     });
 
     if result.is_ok() {
@@ -44,6 +47,6 @@ pub async fn init_gpt_answer_server() {
 
         println!("GPT Answer server started at {}", addr);
     } else {
-        println!("GPT Answer server failed to start");
+        eprintln!("GPT Answer server failed to start");
     }
 }
diff --git a/src/grpc/src/grpc_client/mod.rs b/src/grpc_server/src/controllers/mod.rs
similarity index 100%
rename from src/grpc/src/grpc_client/mod.rs
rename to src/grpc_server/src/controllers/mod.rs
diff --git a/src/grpc_server/src/lib.rs b/src/grpc_server/src/lib.rs
new file mode 100644
index 0000000..889f08d
--- /dev/null
+++ b/src/grpc_server/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod controllers;
+pub mod options;
\ No newline at end of file
diff --git a/src/grpc_server/src/main.rs b/src/grpc_server/src/main.rs
new file mode 100644
index 0000000..201752b
--- /dev/null
+++ b/src/grpc_server/src/main.rs
@@ -0,0 +1,62 @@
+use clap::{Parser, Subcommand};
+use grpc_server::{controllers, options};
+use opentelemetry::global;
+
+use common::loggers::telemetry::init_telemetry;
+use common::options::parse_options;
+use controllers::gpt_answer::init_gpt_answer_server;
+use options::Options;
+
+#[tokio::main]
+async fn main() {
+    let args = Args::parse();
+    if args.version {
+        println!(env!("APP_VERSION"));
+        return;
+    }
+
+    let options: Options = match parse_options(args.config_path) {
+        Ok(options) => options,
+        Err(err) => {
+            eprintln!("Failed to load config: {}", err);
+            return;
+        }
+    };
+
+    if let Some(Commands::Config) = args.command {
+        println!("{:#?}", options);
+        return;
+    }
+
+    init_telemetry(
+        options.service_name.as_str(),
+        options.exporter_endpoint.as_str(),
+        options.log.level.as_str(),
+    );
+
+    let gpt_answer_server = tokio::spawn(init_gpt_answer_server(options));
+
+    tokio::try_join!(gpt_answer_server).expect("Failed to run servers");
+
+    global::shutdown_tracer_provider();
+}
+
+/// Simple REST server.
+#[derive(Parser, Debug)]
+#[command(about, long_about = None)]
+struct Args {
+    #[command(subcommand)]
+    command: Option<Commands>,
+    /// Config file
+    #[arg(short, long, default_value = "config/00-default.toml")]
+    config_path: Vec<String>,
+    /// Print version
+    #[clap(short, long)]
+    version: bool,
+}
+
+#[derive(Subcommand, Clone, Debug)]
+enum Commands {
+    /// Print config
+    Config,
+}
diff --git a/src/grpc_server/src/options.rs b/src/grpc_server/src/options.rs
new file mode 100644
index 0000000..3b3aa81
--- /dev/null
+++ b/src/grpc_server/src/options.rs
@@ -0,0 +1,34 @@
+use serde::Deserialize;
+use common::options::{default_log, Log};
+
+/// Configuration options for the application.
+///
+/// This struct represents the configuration options for the application, including server settings,
+/// database configuration, endpoint for the exporter, service name, and logging configuration.
+#[readonly::make]
+#[derive(Deserialize, Debug)]
+pub struct Options {
+    /// Configuration for the servers.
+    pub servers: Servers,
+    /// The endpoint for the exporter.
+    pub exporter_endpoint: String,
+    /// The name of the service.
+    pub service_name: String,
+    /// Configuration for logging, including log level.
+    #[serde(default = "default_log")]
+    pub log: Log,
+}
+
+/// Represents servers configuration options.
+#[derive(Deserialize, Debug)]
+pub struct Servers {
+    /// Configuration for using in-memory database.
+    pub gpt_answer_service: Option<ServiceServer>,
+}
+
+/// Represents service server configuration.
+#[derive(Debug, Deserialize, Clone)]
+pub struct ServiceServer {
+    /// URL for the server.
+    pub url: String,
+}
diff --git a/src/public/Cargo.toml b/src/public/Cargo.toml
index 1c4b180..74b8a55 100644
--- a/src/public/Cargo.toml
+++ b/src/public/Cargo.toml
@@ -103,7 +103,7 @@ version = "1.0.57"
 [dependencies.anyhow]
 version = "1.0.80"
 
-[dependencies.grpc_client]
+[dependencies.grpc_interface]
 path = "../grpc"
 
 [lib]
diff --git a/src/public/src/controllers/question.rs b/src/public/src/controllers/question.rs
index 1f83d49..5a459c5 100644
--- a/src/public/src/controllers/question.rs
+++ b/src/public/src/controllers/question.rs
@@ -2,6 +2,7 @@ use std::collections::HashMap;
 use std::str::FromStr;
 use std::sync::Arc;
 
+use adapter::repositories::grpc::models::gpt_answer::GptAnswerGrpcClient;
 use tracing::instrument;
 use warp::http::StatusCode;
 use warp::reject::Rejection;
@@ -11,8 +12,6 @@ use rust_core::entities::question::{QuestionEntity, QuestionId};
 use rust_core::entities::question_filter::QuestionFilter;
 use rust_core::ports::question::QuestionPort;
 
-use grpc_client::grpc_client::gpt_answer::GptAnswerGrpcClient;
-
 use crate::errors::WarpError;
 
 /// Handler for retrieving questions based on query parameters.
@@ -25,12 +24,12 @@ pub async fn get_questions(
     question_port: Arc<dyn QuestionPort + Send + Sync>,
     query: HashMap<String, String>,
 ) -> Result<impl Reply, Rejection> {
-    let question_filter = QuestionFilter::from_query(&query).map_err(|err| WarpError::from(err))?;
+    let question_filter = QuestionFilter::from_query(&query).map_err(WarpError::from)?;
 
     let questions = question_port
         .list(&question_filter)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::json(&questions))
 }
@@ -45,12 +44,12 @@ pub async fn get_question(
     question_port: Arc<dyn QuestionPort + Send + Sync>,
     id: String,
 ) -> Result<impl Reply, Rejection> {
-    let question_id = QuestionId::from_str(id.as_str()).map_err(|err| WarpError::from(err))?;
+    let question_id = QuestionId::from_str(id.as_str()).map_err(WarpError::from)?;
 
     let question = question_port
         .get(&question_id)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::json(&question))
 }
@@ -68,7 +67,7 @@ pub async fn add_question(
     question_port
         .add(question)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::with_status("Question added", StatusCode::OK))
 }
@@ -83,12 +82,12 @@ pub async fn delete_question(
     question_port: Arc<dyn QuestionPort + Send + Sync>,
     id: String,
 ) -> Result<impl Reply, Rejection> {
-    let question_id = QuestionId::from_str(id.as_str()).map_err(|err| WarpError::from(err))?;
+    let question_id = QuestionId::from_str(id.as_str()).map_err(WarpError::from)?;
 
     question_port
         .delete(&question_id)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::with_status("Question deleted", StatusCode::OK))
 }
@@ -105,13 +104,13 @@ pub async fn update_question(
     id: String,
     mut question: QuestionEntity,
 ) -> Result<impl Reply, Rejection> {
-    let question_id = QuestionId::from_str(id.as_str()).map_err(|err| WarpError::from(err))?;
+    let question_id = QuestionId::from_str(id.as_str()).map_err(WarpError::from)?;
     question.id = question_id;
 
     question_port
         .update(question)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::with_status("Question updated", StatusCode::OK))
 }
@@ -141,15 +140,15 @@ pub async fn get_question_answer_controller(
     let question = question_port
         .get(&QuestionId::from_str(id.as_str()).unwrap())
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
-    let client = GptAnswerGrpcClient::get_instance().await;
+    let client = GptAnswerGrpcClient::get_instance("0.0.0.0:50051").await;
 
     let answer = client
         .unwrap()
         .get_answer(&question.content)
         .await
-        .map_err(|err| WarpError::from(err))?;
+        .map_err(WarpError::from)?;
 
     Ok(warp::reply::with_status(answer, StatusCode::OK))
 }
diff --git a/src/public/src/main.rs b/src/public/src/main.rs
index 50487ee..009a655 100644
--- a/src/public/src/main.rs
+++ b/src/public/src/main.rs
@@ -1,4 +1,3 @@
-#[rustfmt::skip]
 #[cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
 use openssl;
 #[rustfmt::skip]
@@ -23,8 +22,6 @@ use common::loggers::telemetry::init_telemetry;
 use common::options::parse_options;
 use rust_core::ports::question::QuestionPort;
 
-use grpc_client::grpc_server::gpt_answer::init_gpt_answer_server;
-
 #[tokio::main]
 async fn main() {
     let args = Args::parse();
@@ -52,9 +49,8 @@ async fn main() {
         options.log.level.as_str(),
     );
 
-    let grpc_server = tokio::spawn(init_gpt_answer_server());
     let warp_server = tokio::spawn(run_warp_server(options));
-    tokio::try_join!(grpc_server, warp_server).expect("Failed to run servers");
+    tokio::try_join!(warp_server).expect("Failed to run servers");
 
     global::shutdown_tracer_provider();
 }