Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 83 additions & 7 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
services:
user-db:
build:
context: ./docker/user-db
container_name: bytebite-user-db
environment:
POSTGRES_DB: ${USER_DB_NAME:-bytebite_user}
POSTGRES_USER: ${USER_DB_USER:-bytebite_user}
POSTGRES_PASSWORD: ${USER_DB_PASSWORD:-bytebite_user_password}
volumes:
- user-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5

grocery-db:
build:
context: ./docker/grocery-db
container_name: bytebite-grocery-db
environment:
POSTGRES_DB: ${GROCERY_DB_NAME:-bytebite_grocery}
POSTGRES_USER: ${GROCERY_DB_USER:-bytebite_grocery}
POSTGRES_PASSWORD: ${GROCERY_DB_PASSWORD:-bytebite_grocery_password}
volumes:
- grocery-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5

gen-ai:
# `up --build` builds this locally; on the VM `pull` fetches it from GHCR.
image: ${REGISTRY:-ghcr.io/aet-devops26/team-bytebite}/gen-ai:${IMAGE_TAG:-latest}
Expand All @@ -10,17 +42,57 @@ services:
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}

server:
image: ${REGISTRY:-ghcr.io/aet-devops26/team-bytebite}/server:${IMAGE_TAG:-latest}

user-service:
build:
context: ./user-service
container_name: bytebite-user-service
expose:
- "8080"
environment:
SPRING_APPLICATION_NAME: user-service
SPRING_DATASOURCE_URL: jdbc:postgresql://user-db:5432/${USER_DB_NAME:-bytebite_user}
SPRING_DATASOURCE_USERNAME: ${USER_DB_USER:-bytebite_user}
SPRING_DATASOURCE_PASSWORD: ${USER_DB_PASSWORD:-bytebite_user_password}
depends_on:
user-db:
condition: service_healthy

grocery-service:
build:
context: ./server
container_name: bytebite-server
context: ./grocery-service
container_name: bytebite-grocery-service
expose:
- "8080"
environment:
SPRING_APPLICATION_NAME: grocery-service
GENAI_BASE_URL: http://gen-ai:8000
SPRING_DATASOURCE_URL: jdbc:postgresql://grocery-db:5432/${GROCERY_DB_NAME:-bytebite_grocery}
SPRING_DATASOURCE_USERNAME: ${GROCERY_DB_USER:-bytebite_grocery}
SPRING_DATASOURCE_PASSWORD: ${GROCERY_DB_PASSWORD:-bytebite_grocery_password}
depends_on:
grocery-db:
condition: service_healthy
gen-ai:
condition: service_started

api-gateway:
build:
context: ./api-gateway
container_name: bytebite-api-gateway
ports:
- "8080:8080"
environment:
GENAI_BASE_URL: http://gen-ai:8000
SPRING_APPLICATION_NAME: api-gateway
USER_SERVICE_BASE_URL: http://user-service:8080
GROCERY_SERVICE_BASE_URL: http://grocery-service:8080
depends_on:
- gen-ai
user-service:
condition: service_started
grocery-service:
condition: service_started
gen-ai:
condition: service_started

client:
image: ${REGISTRY:-ghcr.io/aet-devops26/team-bytebite}/client:${IMAGE_TAG:-latest}
Expand All @@ -30,4 +102,8 @@ services:
ports:
- "8081:80"
depends_on:
- server
- api-gateway

volumes:
user-db-data:
grocery-db-data:
3 changes: 3 additions & 0 deletions docker/grocery-db/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM postgres:16-alpine

COPY init.sql /docker-entrypoint-initdb.d/01-init.sql
77 changes: 77 additions & 0 deletions docker/grocery-db/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TYPE grocery_category AS ENUM (
'PRODUCE',
'DAIRY',
'MEAT',
'SEAFOOD',
'BAKERY',
'PANTRY',
'FROZEN',
'BEVERAGES',
'SPICES',
'OTHER'
);

CREATE TABLE IF NOT EXISTS recipes (
recipe_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
user_id UUID NOT NULL
);

CREATE TABLE IF NOT EXISTS grocery_lists (
grocery_list_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
outdated BOOLEAN NOT NULL DEFAULT FALSE,
user_id UUID NOT NULL
);

CREATE TABLE IF NOT EXISTS grocery_list_recipes (
grocery_list_id UUID NOT NULL,
recipe_id UUID NOT NULL,
PRIMARY KEY (grocery_list_id, recipe_id),
CONSTRAINT fk_grocery_list_recipes_grocery_list
FOREIGN KEY (grocery_list_id)
REFERENCES grocery_lists (grocery_list_id)
ON DELETE CASCADE,
CONSTRAINT fk_grocery_list_recipes_recipe
FOREIGN KEY (recipe_id)
REFERENCES recipes (recipe_id)
ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS grocery_items (
item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
quantity DOUBLE PRECISION NOT NULL,
unit VARCHAR(50) NOT NULL,
category grocery_category NOT NULL DEFAULT 'OTHER',
is_purchased BOOLEAN NOT NULL DEFAULT FALSE,
recipe_id UUID,
grocery_list_id UUID,
CONSTRAINT fk_grocery_items_recipe
FOREIGN KEY (recipe_id)
REFERENCES recipes (recipe_id)
ON DELETE CASCADE,
CONSTRAINT fk_grocery_items_grocery_list
FOREIGN KEY (grocery_list_id)
REFERENCES grocery_lists (grocery_list_id)
ON DELETE CASCADE,
CONSTRAINT chk_grocery_items_owner
CHECK (recipe_id IS NOT NULL OR grocery_list_id IS NOT NULL)
);

CREATE INDEX IF NOT EXISTS idx_recipes_user_id
ON recipes (user_id);

CREATE INDEX IF NOT EXISTS idx_grocery_lists_user_id
ON grocery_lists (user_id);

CREATE INDEX IF NOT EXISTS idx_grocery_list_recipes_recipe_id
ON grocery_list_recipes (recipe_id);

CREATE INDEX IF NOT EXISTS idx_grocery_items_recipe_id
ON grocery_items (recipe_id);

CREATE INDEX IF NOT EXISTS idx_grocery_items_grocery_list_id
ON grocery_items (grocery_list_id);
3 changes: 3 additions & 0 deletions docker/user-db/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM postgres:16-alpine

COPY init.sql /docker-entrypoint-initdb.d/01-init.sql
10 changes: 10 additions & 0 deletions docker/user-db/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE IF NOT EXISTS users (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

139 changes: 139 additions & 0 deletions documentation/database/DBSchemaDiagram.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<mxfile host="app.diagrams.net">
<diagram name="Seite-1" id="V-4LWKYRLYHX_k2GcSv2">
<mxGraphModel dx="2165" dy="878" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="RtYmGdZxLaFv6OyNaVwG-60" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
<mxGeometry height="720" width="900" x="-290" y="10" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-1" parent="1" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" value="User" vertex="1">
<mxGeometry height="130" width="160" x="80" y="80" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-2" parent="RtYmGdZxLaFv6OyNaVwG-1" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" value="userId: UUID&lt;div&gt;name: String&lt;/div&gt;&lt;div&gt;email:String&lt;/div&gt;&lt;div&gt;passwordHash: String&lt;/div&gt;&lt;div&gt;createdAt: DateTime&lt;/div&gt;" vertex="1">
<mxGeometry height="104" width="160" y="26" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-8" parent="1" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" value="GroceryList" vertex="1">
<mxGeometry height="110" width="160" x="334" y="345" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-9" parent="RtYmGdZxLaFv6OyNaVwG-8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" value="groceryListId: UUID&lt;div&gt;name: String&lt;/div&gt;&lt;div&gt;usedRecipeIds: Recipe[]&lt;/div&gt;&lt;div&gt;outdated: boolean&lt;/div&gt;&lt;div&gt;FK userId&lt;/div&gt;" vertex="1">
<mxGeometry height="84" width="160" y="26" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-10" parent="1" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" value="GroceryItem" vertex="1">
<mxGeometry height="160" width="160" x="80" y="470" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-11" parent="RtYmGdZxLaFv6OyNaVwG-10" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" value="itemId: UUID&lt;div&gt;name: String&lt;/div&gt;&lt;div&gt;quantity: Float&lt;/div&gt;&lt;div&gt;unit: String&lt;/div&gt;&lt;div&gt;category: String (enum)&lt;/div&gt;&lt;div&gt;isPurchased: boolean&lt;/div&gt;&lt;div&gt;FK recipeId&lt;/div&gt;&lt;div&gt;FK groceryListId&lt;/div&gt;" vertex="1">
<mxGeometry height="134" width="160" y="26" as="geometry" />
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-20" edge="1" parent="1" source="RtYmGdZxLaFv6OyNaVwG-2" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" target="RtYmGdZxLaFv6OyNaVwG-8" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="250" y="130" as="sourcePoint" />
<mxPoint x="290" y="130" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-21" connectable="0" parent="RtYmGdZxLaFv6OyNaVwG-20" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint x="10" y="12" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="RtYmGdZxLaFv6OyNaVwG-22" connectable="0" parent="RtYmGdZxLaFv6OyNaVwG-20" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint x="6" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-1" parent="1" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" value="Recipe" vertex="1">
<mxGeometry height="100" width="160" x="-180" y="350" as="geometry" />
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-2" parent="i-Sbl1M0T4CMpME9p0qI-1" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" value="recipeId: UUID&lt;div&gt;name: String&lt;/div&gt;&lt;div&gt;FK userId&lt;/div&gt;" vertex="1">
<mxGeometry height="74" width="160" y="26" as="geometry" />
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-4" edge="1" parent="1" source="RtYmGdZxLaFv6OyNaVwG-9" style="endArrow=none;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" target="RtYmGdZxLaFv6OyNaVwG-11" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="280" y="390" as="sourcePoint" />
<mxPoint x="400" y="270" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-5" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-4" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint x="-14" y="37" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-6" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-4" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint x="10" y="22" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-7" edge="1" parent="1" source="RtYmGdZxLaFv6OyNaVwG-11" style="endArrow=none;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" target="i-Sbl1M0T4CMpME9p0qI-2" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="109.51999999999998" y="280" as="sourcePoint" />
<mxPoint x="-120" y="220" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-8" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-7" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint x="-10" y="17" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-9" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-7" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint x="10" y="27" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-10" edge="1" parent="1" source="i-Sbl1M0T4CMpME9p0qI-1" style="endArrow=none;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" target="RtYmGdZxLaFv6OyNaVwG-2" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint y="152.47" as="sourcePoint" />
<mxPoint x="80" y="152.47" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-11" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-10" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint x="-10" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-12" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-10" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint x="-10" y="12" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-13" parent="1" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" value="GroceryListRecipies" vertex="1">
<mxGeometry height="80" width="160" x="80" y="290" as="geometry" />
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-14" parent="i-Sbl1M0T4CMpME9p0qI-13" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" value="&lt;div&gt;FK userId&lt;/div&gt;&lt;div&gt;FK groceryListId&lt;/div&gt;" vertex="1">
<mxGeometry height="54" width="160" y="26" as="geometry" />
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-15" edge="1" parent="1" source="i-Sbl1M0T4CMpME9p0qI-2" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" target="i-Sbl1M0T4CMpME9p0qI-14" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="-20" y="452" as="sourcePoint" />
<mxPoint x="160" y="260" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-16" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-15" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint y="-3" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-17" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-15" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-18" edge="1" parent="1" source="RtYmGdZxLaFv6OyNaVwG-9" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="i-Sbl1M0T4CMpME9p0qI-14" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="200" y="413" as="sourcePoint" />
<mxPoint x="300" y="330" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-19" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-18" style="resizable=0;html=1;whiteSpace=wrap;align=left;verticalAlign=bottom;" value="1" vertex="1">
<mxGeometry relative="1" x="-1" as="geometry">
<mxPoint x="-14" y="-13" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="i-Sbl1M0T4CMpME9p0qI-20" connectable="0" parent="i-Sbl1M0T4CMpME9p0qI-18" style="resizable=0;html=1;whiteSpace=wrap;align=right;verticalAlign=bottom;" value="*" vertex="1">
<mxGeometry relative="1" x="1" as="geometry">
<mxPoint x="10" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file added documentation/database/SchemaDiagram.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading