Add Docker Compose and backend Dockerfile

Include .gitignore, go.mod/go.sum, initial Go source, init.sql, and
Docker‑Compose
configuration for the MSSQL and backend services.
This commit is contained in:
2026-01-18 14:56:59 -05:00
parent fb3e983eff
commit fa1ad45ed8
12 changed files with 516 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
db/data/log

34
backend/Dockerfile Normal file
View File

@@ -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"]

11
backend/go.mod Normal file
View File

@@ -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
)

38
backend/go.sum Normal file
View File

@@ -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=

BIN
backend/main Normal file
View File

Binary file not shown.

173
backend/main.go Normal file
View File

@@ -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 dockercompose)
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 gomssqldb
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", "5551234"),
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")
}

View File

@@ -0,0 +1,3 @@
59629046-e974-4356-ba63-5f0605022367
9979554600398963173
3457

View File

View File

Binary file not shown.

View File

Binary file not shown.

223
db/init.sql Normal file
View File

@@ -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

33
docker-compose.yml Normal file
View File

@@ -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