diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb875b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +db/data/log diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..a1d8da4 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,34 @@ +# Use the official Go image as the base image +FROM golang:1.22-alpine AS builder + +# Set the working directory inside the container +WORKDIR /app + +# Cache Go modules +COPY go.mod ./ +RUN apk add --no-cache git +RUN go mod download + +# Copy the source code +COPY . . + +# Build the Go binary +RUN go build -o main . + +# ---- Runtime image ---- +FROM alpine:3.19 + +# Install ca-certificates (if needed) +RUN apk add --no-cache ca-certificates + +# Set workdir +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /app/main . + +# Expose the application port (default for the Go server) +EXPOSE 8080 + +# Command to run the binary +CMD ["./main"] diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..567a85e --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,11 @@ +module backend + +go 1.22 + +require github.com/denisenkom/go-mssqldb v0.12.3 + +require ( + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..d8de2dc --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,38 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/main b/backend/main new file mode 100644 index 0000000..06c74ba Binary files /dev/null and b/backend/main differ diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..319295a --- /dev/null +++ b/backend/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + + _ "github.com/denisenkom/go-mssqldb" +) + +func main() { + // Load DB connection settings from environment (set in docker‑compose) + server := os.Getenv("DB_SERVER") + user := os.Getenv("DB_USER") + password := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + + if server == "" || user == "" || password == "" || dbName == "" { + log.Fatalf("One or more DB environment variables are missing") + } + + // Build the connection string for go‑mssqldb + connStr := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s", user, password, server, dbName) + + // Open a connection pool + db, err := sql.Open("sqlserver", connStr) + if err != nil { + log.Fatalf("sql.Open failed: %v", err) + } + defer db.Close() + + // Verify the connection works + if err = db.Ping(); err != nil { + log.Fatalf("cannot ping database: %v", err) + } + fmt.Println("✅ Connected to MSSQL") + + // ----------------------------------------------------------------- + // 1️⃣ Insert a new customer via the stored procedure sp_InsertCustomer + // ----------------------------------------------------------------- + var newCustID int + insertCustSQL := ` +DECLARE @NewID int; +EXEC dbo.sp_InsertCustomer + @CustomerName = @name, + @AddressLine1 = @addr1, + @AddressLine2 = @addr2, + @City = @city, + @State = @state, + @ZipCode = @zip, + @Country = @country, + @NewCustomerID = @NewID OUTPUT; +SELECT @NewID; +` + row := db.QueryRow(insertCustSQL, + sql.Named("name", "Acme Corp"), + sql.Named("addr1", "123 Main St"), + sql.Named("addr2", nil), + sql.Named("city", "New York"), + sql.Named("state", "NY"), + sql.Named("zip", "10001"), + sql.Named("country", "USA"), + ) + if err = row.Scan(&newCustID); err != nil { + log.Fatalf("Insert customer failed: %v", err) + } + fmt.Printf("🆕 Inserted Customer ID: %d\n", newCustID) + + // ----------------------------------------------------------------- + // 2️⃣ Insert a contact for the new customer via sp_InsertContact + // ----------------------------------------------------------------- + var newContactID int + insertContactSQL := ` +DECLARE @NewContactID int; +EXEC dbo.sp_InsertContact + @CustomerID = @custID, + @ContactTypeID = @typeID, + @ContactValue = @value, + @IsPrimary = @primary, + @NewContactID = @NewContactID OUTPUT; +SELECT @NewContactID; +` + // Assume ContactTypeID 1 = Phone (created by init.sql) + row = db.QueryRow(insertContactSQL, + sql.Named("custID", newCustID), + sql.Named("typeID", 1), + sql.Named("value", "555‑1234"), + sql.Named("primary", 1), + ) + if err = row.Scan(&newContactID); err != nil { + log.Fatalf("Insert contact failed: %v", err) + } + fmt.Printf("📞 Inserted Contact ID: %d\n", newContactID) + + // ----------------------------------------------------------------- + // 3️⃣ Retrieve the customer via sp_GetCustomer + // ----------------------------------------------------------------- + var custName, addr1, addr2, city, state, zip, country string + getCustSQL := `EXEC dbo.sp_GetCustomer @CustomerID = @id` + err = db.QueryRow(getCustSQL, sql.Named("id", newCustID)).Scan( + &newCustID, &custName, &addr1, &addr2, &city, &state, &zip, &country, &sql.NullTime{}, &sql.NullTime{}) + if err != nil { + log.Fatalf("Get customer failed: %v", err) + } + fmt.Printf("🔎 Fetched Customer: %s, %s, %s\n", custName, city, state) + + // ----------------------------------------------------------------- + // 4️⃣ List contacts for the customer via sp_GetContactsByCustomer + // ----------------------------------------------------------------- + fmt.Println("📄 Contacts for the customer:") + rows, err := db.Query(`EXEC dbo.sp_GetContactsByCustomer @CustomerID = @id`, sql.Named("id", newCustID)) + if err != nil { + log.Fatalf("Get contacts failed: %v", err) + } + defer rows.Close() + for rows.Next() { + var contactID, custID, ctID int + var value string + var isPrimary bool + var typeName string + if err = rows.Scan(&contactID, &custID, &ctID, &value, &isPrimary, &sql.NullTime{}, &sql.NullTime{}, &typeName); err != nil { + log.Fatalf("Scanning contacts: %v", err) + } + fmt.Printf("- %s (%s) Primary:%v\n", value, typeName, isPrimary) + } + if err = rows.Err(); err != nil { + log.Fatalf("Row iteration error: %v", err) + } + + // ----------------------------------------------------------------- + // 5️⃣ Update the customer name via sp_UpdateCustomer + // ----------------------------------------------------------------- + _, err = db.Exec(`EXEC dbo.sp_UpdateCustomer + @CustomerID = @id, + @CustomerName = @name, + @AddressLine1 = @addr1, + @AddressLine2 = @addr2, + @City = @city, + @State = @state, + @ZipCode = @zip, + @Country = @country`, + sql.Named("id", newCustID), + sql.Named("name", "Acme Corp – Updated"), + sql.Named("addr1", "123 Main St"), + sql.Named("addr2", nil), + sql.Named("city", "New York"), + sql.Named("state", "NY"), + sql.Named("zip", "10001"), + sql.Named("country", "USA"), + ) + if err != nil { + log.Fatalf("Update customer failed: %v", err) + } + fmt.Println("✏️ Updated customer name") + + // ----------------------------------------------------------------- + // 6️⃣ Delete the contact and the customer (cleanup) + // ----------------------------------------------------------------- + _, err = db.Exec(`EXEC dbo.sp_DeleteContact @ContactID = @cid`, sql.Named("cid", newContactID)) + if err != nil { + log.Fatalf("Delete contact failed: %v", err) + } + fmt.Println("🗑️ Deleted contact") + + _, err = db.Exec(`EXEC dbo.sp_DeleteCustomer @CustomerID = @cid`, sql.Named("cid", newCustID)) + if err != nil { + log.Fatalf("Delete customer failed: %v", err) + } + fmt.Println("🗑️ Deleted customer") + + fmt.Println("✅ All CRUD operations completed successfully") +} diff --git a/db/data/.system/instance_id b/db/data/.system/instance_id new file mode 100644 index 0000000..cc932c0 --- /dev/null +++ b/db/data/.system/instance_id @@ -0,0 +1,3 @@ +59629046-e974-4356-ba63-5f0605022367 +9979554600398963173 +3457 \ No newline at end of file diff --git a/db/data/.system/system/debug/PASSWD.LOG b/db/data/.system/system/debug/PASSWD.LOG new file mode 100644 index 0000000..e69de29 diff --git a/db/data/.system/system/lsa.hiv b/db/data/.system/system/lsa.hiv new file mode 100644 index 0000000..778f151 Binary files /dev/null and b/db/data/.system/system/lsa.hiv differ diff --git a/db/data/.system/system/security.hiv b/db/data/.system/system/security.hiv new file mode 100644 index 0000000..eed91de Binary files /dev/null and b/db/data/.system/system/security.hiv differ diff --git a/db/init.sql b/db/init.sql new file mode 100644 index 0000000..96aba5f --- /dev/null +++ b/db/init.sql @@ -0,0 +1,223 @@ +-- init.sql: Create CustomerDB schema, tables, types, procedures, and views. + +-- ------------------------------------------------------------ +-- Create the database +-- ------------------------------------------------------------ +IF DB_ID(N'CustomerDB') IS NULL +BEGIN + CREATE DATABASE CustomerDB; +END +GO + +USE CustomerDB; +GO + +-- ------------------------------------------------------------ +-- Types / Lookup tables +-- ------------------------------------------------------------ +CREATE TABLE dbo.ContactTypes ( + ContactTypeID INT IDENTITY(1,1) PRIMARY KEY, + TypeName NVARCHAR(50) NOT NULL UNIQUE +); +GO + +INSERT INTO dbo.ContactTypes (TypeName) VALUES + (N'Phone'), + (N'Email'), + (N'Fax'), + (N'Website'); +GO + +-- ------------------------------------------------------------ +-- Core tables +-- ------------------------------------------------------------ +CREATE TABLE dbo.Customers ( + CustomerID INT IDENTITY(1,1) PRIMARY KEY, + CustomerName NVARCHAR(150) NOT NULL, + AddressLine1 NVARCHAR(200) NULL, + AddressLine2 NVARCHAR(200) NULL, + City NVARCHAR(100) NULL, + State NVARCHAR(100) NULL, + ZipCode NVARCHAR(20) NULL, + Country NVARCHAR(100) NULL, + CreatedAt DATETIME2(0) NOT NULL CONSTRAINT DF_Customers_CreatedAt DEFAULT (SYSDATETIME()), + UpdatedAt DATETIME2(0) NULL +); +GO + +CREATE TABLE dbo.Contacts ( + ContactID INT IDENTITY(1,1) PRIMARY KEY, + CustomerID INT NOT NULL, + ContactTypeID INT NOT NULL, + ContactValue NVARCHAR(200) NOT NULL, + IsPrimary BIT NOT NULL CONSTRAINT DF_Contacts_IsPrimary DEFAULT (0), + CreatedAt DATETIME2(0) NOT NULL CONSTRAINT DF_Contacts_CreatedAt DEFAULT (SYSDATETIME()), + UpdatedAt DATETIME2(0) NULL, + CONSTRAINT FK_Contacts_Customers FOREIGN KEY (CustomerID) REFERENCES dbo.Customers(CustomerID) ON DELETE CASCADE, + CONSTRAINT FK_Contacts_ContactTypes FOREIGN KEY (ContactTypeID) REFERENCES dbo.ContactTypes(ContactTypeID) +); +GO + +-- ------------------------------------------------------------ +-- Stored procedures for Customers +-- ------------------------------------------------------------ +CREATE PROCEDURE dbo.sp_InsertCustomer + @CustomerName NVARCHAR(150), + @AddressLine1 NVARCHAR(200) = NULL, + @AddressLine2 NVARCHAR(200) = NULL, + @City NVARCHAR(100) = NULL, + @State NVARCHAR(100) = NULL, + @ZipCode NVARCHAR(20) = NULL, + @Country NVARCHAR(100) = NULL, + @NewCustomerID INT OUTPUT +AS +BEGIN + SET NOCOUNT ON; + + INSERT INTO dbo.Customers (CustomerName, AddressLine1, AddressLine2, City, State, ZipCode, Country) + VALUES (@CustomerName, @AddressLine1, @AddressLine2, @City, @State, @ZipCode, @Country); + + SET @NewCustomerID = SCOPE_IDENTITY(); +END +GO + +CREATE PROCEDURE dbo.sp_GetCustomer + @CustomerID INT +AS +BEGIN + SET NOCOUNT ON; + SELECT * + FROM dbo.Customers + WHERE CustomerID = @CustomerID; +END +GO + +CREATE PROCEDURE dbo.sp_UpdateCustomer + @CustomerID INT, + @CustomerName NVARCHAR(150), + @AddressLine1 NVARCHAR(200) = NULL, + @AddressLine2 NVARCHAR(200) = NULL, + @City NVARCHAR(100) = NULL, + @State NVARCHAR(100) = NULL, + @ZipCode NVARCHAR(20) = NULL, + @Country NVARCHAR(100) = NULL +AS +BEGIN + SET NOCOUNT ON; + + UPDATE dbo.Customers + SET CustomerName = @CustomerName, + AddressLine1 = @AddressLine1, + AddressLine2 = @AddressLine2, + City = @City, + State = @State, + ZipCode = @ZipCode, + Country = @Country, + UpdatedAt = SYSDATETIME() + WHERE CustomerID = @CustomerID; +END +GO + +CREATE PROCEDURE dbo.sp_DeleteCustomer + @CustomerID INT +AS +BEGIN + SET NOCOUNT ON; + DELETE FROM dbo.Customers WHERE CustomerID = @CustomerID; +END +GO + +-- ------------------------------------------------------------ +-- Stored procedures for Contacts +-- ------------------------------------------------------------ +CREATE PROCEDURE dbo.sp_InsertContact + @CustomerID INT, + @ContactTypeID INT, + @ContactValue NVARCHAR(200), + @IsPrimary BIT = 0, + @NewContactID INT OUTPUT +AS +BEGIN + SET NOCOUNT ON; + + INSERT INTO dbo.Contacts (CustomerID, ContactTypeID, ContactValue, IsPrimary) + VALUES (@CustomerID, @ContactTypeID, @ContactValue, @IsPrimary); + + SET @NewContactID = SCOPE_IDENTITY(); +END +GO + +CREATE PROCEDURE dbo.sp_GetContactsByCustomer + @CustomerID INT +AS +BEGIN + SET NOCOUNT ON; + SELECT c.*, ct.TypeName + FROM dbo.Contacts c + JOIN dbo.ContactTypes ct ON c.ContactTypeID = ct.ContactTypeID + WHERE c.CustomerID = @CustomerID; +END +GO + +CREATE PROCEDURE dbo.sp_UpdateContact + @ContactID INT, + @ContactTypeID INT, + @ContactValue NVARCHAR(200), + @IsPrimary BIT +AS +BEGIN + SET NOCOUNT ON; + + UPDATE dbo.Contacts + SET ContactTypeID = @ContactTypeID, + ContactValue = @ContactValue, + IsPrimary = @IsPrimary, + UpdatedAt = SYSDATETIME() + WHERE ContactID = @ContactID; +END +GO + +CREATE PROCEDURE dbo.sp_DeleteContact + @ContactID INT +AS +BEGIN + SET NOCOUNT ON; + DELETE FROM dbo.Contacts WHERE ContactID = @ContactID; +END +GO + +-- ------------------------------------------------------------ +-- Views +-- ------------------------------------------------------------ +CREATE VIEW dbo.vw_CustomerContacts AS +SELECT + cu.CustomerID, + cu.CustomerName, + cu.AddressLine1, + cu.AddressLine2, + cu.City, + cu.State, + cu.ZipCode, + cu.Country, + ct.TypeName, + co.ContactValue, + co.IsPrimary +FROM dbo.Customers cu +LEFT JOIN dbo.Contacts co ON cu.CustomerID = co.CustomerID +LEFT JOIN dbo.ContactTypes ct ON co.ContactTypeID = ct.ContactTypeID; +GO + +-- ------------------------------------------------------------ +-- Sample data (optional, for quick testing) +-- ------------------------------------------------------------ +INSERT INTO dbo.Customers (CustomerName, City, State, Country) +VALUES (N'Acme Corp', N'New York', N'NY', N'USA'); + +DECLARE @CustID INT = SCOPE_IDENTITY(); + +EXEC dbo.sp_InsertContact @CustomerID = @CustID, + @ContactTypeID = (SELECT ContactTypeID FROM dbo.ContactTypes WHERE TypeName = N'Phone'), + @ContactValue = N'555-1234', + @IsPrimary = 1, + @NewContactID = @CustID OUTPUT; +GO diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b399e61 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +services: + mssql: + image: mcr.microsoft.com/mssql/server:2019-latest + container_name: mssql + environment: + - SA_PASSWORD=Passw0rd + - ACCEPT_EULA=Y + ports: + - "1433:1433" + volumes: + - ./db/data:/var/opt/mssql + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: backend + depends_on: + - mssql + environment: + - DB_SERVER=mssql + - DB_USER=sa + - DB_PASSWORD=Passw0rd + - DB_NAME=CustomerDB + ports: + - "8080:8080" + volumes: + - ./backend:/app + command: ./main + +networks: + default: + driver: bridge