diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a01213f..c3c902f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/go { - "name": "macrun-teamvs_", + "name": "macrun-teamvsl", // "build": { // "dockerfile": "Dockerfile" // }, @@ -10,17 +10,19 @@ // Features to add to the dev container. More info: https://containers.dev/features. "features": { - "ghcr.io/devcontainers/features/common-utils:2": { + "ghcr.io/devcontainers/features/common-utils:2": { "configureZshAsDefaultShell": true + }, + "ghcr.io/itsmechlark/features/rabbitmq-server:1" : { } // "ghcr.io/devcontainers-contrib/features/protoc:1": {} }, // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8000, "8000:8000"], + // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "bash ./.devcontainer/install.sh", + "postCreateCommand": "sudo rabbitmq-plugins enable rabbitmq_management", // Configure tool-specific properties. "customizations": { diff --git a/README.md b/README.md index 180f0bc..19b726a 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-24ddc0f5d75046c5622901739e7c5dd533143b0c8e959d652212380cedb1ea36.svg)](https://classroom.github.com/a/I8UeB_vA) +# ACME RUN - Liuyin, Samkith, Varun + +Welcome to our implementation of ACME RUN, a collaborative project developed by Liuyin, Samkith, and Varun as part of CAS 735 (Fall 2023). + +## Installation + +1. Clone the Repository: +```bash +git clone https://github.com/CAS735-F23/macrun-teamvsl.git +cd macrun-teamvsl +``` + +2. Run Docker Compose +```bash +docker compose up +``` + +## Getting Started + +Now that you have the project running, follow these steps to get started: + +1. **Import Postman Collection**: +- Import the provided Postman Collection into your Postman workspace. +- The collection includes predefined API requests for ACME RUN. + +2. **Run APIs**: +- Execute the API requests from the top of the Postman Collection. +- Explore and interact with the various endpoints available in ACME RUN. + + +## Contributors + +- Liuyin Shi (shil9@mcmaster.ca) +- Samkith Jain (kishors@mcmaster.ca) +- Varun Rajput (rajpuv2@mcmaster.ca) diff --git a/apis.postman_collection.json b/apis.postman_collection.json new file mode 100644 index 0000000..4f7e6c0 --- /dev/null +++ b/apis.postman_collection.json @@ -0,0 +1,297 @@ +{ + "info": { + "_postman_id": "9d58d4fc-4c61-4f41-9944-6d29c3b2cd9e", + "name": "MacRun - Liuyin, Samkith, Varun", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "14312203", + "_collection_link": "https://winter-satellite-393249.postman.co/workspace/cas-735~2906f288-5f3e-4839-8f70-f7f36274cd09/collection/14312203-9d58d4fc-4c61-4f41-9944-6d29c3b2cd9e?action=share&source=collection_link&creator=14312203" + }, + "item": [ + { + "name": "Player Service", + "item": [ + { + "name": "Create Player", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const responseJson = pm.response.json();", + "const playerId = responseJson.id;", + "", + "pm.collectionVariables.set('playerId', playerId); ", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"user\": {\n \"name\": \"Player1\",\n \"email\": \"player1@gmail.com\",\n \"dob\": \"1998-07-19\"\n },\n \"height\": 175,\n \"weight\": 80,\n \"geographicalZone\": \"McMaster University\"\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/player", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "player" + ] + } + }, + "response": [] + }, + { + "name": "List Players", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/players", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "players" + ] + } + }, + "response": [] + }, + { + "name": "Get Player", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/players/{{playerId}}", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "players", + "{{playerId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "HRM Service", + "item": [ + { + "name": "Connect HRM", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"hrm_id\" : \"{{hrmId}}\", \n \"connect\" : true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8004/hrms", + "host": [ + "localhost" + ], + "port": "8004", + "path": [ + "hrms" + ] + } + }, + "response": [] + }, + { + "name": "Disconnect HRM", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"hrm_id\" : \"{{hrmId}}\", \n \"connect\" : false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8004/hrms", + "host": [ + "localhost" + ], + "port": "8004", + "path": [ + "hrms" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "WorkoutService", + "item": [ + { + "name": "Start Work Out", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "const responseJson = pm.response.json();", + "const workoutId = responseJson.id;", + "", + "pm.collectionVariables.set('workoutId', workoutId); ", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"trailID\":\"{{trailId}}\",\n \"playerID\":\"{{playerId}}\",\n \"hrmConnected\":true,\n \"hrmID\":\"{{hrmId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8001/workout", + "host": [ + "localhost" + ], + "port": "8001", + "path": [ + "workout" + ] + } + }, + "response": [] + }, + { + "name": "List Workouts", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8001/workouts", + "host": [ + "localhost" + ], + "port": "8001", + "path": [ + "workouts" + ] + } + }, + "response": [] + }, + { + "name": "Stop Work Out", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"workoutID\": \"4bbb5ae9-d7c4-4a24-8264-be12a2d8b5b5\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8001/workout", + "host": [ + "localhost" + ], + "port": "8001", + "path": [ + "workout" + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "playerId", + "value": "", + "type": "string" + }, + { + "key": "hrmId", + "value": "", + "type": "string" + }, + { + "key": "trailId", + "value": "", + "type": "string" + }, + { + "key": "workoutId", + "value": "", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..98288b0 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,49 @@ +version: "3.8" + +services: + rabbitmq: + container_name: rabbitmq + image: rabbitmq:management + ports: + - "5672:5672" + - "15672:15672" + healthcheck: + test: rabbitmq-diagnostics check_port_connectivity + interval: 5s + timeout: 5s + retries: 3 + + player: + container_name: player + build: ./player + ports: + - "8000:8000" + + hrm: + container_name: hrm + build: ./hrm + ports: + - "8004:8004" + environment: + - RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ + #- POSTGRES_URL=postgres://postgres:postgres@postgres:5432/postgres + depends_on: + rabbitmq: + condition: service_healthy + player: + condition: service_started + + workout: + container_name: workout + build: ./workout + ports: + - "8001:8001" + environment: + - RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ + depends_on: + rabbitmq: + condition: service_healthy + hrm: + condition: service_started + player: + condition: service_started diff --git a/examples/ddd/aggregate/hrm.go b/examples/ddd/aggregate/hrm.go deleted file mode 100644 index e32afbe..0000000 --- a/examples/ddd/aggregate/hrm.go +++ /dev/null @@ -1,6 +0,0 @@ -package aggregate - -// type HeartRateMonitor struct { -// WorkoutId uuid.UUID -// heartRate []valueobject.HeartRate -// } diff --git a/examples/ddd/aggregate/player.go b/examples/ddd/aggregate/player.go deleted file mode 100644 index cc0151c..0000000 --- a/examples/ddd/aggregate/player.go +++ /dev/null @@ -1,72 +0,0 @@ -// Package entities holds all the entities that are shared across all subdomains -package aggregate - -import ( - "errors" - "net/mail" - "time" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/entity" -) - -var ( - ErrInvalidEmail = errors.New("a customer has to have a valid email") -) - -// Player is a entity that represents a Player in all Domains -type Player struct { - // User is the root entity of Player - user *entity.User - - weight float32 - // Height of the player - height float32 - // GeographicalZone is a group of trails in a region - geographicalzone string - // CreatedAt is the time when the player registered - createdat time.Time - // UpdatedAt is the time when the player last updated the profile - updatedat time.Time -} - -// NewPlayer is a factory to create a new Player aggregate -// It will validate that the name is not empty -func NewPlayer(name string, email string, dob string, weight float32, height float32, geographicalzone string) (Player, error) { - // Validate that the Email has @ - _, err := mail.ParseAddress(email) - if err != nil { - return Player{}, ErrInvalidEmail - } - - // Create a new person and generate ID - user := &entity.User{ - Name: name, - Email: email, - DateOfBirth: dob, - ID: uuid.New(), - } - // Create a customer object and initialize all the values to avoid nil pointer exceptions - return Player{ - user: user, - weight: weight, - height: height, - geographicalzone: geographicalzone, - }, nil -} - -func (player *Player) GetID() uuid.UUID { - return player.user.ID -} - -func (player *Player) SetID(id uuid.UUID) { - player.user.ID = id -} - -func (player *Player) GetEmail() string { - return player.user.Email -} - -func (player *Player) SetEmail(email string) { - player.user.Email = email -} diff --git a/examples/ddd/aggregate/player_test.go b/examples/ddd/aggregate/player_test.go deleted file mode 100644 index e2b4896..0000000 --- a/examples/ddd/aggregate/player_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package aggregate_test - -import ( - "errors" - "testing" - - "github.com/rvarun11/macrun-teamvs/aggregate" -) - -func TestPlayer_NewPlayer(t *testing.T) { - type testCase struct { - test string - name string - email string - dob string - Weight float32 - Height float32 - GeographicalZone string - expectedErr error - } - - testCases := []testCase{ - { - test: "Invalid Email Check", - name: "Samkith K Jain", - email: "kishors#mcmaster.ca", - dob: "11/09/1997", - Weight: 60.0, - Height: 60.0, - GeographicalZone: "Mac", - expectedErr: aggregate.ErrInvalidEmail, - }, - { - test: "Valid", - name: "Samkith K Jain", - email: "kishors@mcmaster.ca", - dob: "11/09/1997", - Weight: 60.0, - Height: 60.0, - GeographicalZone: "Mac", - expectedErr: nil, - }, - } - - for _, tc := range testCases { - - t.Run(tc.test, func(t *testing.T) { - _, err := aggregate.NewPlayer(tc.name, tc.email, tc.dob, tc.Weight, tc.Height, tc.GeographicalZone) - - if !errors.Is(err, tc.expectedErr) { - t.Errorf("Expected Error %v, Got Error %v", tc.expectedErr, err) - } - }) - } -} diff --git a/examples/ddd/aggregate/workout.go b/examples/ddd/aggregate/workout.go deleted file mode 100644 index fae61dc..0000000 --- a/examples/ddd/aggregate/workout.go +++ /dev/null @@ -1,71 +0,0 @@ -// Package entities holds all the entities that are shared across all subdomains -package aggregate - -import ( - "errors" - "time" - - "github.com/google/uuid" -) - -var ( - ErrInvalidWorkout = errors.New("no player associated with the workout session") -) - -// Workout is a entity that represents a workout in all Domains -type Workout struct { - // ID is the identifier of the Entity, the ID is shared for all sub domains - id uuid.UUID - // trailId is the id of the trail player is on - trailId uuid.UUID - // PlayerID of the player starting the workout session - playerID uuid.UUID - // CreatedAt is the time when the workout was started/created at? - createdAt time.Time - // Duration of the workout, TODO: fix type - endedAt *time.Time - // DurationCovered is the total distance covered during the session - distanceCovered float64 - // TODO: temp value. It can be either "cardio", "physical" or "dynamic" - option string - // HardcoreMode is the difficulty level chosen by the player - hardcoreMode bool - // HRM Reading from the workout - // heartRate []valueobject.HeartRate -} - -func NewWorkout(playerID uuid.UUID, trailId uuid.UUID, hardcoreMode bool, option string) (Workout, error) { - if playerID == uuid.Nil { - return Workout{}, ErrInvalidWorkout - } - - return Workout{ - id: uuid.New(), - playerID: playerID, - trailId: trailId, - option: option, - hardcoreMode: hardcoreMode, - createdAt: time.Now(), - endedAt: nil, - distanceCovered: 0, - // heartRates: make([]valueobject.HeartRate, 0), - }, nil -} - -func (ws *Workout) GetID() uuid.UUID { - return ws.id -} - -func (ws *Workout) SetID(id uuid.UUID) { - ws.id = id -} - -func (ws *Workout) GetPlayerID() uuid.UUID { - return ws.playerID -} - -func (ws *Workout) SetPlayerID(playerID uuid.UUID) { - ws.playerID = playerID -} - -// TODO: Add the rest as per need diff --git a/examples/ddd/aggregate/workout_test.go b/examples/ddd/aggregate/workout_test.go deleted file mode 100644 index 7a68849..0000000 --- a/examples/ddd/aggregate/workout_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package aggregate_test - -import ( - "errors" - "testing" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" -) - -func TestWorkout_NewWorkout(t *testing.T) { - type testCase struct { - test string - id uuid.UUID - expectedErr error - } - - // TODO: Add test case of workout type, dynamic strength or cardio - testCases := []testCase{ - { - test: "Empty workout session param validation", - id: uuid.Nil, - expectedErr: aggregate.ErrInvalidWorkout, - }, { - test: "Valid workout session", - id: uuid.New(), - expectedErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.test, func(t *testing.T) { - _, err := aggregate.NewWorkout(tc.id, uuid.New(), false, "dynamic") - - if !errors.Is(err, tc.expectedErr) { - t.Errorf("expected error %v, got %v", tc.expectedErr, err) - } - }) - } -} diff --git a/examples/ddd/domain/player/memory/memory.go b/examples/ddd/domain/player/memory/memory.go deleted file mode 100644 index 9b5756c..0000000 --- a/examples/ddd/domain/player/memory/memory.go +++ /dev/null @@ -1,53 +0,0 @@ -package memory - -import ( - "fmt" - "sync" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" - PlayerRepository "github.com/rvarun11/macrun-teamvs/domain/player" -) - -type MemoryRepository struct { - players map[uuid.UUID]aggregate.Player - sync.Mutex -} - -func New() *MemoryRepository { - return &MemoryRepository{ - players: make(map[uuid.UUID]aggregate.Player), - } -} - -func (mr *MemoryRepository) Get(id uuid.UUID) (aggregate.Player, error) { - if player, ok := mr.players[id]; ok { - return player, nil - } - return aggregate.Player{}, PlayerRepository.ErrorPlayerNotFound -} - -func (mr *MemoryRepository) Add(player aggregate.Player) error { - if mr.players == nil { - mr.Lock() - mr.players = make(map[uuid.UUID]aggregate.Player) - mr.Unlock() - } - if _, ok := mr.players[player.GetID()]; ok { - return fmt.Errorf("player already exist: %w", PlayerRepository.ErrorFailedToAddPlayer) - } - mr.Lock() - mr.players[player.GetID()] = player - mr.Unlock() - return nil -} - -func (mr *MemoryRepository) Update(player aggregate.Player) error { - if _, ok := mr.players[player.GetID()]; ok { - return fmt.Errorf("workout session does not exist: %w", PlayerRepository.ErrorUpdatePlayerFailed) - } - mr.Lock() - mr.players[player.GetID()] = player - mr.Unlock() - return nil -} diff --git a/examples/ddd/domain/player/memory/memory_test.go b/examples/ddd/domain/player/memory/memory_test.go deleted file mode 100644 index 7473305..0000000 --- a/examples/ddd/domain/player/memory/memory_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package memory - -import ( - "testing" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" - PlayerRepository "github.com/rvarun11/macrun-teamvs/domain/player" -) - -func TestMemory_GetPlayer(t *testing.T) { - type testCase struct { - name string - id uuid.UUID - expectedErr error - } - - // Create a fake customer to add to repository - player, err := aggregate.NewPlayer("Samkith", "kishors@mcmaster.ca", "11/09/1997", 60.0, 5.6, "Mac") - if err != nil { - t.Fatal(err) - } - id := player.GetID() - // Create the repo to use, and add some test Data to it for testing - // Skip Factory for this - repo := MemoryRepository{ - players: map[uuid.UUID]aggregate.Player{ - id: player, - }, - } - - testCases := []testCase{ - { - name: "No Player By ID", - id: uuid.MustParse("f47ac10b-58cc-0372-8567-0e02b2c3d479"), - expectedErr: PlayerRepository.ErrorPlayerNotFound, - }, { - name: "Player By ID", - id: id, - expectedErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - _, err := repo.Get(tc.id) - if err != tc.expectedErr { - t.Errorf("Expected error %v, got %v", tc.expectedErr, err) - } - }) - } -} - -func TestMemory_AddPlayer(t *testing.T) { - type testCase struct { - test string - name string - email string - dob string - Weight float32 - Height float32 - GeographicalZone string - expectedErr error - } - - testCases := []testCase{ - { - test: "Add a Player", - name: "Samkith K Jain", - email: "kishors@mcmaster.ca", - dob: "11/09/1997", - Weight: 60.0, - Height: 5.6, - GeographicalZone: "Mac", - expectedErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - repo := MemoryRepository{ - players: map[uuid.UUID]aggregate.Player{}, - } - - player, err := aggregate.NewPlayer(tc.name, tc.email, tc.dob, tc.Weight, tc.Height, tc.GeographicalZone) - if err != nil { - t.Fatal(err) - } - - err = repo.Add(player) - if err != tc.expectedErr { - t.Errorf("Expected error %v, got %v", tc.expectedErr, err) - } - - found, err := repo.Get(player.GetID()) - if err != nil { - t.Fatal(err) - } - if found.GetID() != player.GetID() { - t.Errorf("Expected %v, got %v", player.GetID(), found.GetID()) - } - }) - } -} diff --git a/examples/ddd/domain/player/repository.go b/examples/ddd/domain/player/repository.go deleted file mode 100644 index b56645e..0000000 --- a/examples/ddd/domain/player/repository.go +++ /dev/null @@ -1,20 +0,0 @@ -package Workout - -import ( - "errors" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" -) - -var ( - ErrorPlayerNotFound = errors.New("the player session not found in repository") - ErrorFailedToAddPlayer = errors.New("failed to add the player") - ErrorUpdatePlayerFailed = errors.New("failed to update player") -) - -type PlayerRepository interface { - Get(uuid.UUID) (aggregate.Player, error) - Add(aggregate.Player) error - Update(aggregate.Player) error -} diff --git a/examples/ddd/domain/workout/memory/memory.go b/examples/ddd/domain/workout/memory/memory.go deleted file mode 100644 index c9e15bf..0000000 --- a/examples/ddd/domain/workout/memory/memory.go +++ /dev/null @@ -1,53 +0,0 @@ -package memory - -import ( - "fmt" - "sync" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" - workout "github.com/rvarun11/macrun-teamvs/domain/workout" -) - -type MemoryRepository struct { - Workouts map[uuid.UUID]aggregate.Workout - sync.Mutex -} - -func New() *MemoryRepository { - return &MemoryRepository{ - Workouts: make(map[uuid.UUID]aggregate.Workout), - } -} - -func (mr *MemoryRepository) Get(id uuid.UUID) (aggregate.Workout, error) { - if Workout, ok := mr.Workouts[id]; ok { - return Workout, nil - } - return aggregate.Workout{}, workout.ErrWorkoutNotFound -} - -func (mr *MemoryRepository) Add(ws aggregate.Workout) error { - if mr.Workouts == nil { - mr.Lock() - mr.Workouts = make(map[uuid.UUID]aggregate.Workout) - mr.Unlock() - } - if _, ok := mr.Workouts[ws.GetID()]; ok { - return fmt.Errorf("workout session already exist: %w", workout.ErrAddWorkoutFailed) - } - mr.Lock() - mr.Workouts[ws.GetID()] = ws - mr.Unlock() - return nil -} - -func (mr *MemoryRepository) Update(ws aggregate.Workout) error { - if _, ok := mr.Workouts[ws.GetID()]; ok { - return fmt.Errorf("workout session does not exist: %w", workout.ErrorUpdateWorkoutFailed) - } - mr.Lock() - mr.Workouts[ws.GetID()] = ws - mr.Unlock() - return nil -} diff --git a/examples/ddd/domain/workout/memory/memory_test.go b/examples/ddd/domain/workout/memory/memory_test.go deleted file mode 100644 index 3635d61..0000000 --- a/examples/ddd/domain/workout/memory/memory_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package memory - -import ( - "errors" - "testing" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" - workout "github.com/rvarun11/macrun-teamvs/domain/workout" -) - -func TestMemory_GetWorkout(t *testing.T) { - type testCase struct { - name string - id uuid.UUID - expectedErr error - } - - ws, err := aggregate.NewWorkout(uuid.New(), uuid.New(), false, "dynamic") - if err != nil { - t.Fatal(err) - } - - id := ws.GetID() - - repo := MemoryRepository{ - Workouts: map[uuid.UUID]aggregate.Workout{ - id: ws, - }, - } - - testCases := []testCase{ - { - name: "no workout session by id", - id: uuid.MustParse("bd0776ac-581e-4a62-93d3-011ec4e072cd"), - expectedErr: workout.ErrWorkoutNotFound, - }, { - name: "workout session by id", - id: id, - expectedErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - _, err := repo.Get(tc.id) - - if !errors.Is(err, tc.expectedErr) { - t.Errorf("expected error %v, got %v", tc.expectedErr, err) - } - }) - } - -} diff --git a/examples/ddd/domain/workout/respository.go b/examples/ddd/domain/workout/respository.go deleted file mode 100644 index 9137d55..0000000 --- a/examples/ddd/domain/workout/respository.go +++ /dev/null @@ -1,20 +0,0 @@ -package Workout - -import ( - "errors" - - "github.com/google/uuid" - "github.com/rvarun11/macrun-teamvs/aggregate" -) - -var ( - ErrWorkoutNotFound = errors.New("the workout session not found in repository") - ErrAddWorkoutFailed = errors.New("failed to add the workout session") - ErrorUpdateWorkoutFailed = errors.New("failed to update workout session") -) - -type WorkoutRepository interface { - Get(uuid.UUID) (aggregate.Workout, error) - Add(aggregate.Workout) error - Update(aggregate.Workout) error -} diff --git a/examples/ddd/entity/user.go b/examples/ddd/entity/user.go deleted file mode 100644 index 8500f60..0000000 --- a/examples/ddd/entity/user.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package entities holds all the entities that are shared across all subdomains -package entity - -import ( - "github.com/google/uuid" -) - -// User is a entity that represents a Users in all Domains -type User struct { - // ID is the identifier of the Entity, the ID is shared for all sub domains - ID uuid.UUID - // First Name of the person - Name string - // Email - Email string - // Date of Birth of the player TODO: Fix type - DateOfBirth string -} diff --git a/examples/ddd/service/player.go b/examples/ddd/service/player.go deleted file mode 100644 index 42057bc..0000000 --- a/examples/ddd/service/player.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package services holds all the services that connects repositories into a business flow -package service - -import ( - player "github.com/rvarun11/macrun-teamvs/domain/player" - "github.com/rvarun11/macrun-teamvs/domain/player/memory" -) - -// PlayerConfiguration is an alias for a function that will take in a pointer to an PlayerService and modify it -type PlayerConfiguration func(os *PlayerService) error - -// OrderService is a implementation of the OrderService -type PlayerService struct { - players player.PlayerRepository -} - -// NewPlayerService takes a variable amount of PlayerConfiguration functions and returns a new PlayerService -// Each PlayerConfiguration will be called in the order they are passed in -func NewOrderService(cfgs ...PlayerConfiguration) (*PlayerService, error) { - // Create the PlayerService - os := &PlayerService{} - // Apply all Configurations passed in - for _, cfg := range cfgs { - // Pass the service into the configuration function - err := cfg(os) - if err != nil { - return nil, err - } - } - return os, nil -} - -// WithPlayerRepository applies a given customer repository to the OrderService -func WithPlayerRepository(pr player.PlayerRepository) PlayerConfiguration { - // return a function that matches the PlayerConfiguration alias, - // You need to return this so that the parent function can take in all the needed parameters - return func(ps *PlayerService) error { - ps.players = pr - return nil - } -} - -// WithMemoryCustomerRepository applies a memory customer repository to the PlayerService -func WithMemoryCustomerRepository() PlayerConfiguration { - // Create the memory repo, if we needed parameters, such as connection strings they could be inputted here - pr := memory.New() - return WithPlayerRepository(pr) -} diff --git a/examples/ddd/service/workout.go b/examples/ddd/service/workout.go deleted file mode 100644 index 330df85..0000000 --- a/examples/ddd/service/workout.go +++ /dev/null @@ -1,56 +0,0 @@ -package service - -import ( - "github.com/google/uuid" - workout "github.com/rvarun11/macrun-teamvs/domain/workout" - "github.com/rvarun11/macrun-teamvs/domain/workout/memory" -) - -type WorkoutConfiguration func(wss *WorkoutService) error - -type WorkoutService struct { - Workouts workout.WorkoutRepository -} - -func NewWorkoutService(cfgs ...WorkoutConfiguration) (*WorkoutService, error) { - wss := &WorkoutService{} - - for _, cfg := range cfgs { - err := cfg(wss) - - if err != nil { - return nil, err - } - } - return wss, nil -} - -// WithWorkoutRepository applies a given Workout repository to the WorkoutService -func WithWorkoutRepository(wsr workout.WorkoutRepository) WorkoutConfiguration { - // return a function that matches the WorkoutConfiguration alias, - // You need to return this so that the parent function can take in all the needed parameters - return func(wss *WorkoutService) error { - wss.Workouts = wsr - return nil - } -} - -// WithMemoryWorkoutRepository applies a memory Workout repository to the WorkoutService -func WithMemoryWorkoutRepository() WorkoutConfiguration { - // Create the memory repo, if we needed parameters, such as connection strings they could be inputted here - wsr := memory.New() - return WithWorkoutRepository(wsr) -} - -// CreateWorkout will chaintogether all repositories to create a order for a customer -func (wss *WorkoutService) CreateWorkout(wsID uuid.UUID, productIDs []uuid.UUID) error { - // Get the customer - _, err := wss.Workouts.Get(wsID) - if err != nil { - return err - } - - // Get each Product, Ouchie, We need a ProductRepository - - return nil -} diff --git a/examples/ddd/valueobject/hr.go b/examples/ddd/valueobject/hr.go deleted file mode 100644 index 564af8e..0000000 --- a/examples/ddd/valueobject/hr.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package entities holds all the entities that are shared across all subdomains -package entity - -import ( - "time" - - "github.com/google/uuid" -) - -// HeartRate is a entity that represents a heart in all Domains -type HeartRate struct { - // workout id is the id of the workout - workoutId uuid.UUID - // heartRate is the recorded heart rate by the HRM - heartRate float64 - // createdAt is the time when the heartRate was recorded - createdAt time.Time -} diff --git a/examples/ddd/valueobject/hrm.go b/examples/ddd/valueobject/hrm.go deleted file mode 100644 index e84b82c..0000000 --- a/examples/ddd/valueobject/hrm.go +++ /dev/null @@ -1,22 +0,0 @@ -// Package entities holds all the entities that are shared across all subdomains -package entity - -import ( - "time" - - "github.com/google/uuid" -) - -// Person is a entity that represents a person in all Domains -type HeartRateMonitor struct { - // WorkoutID is the ..... - WorkoutID uuid.UUID - // - minHeartRate float32 - // - maxHeartRate float32 - // HardcoreMode is the difficulty level chosen by the player - interval float32 - - createdAt time.Time -} diff --git a/go.work b/go.work new file mode 100644 index 0000000..8421b53 --- /dev/null +++ b/go.work @@ -0,0 +1,7 @@ +go 1.21.1 + +use ( + ./hrm + ./player + ./workout +) diff --git a/hrm/Dockerfile b/hrm/Dockerfile new file mode 100644 index 0000000..cfd08a2 --- /dev/null +++ b/hrm/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.21.3-bookworm + +WORKDIR /app + +COPY . . + +RUN go get -d -v ./... + +RUN go build -o hrm ./cmd + +# TODO: See if you need to expose the port or not +EXPOSE 8004 + +CMD ["./hrm"] diff --git a/hrm/cmd/main.go b/hrm/cmd/main.go index 93f0487..38cf458 100644 --- a/hrm/cmd/main.go +++ b/hrm/cmd/main.go @@ -3,13 +3,16 @@ package main import ( "flag" "fmt" + "os" + "sync" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/adapters/handler" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/adapters/repository" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/services" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/adapters/handler" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/adapters/repository" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/services" "github.com/gin-gonic/gin" ) + var ( repo = flag.String("db", "postgres", "Database for storing messages") // redisHost = "localhost:6379" @@ -18,7 +21,8 @@ var ( ) func main() { - flag.Parse() + // flag.Parse() + fmt.Printf("Application running using %s\n", *repo) switch *repo { @@ -30,19 +34,47 @@ func main() { store := repository.NewMemoryRepository() svc = services.NewHRMService(store) } + var wg sync.WaitGroup + wg.Add(2) + go InitRabbitMQ(&wg) + go svc.SendHRM(&wg) InitRoutes() + wg.Wait() +} + + +func InitRabbitMQ(wg *sync.WaitGroup) { + defer wg.Done() + cfg := NewConfig() + handler.HRMWorkoutBinder(*svc, cfg.RABBITMQ_URL) } func InitRoutes() { router := gin.Default() handler := handler.NewHTTPHandler(*svc) - router.GET("/hrms", handler.ListHRM) - router.POST("/hrm", handler.CreateHRM) - router.GET("/hrms/:id", handler.GetHRM) + router.POST("/hrms", handler.ConnectHRM) // TODO: Implement when needed // router.PUT("/player", handler.UpdatePlayer) - router.Run(":8000") + router.Run(":8004") + +} +// TODO: Handle service configurations properly +type Config struct { + RABBITMQ_URL string +} + +func NewConfig() Config { + return Config{ + RABBITMQ_URL: getEnv("RABBITMQ_URL", "amqp://guest:guest@localhost:5672/"), + } +} + +func getEnv(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue } diff --git a/hrm/go.mod b/hrm/go.mod new file mode 100644 index 0000000..7def3fa --- /dev/null +++ b/hrm/go.mod @@ -0,0 +1,36 @@ +module github.com/CAS735-F23/macrun-teamvsl/hrm + +go 1.21.1 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.1 + github.com/rabbitmq/amqp091-go v1.9.0 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/hrm/go.sum b/hrm/go.sum new file mode 100644 index 0000000..9642a1d --- /dev/null +++ b/hrm/go.sum @@ -0,0 +1,98 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= +github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/hrm/internal/adapters/handler/http.go b/hrm/internal/adapters/handler/http.go index 72974c1..f5fe35c 100644 --- a/hrm/internal/adapters/handler/http.go +++ b/hrm/internal/adapters/handler/http.go @@ -3,9 +3,8 @@ package handler import ( "net/http" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/services" - - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/services" + "github.com/google/uuid" "github.com/gin-gonic/gin" ) @@ -20,68 +19,24 @@ func NewHTTPHandler(HRMService services.HRMService) *HTTPHandler { } } -func (h *HTTPHandler) ListHRM(ctx *gin.Context) { - - hrms, err := h.svc.List() +func (h *HTTPHandler) ConnectHRM(ctx *gin.Context) { - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{ - "error": err.Error(), - }) - return + type tempDTO struct { + HRMId uuid.UUID `json:"hrm_id"` + Connect bool `json:"connect"` } - ctx.JSON(http.StatusOK, hrms) - -} -func (h *HTTPHandler) CreateHRM(ctx *gin.Context) { - var hrms domain.HRM - if err := ctx.ShouldBindJSON(&hrms); err != nil { + var tempDTOInstance tempDTO + if err := ctx.ShouldBindJSON(&tempDTOInstance); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{ "Error": err, }) return } - // TODO: Should this be here or in the service? - // Keeping it here for now so that uuid can be sent back - hrms, err := domain.NewHRM(hrms) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{ - "error": err, - }) - return + if tempDTOInstance.Connect { + h.svc.ConnectHRM(tempDTOInstance.HRMId) + } else { + h.svc.DisconnectHRM(tempDTOInstance.HRMId) } - // TODO: The two error handling are the same, it can be refactored - err = h.svc.Create(hrms) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{ - "error": err, - }) - return - } - - ctx.JSON(http.StatusCreated, gin.H{ - "id": hrms.HRMId, - "message": "New HRM created successfully", - }) -} - -func (h *HTTPHandler) GetHRM(ctx *gin.Context) { - var hid string - hid = ctx.Param("id") - - hrms, err := h.svc.Get(hid) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{ - "error": err, - }) - return - } - ctx.JSON(http.StatusOK, hrms) -} - -// TODO: To be implemented -func (h *HTTPHandler) UpdateHRM(ctx *gin.Context) { - } diff --git a/hrm/internal/adapters/handler/rabbitMQ.go b/hrm/internal/adapters/handler/rabbitMQ.go new file mode 100644 index 0000000..3298a4d --- /dev/null +++ b/hrm/internal/adapters/handler/rabbitMQ.go @@ -0,0 +1,71 @@ +package handler + +import ( + "encoding/json" + "log" + + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/services" + "github.com/google/uuid" + amqp "github.com/rabbitmq/amqp091-go" +) + +func failOnError(err error, msg string) { + if err != nil { + log.Panicf("%s: %s", msg, err) + } +} + +func HRMWorkoutBinder(svc services.HRMService, url string) { + conn, err := amqp.Dial(url) + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "HR-Workout-001", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + msgs, err := ch.Consume( + q.Name, // queue + "", // consumer + true, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + failOnError(err, "Failed to register a consumer") + + var forever chan struct{} + + // TODO: Temp DTO + type tempDTO struct { + WorkoutID uuid.UUID `json:"workoutID"` + HRMId uuid.UUID `json:"hrmID"` + } + + var tempDTOVar tempDTO + go func() { + for d := range msgs { + log.Printf("Received a message: %s", d.Body) + err = json.Unmarshal(d.Body, &tempDTOVar) + failOnError(err, "Failed to unmarshal") + // TODO: Ignoring Error for now, Handle Error later + // Call the following to get the HR Value updated + svc.BindHRMtoWorkout(tempDTOVar.HRMId, tempDTOVar.WorkoutID) + + } + }() + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + <-forever +} diff --git a/hrm/internal/adapters/repository/memory.go b/hrm/internal/adapters/repository/memory.go index f2dce2e..b329ec5 100644 --- a/hrm/internal/adapters/repository/memory.go +++ b/hrm/internal/adapters/repository/memory.go @@ -4,9 +4,9 @@ import ( "fmt" "sync" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/ports" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/ports" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/domain" "github.com/google/uuid" ) @@ -22,20 +22,7 @@ func NewMemoryRepository() *MemoryRepository { } } -func (r *MemoryRepository) List() ([]*domain.HRM, error) { - if r.hrms == nil { - // If r.hrms is nil, return an error or handle the case accordingly - return nil, fmt.Errorf("hrms map doesn't exit %w", ports.ErrorListHRMSFailed) - } - hrms := make([]*domain.HRM, 0, len(r.hrms)) - - for _, hrm := range r.hrms { - hrms = append(hrms, &hrm) - } - return hrms, nil -} - -func (r *MemoryRepository) Create(hrm domain.HRM) error { +func (r *MemoryRepository) AddHRMIntance(hrm domain.HRM) error { if r.hrms == nil { r.Lock() r.hrms = make(map[uuid.UUID]domain.HRM) @@ -43,23 +30,36 @@ func (r *MemoryRepository) Create(hrm domain.HRM) error { } if _, ok := r.hrms[hrm.GetID()]; ok { - return fmt.Errorf("hrm already exist: %w", ports.ErrorCreateHRMFailed) + return fmt.Errorf("hrm already connected: %w", ports.ErrorCreateHRMFailed) } r.Lock() r.hrms[hrm.GetID()] = hrm r.Unlock() return nil + } -func (mr *MemoryRepository) Get(pid uuid.UUID) (*domain.HRM, error) { - if hrm, ok := mr.hrms[pid]; ok { +func (r *MemoryRepository) DeleteHRMInstance(hrmid uuid.UUID) error { + + if _, ok := r.hrms[hrmid]; !ok { + return fmt.Errorf("hrm is not connected: %w", ports.ErrorCreateHRMFailed) + } + r.Lock() + delete(r.hrms, hrmid) + r.Unlock() + return nil + +} + +func (r *MemoryRepository) Get(hrmId uuid.UUID) (*domain.HRM, error) { + if hrm, ok := r.hrms[hrmId]; ok { return &hrm, nil } return &domain.HRM{}, ports.ErrorHRMNotFound } func (r *MemoryRepository) Update(hrm domain.HRM) error { - if _, ok := r.hrms[hrm.GetID()]; ok { + if _, ok := r.hrms[hrm.GetID()]; !ok { return fmt.Errorf("hrm does not exist: %w", ports.ErrorUpdateHRMFailed) } r.Lock() @@ -67,3 +67,15 @@ func (r *MemoryRepository) Update(hrm domain.HRM) error { r.Unlock() return nil } + +func (r *MemoryRepository) List() ([]*domain.HRM, error) { + if r.hrms == nil { + // If r.workouts is nil, return an error or handle the case accordingly + return nil, fmt.Errorf("hrms map doesn't exit %w", ports.ErrorListHRMSFailed) + } + hrms := make([]*domain.HRM, 0, len(r.hrms)) + for _, hrm := range r.hrms { + hrms = append(hrms, &hrm) + } + return hrms, nil +} diff --git a/hrm/internal/core/domain/model.go b/hrm/internal/core/domain/model.go index e755fbf..64422a6 100644 --- a/hrm/internal/core/domain/model.go +++ b/hrm/internal/core/domain/model.go @@ -12,19 +12,10 @@ var ( ) type HRM struct { - // ID is the identifier of the Entity, the ID is shared for all sub domains - HRMId uuid.UUID `json:"hrmid"` - // Name of the user - Status string `json:"status"` -} - -// Player is a entity that represents a Player in all Domains -type HRM struct { - // Email - HRate string `json:"hrate"` + WorkoutId uuid.UUID `json:"workout_id"` + HRMId uuid.UUID `json:"hrm_id"` + HRate string `json:"heart_rate"` CreatedAt time.Time `json:"created_at"` - // UpdatedAt is the time when the player last updated the profile - UpdatedAt time.Time `json:"updated_at"` } // Getters and Setters for HRM @@ -36,14 +27,15 @@ func (hrm *HRM) SetID(id uuid.UUID) { hrm.HRMId = id } -func (hrm *HRM) getState() string { - return hrm.Status -} - -func (hrm *HRM) connectToHRM() { - hrm.Status = "Connected" -} +/* + func (hrm *HRM) getState() string { + return hrm.Status + } + func (hrm *HRM) connectToHRM() { + hrm.Status = "Connected" + } +*/ func (hrm *HRM) getHRate() string { return hrm.HRate } @@ -60,8 +52,6 @@ func NewHRM(hrm HRM) (HRM, error) { hrmN := HRM{ HRMId: hrm.HRMId, CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Status: "connected", } return hrmN, nil } diff --git a/hrm/internal/core/ports/ports.go b/hrm/internal/core/ports/ports.go index bfbc65d..ef5fed1 100644 --- a/hrm/internal/core/ports/ports.go +++ b/hrm/internal/core/ports/ports.go @@ -3,7 +3,8 @@ package ports import ( "errors" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/domain" + "github.com/google/uuid" ) var ( @@ -14,15 +15,17 @@ var ( ) type HRMService interface { - List() ([]*domain.HRM, error) - Get(id string) (*domain.HRM, error) - Create(hrm domain.HRM) error - Update(hrm domain.HRM) (*domain.HRM, error) + ConnectHRM(HRMId uuid.UUID) error + DisconnectHRM(HRMId uuid.UUID) error + BindHRMtoWorkout(HRMId uuid.UUID, workout uuid.UUID) + Get(hrmId uuid.UUID) (*domain.HRM, error) + SendHRM() } type HRMRepository interface { - List() ([]*domain.HRM, error) - Create(hrm domain.HRM) error - Get(id string) (*domain.HRM, error) + AddHRMIntance(hrm domain.HRM) error + DeleteHRMInstance(hrmId uuid.UUID) error + Get(hrmID uuid.UUID) (*domain.HRM, error) Update(hrm domain.HRM) error + List() ([]*domain.HRM, error) } diff --git a/hrm/internal/core/services/services.go b/hrm/internal/core/services/services.go index d94b015..e734133 100644 --- a/hrm/internal/core/services/services.go +++ b/hrm/internal/core/services/services.go @@ -1,30 +1,116 @@ package services import ( - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/ports" + "context" + "encoding/json" + "log" + "math/rand" + "sync" + "time" - "github.com/CAS735-F23/macrun-teamvs_/hrm/internal/core/domain" + amqp "github.com/rabbitmq/amqp091-go" + + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/ports" + "github.com/google/uuid" + + "github.com/CAS735-F23/macrun-teamvsl/hrm/internal/core/domain" ) + + type HRMService struct { repo ports.HRMRepository } -// Factory for creating a new PlayerService + func NewHRMService(repo ports.HRMRepository) *HRMService { return &HRMService{ repo: repo, } } -func (s *HRMService) List() ([]*domain.HRM, error) { - return s.repo.List() +func (s *HRMService) ConnectHRM(hrmID uuid.UUID) { + var h domain.HRM + //var err error + h.HRMId = hrmID + hrm, _ := domain.NewHRM(h) + s.repo.AddHRMIntance(hrm) } -func (s *HRMService) Create(hrm domain.HRM) error { - return s.repo.Create(hrm) +func (s *HRMService) DisconnectHRM(hrmID uuid.UUID) { + s.repo.DeleteHRMInstance(hrmID) } -func (s *HRMService) Get(id string) (*domain.HRM, error) { - return s.repo.Get(id) +func (s *HRMService) BindHRMtoWorkout(hrmID uuid.UUID, workoutID uuid.UUID) { + //var err error + hrmInstance, _ := s.repo.Get(hrmID) + //TODO Error Handling + hrmInstance.WorkoutId = workoutID + s.repo.Update(*hrmInstance) +} + +func (s *HRMService) SendHRM(wg *sync.WaitGroup) { + defer wg.Done() + + conn, err := amqp.Dial("amqp://guest:guest@rabbitmq/") + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "HR-Queue-001", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + failOnError(err, "Failed to declare a queue") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // TODO: Temp DTO + type tempDTO struct { + WorkoutID uuid.UUID `json:"workoutID"` + HRValue int `json:"hrValue"` + } + for { + hrms, _ := s.repo.List() + for i := 0; i < len(hrms); i++ { + min := 30 + max := 200 + var tempDTOVar tempDTO + tempDTOVar.WorkoutID = (*hrms[i]).WorkoutId + tempDTOVar.HRValue = rand.Intn(max-min) + min + + var body []byte + + body, _ = json.Marshal(tempDTOVar) + + err = ch.PublishWithContext(ctx, + "", // exchange + q.Name, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "text/json", + Body: []byte(body), + }) + failOnError(err, "Failed to publish a message") + log.Printf(" [x] Sent %s\n", body) + } + time.Sleep(5 * time.Second) + } + +} + +func failOnError(err error, msg string) { + if err != nil { + log.Panicf("%s: %s", msg, err) + } } diff --git a/player/Dockerfile b/player/Dockerfile new file mode 100644 index 0000000..d8c3676 --- /dev/null +++ b/player/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.21.3-bookworm + +WORKDIR /app + +COPY . . + +RUN go get -d -v ./... + +RUN go build -o player ./cmd + +EXPOSE 8000 + +CMD ["./player"] diff --git a/player/cmd/main.go b/player/cmd/main.go index e732af0..8bddc11 100644 --- a/player/cmd/main.go +++ b/player/cmd/main.go @@ -4,9 +4,9 @@ import ( "flag" "fmt" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/adapters/handler" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/adapters/repository" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/services" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/adapters/handler" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/adapters/repository" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/services" "github.com/gin-gonic/gin" ) diff --git a/go.mod b/player/go.mod similarity index 96% rename from go.mod rename to player/go.mod index e9725f4..9555ba6 100644 --- a/go.mod +++ b/player/go.mod @@ -1,4 +1,4 @@ -module github.com/CAS735-F23/macrun-teamvs_ +module github.com/CAS735-F23/macrun-teamvsl/player go 1.21.1 diff --git a/go.sum b/player/go.sum similarity index 100% rename from go.sum rename to player/go.sum diff --git a/player/internal/adapters/handler/http.go b/player/internal/adapters/handler/http.go index 3672b5a..5977050 100644 --- a/player/internal/adapters/handler/http.go +++ b/player/internal/adapters/handler/http.go @@ -3,9 +3,9 @@ package handler import ( "net/http" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/services" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/services" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/domain" "github.com/gin-gonic/gin" "github.com/google/uuid" diff --git a/player/internal/adapters/repository/memory.go b/player/internal/adapters/repository/memory.go index 1bfeffa..27f20f5 100644 --- a/player/internal/adapters/repository/memory.go +++ b/player/internal/adapters/repository/memory.go @@ -4,9 +4,9 @@ import ( "fmt" "sync" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/ports" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/ports" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/domain" "github.com/google/uuid" ) @@ -59,7 +59,7 @@ func (mr *MemoryRepository) Get(pid uuid.UUID) (*domain.Player, error) { } func (r *MemoryRepository) Update(player domain.Player) error { - if _, ok := r.players[player.GetID()]; ok { + if _, ok := r.players[player.GetID()]; !ok { return fmt.Errorf("player does not exist: %w", ports.ErrorUpdatePlayerFailed) } r.Lock() diff --git a/player/internal/core/ports/ports.go b/player/internal/core/ports/ports.go index eb8533b..25e5807 100644 --- a/player/internal/core/ports/ports.go +++ b/player/internal/core/ports/ports.go @@ -3,7 +3,7 @@ package ports import ( "errors" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/domain" "github.com/google/uuid" ) diff --git a/player/internal/core/services/services.go b/player/internal/core/services/services.go index 2318546..22c9386 100644 --- a/player/internal/core/services/services.go +++ b/player/internal/core/services/services.go @@ -1,9 +1,9 @@ package services import ( - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/ports" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/ports" - "github.com/CAS735-F23/macrun-teamvs_/player/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/player/internal/core/domain" "github.com/google/uuid" ) diff --git a/workout/Dockerfile b/workout/Dockerfile new file mode 100644 index 0000000..b08b22b --- /dev/null +++ b/workout/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.21.3-bookworm + +WORKDIR /app + +COPY . . + +RUN go get -d -v ./... + +RUN go build -o workout ./cmd + +EXPOSE 8001 + +CMD ["./workout"] diff --git a/workout/cmd/main.go b/workout/cmd/main.go index 3075920..ecef00d 100644 --- a/workout/cmd/main.go +++ b/workout/cmd/main.go @@ -3,10 +3,12 @@ package main import ( "flag" "fmt" + "os" + "sync" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/adapters/handler" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/adapters/repository" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/services" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/adapters/handler" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/adapters/repository" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/services" "github.com/gin-gonic/gin" ) @@ -30,19 +32,49 @@ func main() { store := repository.NewMemoryRepository() svc = services.NewWorkoutService(store) } + var wg sync.WaitGroup + wg.Add(1) + go InitRabbitMQ(&wg) InitRoutes() + wg.Wait() +} + +func InitRabbitMQ(wg *sync.WaitGroup) { + defer wg.Done() + cfg := NewConfig() + handler.HRMSubscriber(*svc, cfg.RABBITMQ_URL) } func InitRoutes() { + router := gin.Default() handler := handler.NewHTTPHandler(*svc) router.GET("/workouts", handler.ListWorkouts) router.POST("/workout", handler.StartWorkout) + router.PUT("/workout", handler.StopWorkout) router.GET("/workouts/:id", handler.GetWorkout) // TODO: Implement when needed // router.POST("/player", handler.UpdatePlayer) router.Run(":8001") } + +// TODO: Handle service configurations properly +type Config struct { + RABBITMQ_URL string +} + +func NewConfig() Config { + return Config{ + RABBITMQ_URL: getEnv("RABBITMQ_URL", "amqp://guest:guest@localhost:5672/"), + } +} + +func getEnv(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue +} diff --git a/workout/go.mod b/workout/go.mod new file mode 100644 index 0000000..877441f --- /dev/null +++ b/workout/go.mod @@ -0,0 +1,36 @@ +module github.com/CAS735-F23/macrun-teamvsl/workout + +go 1.21.1 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.1 + github.com/rabbitmq/amqp091-go v1.9.0 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/workout/go.sum b/workout/go.sum new file mode 100644 index 0000000..9642a1d --- /dev/null +++ b/workout/go.sum @@ -0,0 +1,98 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= +github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/workout/internal/adapters/handler/http.go b/workout/internal/adapters/handler/http.go index 94b894a..e2e6250 100644 --- a/workout/internal/adapters/handler/http.go +++ b/workout/internal/adapters/handler/http.go @@ -3,9 +3,9 @@ package handler import ( "net/http" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/services" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/services" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/domain" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -35,24 +35,48 @@ func (h *HTTPHandler) ListWorkouts(ctx *gin.Context) { } +// TODO: Move all the logic to Start() of WorkoutService func (h *HTTPHandler) StartWorkout(ctx *gin.Context) { - var p domain.Workout - if err := ctx.ShouldBindJSON(&p); err != nil { + + // TODO: Temp DTO + type tempDTO struct { + TrailID uuid.UUID `json:"trailID"` + // PlayerID of the player starting the workout session + PlayerID uuid.UUID `json:"playerID"` + // TODO: Remove this field. If hrmId == nil then it's the same thing + HRMConnected bool `json:"hrmConnected"` + + HRMId uuid.UUID `json:"hrmID"` + } + var w domain.Workout + var tempDTOInstance tempDTO + if err := ctx.ShouldBindJSON(&tempDTOInstance); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{ "Error": err, }) return } + w.PlayerID = tempDTOInstance.PlayerID + w.TrailID = tempDTOInstance.TrailID // TODO: Should this be here or in the service? + // @Samkith : I think in the service // Keeping it here for now so that uuid can be sent back - workout, err := domain.NewWorkout(p) + // TODO: Only one workout can be active for a given player at any given time, handle that case + workout, err := domain.NewWorkout(w) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{ "error": err, }) return } + + // Send request to tie HRM to Workout + if tempDTOInstance.HRMConnected { + // TODO: Move to the service later along with the above code + StartHRM(tempDTOInstance.HRMId, workout.ID) + } + // TODO: The two error handling are the same, it can be refactored err = h.svc.Start(workout) if err != nil { @@ -68,6 +92,36 @@ func (h *HTTPHandler) StartWorkout(ctx *gin.Context) { }) } +func (h *HTTPHandler) StopWorkout(ctx *gin.Context) { + + // TODO: Temp DTO + type tempDTO struct { + // WorkoutID + WorkoutID uuid.UUID `json:"workoutID"` + } + + var tempDTOInstance tempDTO + if err := ctx.ShouldBindJSON(&tempDTOInstance); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "Error": err, + }) + return + } + + var w *domain.Workout + var err error + // TODO: The two error handling are the same, it can be refactored + w, err = h.svc.Stop(tempDTOInstance.WorkoutID) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": err, + }) + return + } + + ctx.JSON(http.StatusAccepted, w) +} + func (h *HTTPHandler) GetWorkout(ctx *gin.Context) { id, err := uuid.Parse(ctx.Param("id")) if err != nil { @@ -87,7 +141,7 @@ func (h *HTTPHandler) GetWorkout(ctx *gin.Context) { ctx.JSON(http.StatusOK, workout) } -// TODO: To be implemented +// TODO: This should probably be in the rabbitmq handler func (h *HTTPHandler) UpdateWorkout(ctx *gin.Context) { } diff --git a/workout/internal/adapters/handler/rabbitmq.go b/workout/internal/adapters/handler/rabbitmq.go new file mode 100644 index 0000000..e29e643 --- /dev/null +++ b/workout/internal/adapters/handler/rabbitmq.go @@ -0,0 +1,125 @@ +package handler + +import ( + "context" + "encoding/json" + "log" + "time" + + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/services" + "github.com/google/uuid" + amqp "github.com/rabbitmq/amqp091-go" +) + +func failOnError(err error, msg string) { + if err != nil { + log.Panicf("%s: %s", msg, err) + } +} + +func HRMSubscriber(svc services.WorkoutService, url string) { + conn, err := amqp.Dial(url) + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "HR-Queue-001", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + msgs, err := ch.Consume( + q.Name, // queue + "", // consumer + true, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + failOnError(err, "Failed to register a consumer") + + var forever chan struct{} + + type tempDTO struct { + WorkoutID uuid.UUID `json:"workoutID"` + HRValue uint16 `json:"hrValue"` + } + + var tempDTOVar tempDTO + go func() { + for d := range msgs { + log.Printf("Received a message: %s", d.Body) + err = json.Unmarshal(d.Body, &tempDTOVar) + // TODO: Ignoring Error for now, Handle Error later + // Call the following to get the HR Value updated + svc.UpdateHRValue(tempDTOVar.WorkoutID, tempDTOVar.HRValue) + + } + }() + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + <-forever +} + +func StartHRM(hrmID uuid.UUID, workoutID uuid.UUID) { + + // Just connect for now and send + // TODO: Should we connect once and use the same for sending and receiving? + + conn, err := amqp.Dial("amqp://guest:guest@rabbitmq:5672/") + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "HR-Workout-001", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + failOnError(err, "Failed to declare a queue") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // TODO: Temp DTO + type tempDTO struct { + WorkoutID uuid.UUID `json:"workoutID"` + HRMId uuid.UUID `json:"hrmID"` + } + + var tempDTOVar tempDTO + tempDTOVar.WorkoutID = workoutID + tempDTOVar.HRMId = hrmID + + var body []byte + + body, _ = json.Marshal(tempDTOVar) + + err = ch.PublishWithContext(ctx, + "", // exchange + q.Name, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "text/json", + Body: []byte(body), + }) + failOnError(err, "Failed to publish a message") + log.Printf(" [x] Sent %s\n", body) +} diff --git a/workout/internal/adapters/repository/memory.go b/workout/internal/adapters/repository/memory.go index e46170e..6b3900c 100644 --- a/workout/internal/adapters/repository/memory.go +++ b/workout/internal/adapters/repository/memory.go @@ -4,9 +4,9 @@ import ( "fmt" "sync" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/ports" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/ports" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/domain" "github.com/google/uuid" ) @@ -59,7 +59,7 @@ func (mr *MemoryRepository) Get(pid uuid.UUID) (*domain.Workout, error) { } func (r *MemoryRepository) Update(workout domain.Workout) error { - if _, ok := r.workouts[workout.GetID()]; ok { + if _, ok := r.workouts[workout.GetID()]; !ok { return fmt.Errorf("workout does not exist: %w", ports.ErrorUpdateWorkoutFailed) } r.Lock() diff --git a/workout/internal/core/domain/model.go b/workout/internal/core/domain/model.go index d5eda47..be4e9c5 100644 --- a/workout/internal/core/domain/model.go +++ b/workout/internal/core/domain/model.go @@ -17,23 +17,24 @@ type Workout struct { // ID is the identifier of the Entity, the ID is shared for all sub domains ID uuid.UUID `json:"id"` // trailId is the id of the trail player is on - TrailID uuid.UUID `json:"trailID"` + TrailID uuid.UUID `json:"trail_id"` // PlayerID of the player starting the workout session - PlayerID uuid.UUID `json:"playerID"` + PlayerID uuid.UUID `json:"player_id"` // InProgress tells whether the workout is in progress - InProgress bool `json:"IsProgress"` + IsCompleted bool `json:"is_completed"` // CreatedAt is the time when the workout was started/created at? CreatedAt time.Time `json:"created_at"` // Duration of the workout, TODO: fix type - EndedAt string `json:"ended_at"` + EndedAt time.Time `json:"ended_at"` // DurationCovered is the total distance covered during the session - DistanceCovered float64 `json:"DistancCovered"` + DistanceCovered float64 `json:"distance_covered"` // TODO: temp value. It can be either "cardio", "physical" or "dynamic" Category string `json:"category"` // HardcoreMode is the difficulty level chosen by the player HardcoreMode bool `json:"HardcoreMode"` // HRM Reading from the workout - // heartRate []valueobject.HeartRate + // TODO: HeartRate should be a valueobject of hrmValue + created_at + HeartRate []uint16 } func NewWorkout(w Workout) (Workout, error) { @@ -46,11 +47,12 @@ func NewWorkout(w Workout) (Workout, error) { PlayerID: w.PlayerID, TrailID: w.TrailID, Category: w.Category, - InProgress: true, + IsCompleted: false, HardcoreMode: w.HardcoreMode, CreatedAt: time.Now(), - EndedAt: "", + EndedAt: time.Time{}, DistanceCovered: 0, + HeartRate: []uint16{}, // heartRates: make([]valueobject.HeartRate, 0), }, nil } diff --git a/workout/internal/core/ports/ports.go b/workout/internal/core/ports/ports.go index 701399c..d2bda26 100644 --- a/workout/internal/core/ports/ports.go +++ b/workout/internal/core/ports/ports.go @@ -3,7 +3,7 @@ package ports import ( "errors" - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/domain" "github.com/google/uuid" ) @@ -19,15 +19,8 @@ type WorkoutService interface { // TODO: List should only return workouts for a particular Player List() ([]*domain.Workout, error) Get(id uuid.UUID) (*domain.Workout, error) - - // TODO: Implement their logic - // RequestHR() - // Get HRData() Start(workout domain.Workout) error - Pause(workout domain.Workout) error Stop(workout domain.Workout) (*domain.Workout, error) - - // Add HRM Service too? } type WorkoutRepository interface { diff --git a/workout/internal/core/services/services.go b/workout/internal/core/services/services.go index ea0963d..902f723 100644 --- a/workout/internal/core/services/services.go +++ b/workout/internal/core/services/services.go @@ -1,10 +1,10 @@ package services import ( - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/ports" - - "github.com/CAS735-F23/macrun-teamvs_/workout/internal/core/domain" + "time" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/domain" + "github.com/CAS735-F23/macrun-teamvsl/workout/internal/core/ports" "github.com/google/uuid" ) @@ -31,7 +31,9 @@ func (s *WorkoutService) Get(id uuid.UUID) (*domain.Workout, error) { func (s *WorkoutService) Start(workout domain.Workout) error { // this will create the workout // Send request Get HRM - return s.repo.Create(workout) + var temp = s.repo.Create(workout) + + return temp } func (s *WorkoutService) Pause(id uuid.UUID) (*domain.Workout, error) { @@ -41,5 +43,33 @@ func (s *WorkoutService) Pause(id uuid.UUID) (*domain.Workout, error) { func (s *WorkoutService) Stop(id uuid.UUID) (*domain.Workout, error) { // Call Update() to update InProgress to False & EndedAt to time.Now() - return s.repo.Get(id) + var tempWorkout *domain.Workout + var err error + tempWorkout, err = s.repo.Get(id) + + // TODO: Better error handling + if err != nil { + return nil, err + } + // TODO: More logic to find distance covered and other things + tempWorkout.EndedAt = time.Now() + tempWorkout.IsCompleted = true + + s.repo.Update(*tempWorkout) + + return tempWorkout, err +} + +func (s *WorkoutService) UpdateHRValue(workoutID uuid.UUID, hrValue uint16) error { + var tempWorkout *domain.Workout + var err error + tempWorkout, err = s.Get(workoutID) + + if err != nil { + return nil + } + + tempWorkout.HeartRate = append(tempWorkout.HeartRate, hrValue) + s.repo.Update(*tempWorkout) + return nil }