├── .dockerignore ├── .gitignore ├── Calculator.exe ├── LICENSE ├── README.Docker.md ├── README.md ├── app.exe ├── backend ├── cmd │ └── app │ │ ├── Dockerfile │ │ └── main.go ├── handlers │ ├── addExpression.go │ ├── getCalcTimes.go │ ├── getExpressionByID.go │ ├── getExpressions.go │ ├── getScheme.go │ ├── getServers.go │ ├── login.go │ ├── logout.go │ └── register.go ├── internal │ ├── cacheMaster │ │ └── cacheManager.go │ ├── calculator │ │ ├── calculator.go │ │ └── changeNotation.go │ ├── database │ │ └── database.db │ ├── databaseManager │ │ ├── librarian.go │ │ └── userManager.go │ ├── orchestratorAndAgents │ │ └── orchestratorAndAgent.go │ └── queueMaster │ │ └── queueManager.go ├── middleware │ └── checkLogin.go ├── pkg │ ├── models │ │ ├── JWT.go │ │ ├── expression.go │ │ ├── operations.go │ │ ├── serversData.go │ │ ├── stack.go │ │ ├── templateMessage.go │ │ └── userJWT.go │ └── utils │ │ └── utils.go └── tests │ ├── JWT_test.go │ ├── cache_test.go │ ├── calculator_test.go │ ├── models_test.go │ ├── orchestra_test.go │ ├── queueManager_test.go │ ├── servers_test.go │ ├── stack_test.go │ ├── templateMessage_test.go │ ├── test.db │ └── utils_test.go ├── calculationServer └── cmd │ └── server │ ├── Dockerfile │ └── main.go ├── compose.yaml ├── go.mod ├── go.sum ├── proto ├── agent.pb.go ├── agent.proto └── agent_grpc.pb.go ├── server.exe ├── start_app.bat └── static └── assets ├── create_expression.html ├── distributed-calculator.html ├── edit_time.html ├── expression_by_id.html ├── icon1.ico ├── login.html ├── logout.html ├── register.html ├── scheme.html ├── server_data.html ├── style.css └── view_expressions.html /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.DS_Store 8 | **/.classpath 9 | **/.dockerignore 10 | **/.env 11 | **/.git 12 | **/.gitignore 13 | **/.project 14 | **/.settings 15 | **/.toolstarget 16 | **/.vs 17 | **/.vscode 18 | **/*.*proj.user 19 | **/*.dbmdl 20 | **/*.jfm 21 | **/bin 22 | **/charts 23 | **/docker-compose* 24 | **/compose* 25 | backend/cmd/app/Dockerfile 26 | **/node_modules 27 | **/npm-debug.log 28 | **/obj 29 | **/secrets.dev.yaml 30 | **/values.dev.yaml 31 | LICENSE 32 | README.md 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.dll 6 | *.so 7 | *.dylib 8 | .idea 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | # Go workspace file 20 | go.work 21 | -------------------------------------------------------------------------------- /Calculator.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KFN002/distributed-arithmetic-expression-evaluator/2afca86a33e27a96ee0ed50316e9aed3eb968737/Calculator.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Fedotov Kirill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.Docker.md: -------------------------------------------------------------------------------- 1 | ### Building and running your application 2 | 3 | When you're ready, start your application by running: 4 | `docker compose up --build`. 5 | 6 | Your application will be available at http://localhost:8080. 7 | 8 | ### Deploying your application to the cloud 9 | 10 | First, build your image, e.g.: `docker build -t myapp .`. 11 | If your cloud uses a different CPU architecture than your development 12 | machine (e.g., you are on a Mac M1 and your cloud provider is amd64), 13 | you'll want to build the image for that platform, e.g.: 14 | `docker build --platform=linux/amd64 -t myapp .`. 15 | 16 | Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. 17 | 18 | Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) 19 | docs for more detail on building and pushing. 20 | 21 | ### References 22 | * [Docker's Go guide](https://docs.docker.com/language/golang/) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # _**Distributed-Arithmetic-Expression-Evaluator**_ 2 | 3 | --- 4 | ## Задача: 5 | 6 | **_Пользователь хочет считать арифметические выражения. Он вводит строку 2 + 2 * 2 и хочет получить в ответ 6. Но наши операции сложения и умножения (также деления и вычитания) выполняются "очень-очень" долго. Поэтому вариант, при котором пользователь делает http-запрос и получает в качетсве ответа результат, невозможна. Более того: вычисление каждой такой операции в нашей "альтернативной реальности" занимает "гигантские" вычислительные мощности. Соответственно, каждое действие мы должны уметь выполнять отдельно и масштабировать эту систему можем добавлением вычислительных мощностей в нашу систему в виде новых "машин". Поэтому пользователь, присылая выражение, получает в ответ идентификатор выражения и может с какой-то периодичностью уточнять у сервера "не посчиталость ли выражение"? Если выражение наконец будет вычислено - то он получит результат. Помните, что некоторые части арфиметического выражения можно вычислять параллельно._** 7 | 8 | --- 9 | 10 | ## "Стартуем, я сказала стартуем!" 11 | 12 | 13 | **Установка:** 14 | * клонируйте репозиторий или скачайте .zip архив и распакуйте в любое удобное место 15 | 16 | **Быстрый запуск:** 17 | * Запустите файл `Calculator.exe` - через 5 секунд после запуска Вас перекинет в дефолтный браузер с открытой вкладкой localhost:8080 18 | 19 | **Перед запуском убедитесь, что у вас последняя версия и порты 8080 и 8050 свободны** 20 | 21 | **Система не на Windows? Создайте свои исполняемые файлы!** 22 | 23 | 24 | 25 | * Пример: 26 | 27 | 28 | 29 | ``` 30 | env GOOS=target-OS GOARCH=target-architecture go build package-import-path 31 | ``` 32 | 33 | 34 | 35 | * Таблица нужных значений GOOS и GOARCH 36 | 37 | | GOOS | GOARCH | 38 | |-----------|----------| 39 | | android | arm | 40 | | darwin | 386 | 41 | | darwin | amd64 | 42 | | darwin | arm | 43 | | darwin | arm64 | 44 | | linux | 386 | 45 | | linux | amd64 | 46 | | linux | arm | 47 | | linux | arm64 | 48 | | linux | ppc64 | 49 | | linux | ppc64le | 50 | | linux | mips | 51 | | linux | mipsle | 52 | | linux | mips64 | 53 | | linux | mips64le | 54 | | windows | 386 | 55 | | windows | amd64 | 56 | 57 | 58 | 59 | * В терминале, находясь в папке проекта: 60 | 61 | 62 | ``` 63 | go build ./backend/cmd/app 64 | 65 | go build ./calculationServer/cmd/server 66 | ``` 67 | 68 | 69 | 70 | * Запускайте файлы (оба)! 71 | 72 | --- 73 | 74 | ## Что теперь? 75 | 76 | * **Куда идти потом?** [http://localhost:8080/](http://localhost:8080/) 77 | 78 | 79 | * Навигация на страницах имеет говорящие названия: 80 | * `Create Expression` - создание выражения: **Пользовательский ввод арифметического выражения** -> _ID и статус задачи_. 81 | * `Expressions` - таблица со всеми выражениями из БД с колонками: _ID, Status, Expression, Result, Creation Date, Completion Date_. 82 | * `Expression by ID` - получение данных о задаче по ID: **Пользовательский ввод числа** -> _данные о задаче_. Если ID больше, чем есть задач, то будет ошибка (_failed to fetch an expression_). 83 | * `Edit Time` - Изменение времени выполнения операций: **Пользовательский ввод числа/чисел** -> изменение времени выполнения операций. 84 | * `Server Data` - Данные о воркерах (серверах/горутинах): _ID "сервера", статус, задание, которое выполняет, последний ответ на запрос о состоянии_. 85 | * `Project Scheme`- Схема проекта 86 | * `Log In` - Вход в аккаунт 87 | * `Sign Up` - Регистрация аккаунта 88 | * `Logout` - Выход из аккаунта 89 | 90 | 91 | * Запуск Ручками: `calculaionServer/cmd/server/main.go` и `backend/cmd/app/main.go` 92 | 93 | 94 | * Если будет проблема с _GCC_, надо будет его установить и добавить в переменные окружения: 95 | * [StackOverflow](https://stackoverflow.com/questions/43580131/exec-gcc-executable-file-not-found-in-path-when-trying-go-build) 96 | * [Discourse GoHugo](https://discourse.gohugo.io/t/golang-newbie-keen-to-contribute/35087) 97 | * [GitHub Issue](https://github.com/golang/go/issues/47215) 98 | 99 | --- 100 | 101 | ## Как все устроено? 102 | 103 | 1. `backend` - весь бэк по модулям 104 | 2. `static` - весь фронт: 105 | * `assets` - .html templates и .css файлы 106 | 3. `proto` - для gRPC 107 | 4. `calculationServer` - подсчет выражений 108 | 109 | --- 110 | 111 | ## Поподробнее о `backend`, но не слишком: 112 | 113 | 1. `dataManager` **_- internal_** 114 | * `librarian.go` - отвечает за работу с базой данных, то есть там находятся все функции для получения/изменения данных в базе 115 | * `userManager.go` - работа с данными пользователя в бд 116 | 2. `orchestratorAndAgent` 117 | * `orchestratorAndAgent.go` - занимается получением ответа на выражение: получает выражение через очередь от оркестратора и распределяет его на мелкие таски, считает, а после собирает весь ответ и закидывает в базу данных 118 | 3. `handlers` 119 | * `handlers.go` - обработка запросов пользователя: получение всех выражений, получение выражения по id, изменение времени работы операции и т.д. 120 | 4. `models` **_- pkg_** 121 | * `expression` - структура выражения 122 | * `operations` - структура числовых операций: время их выполнения 123 | * `serversData` - структура и пара функций для серверов (горутин) - их состояния, таски. В этом же файле можно масштабироваться (менять переменную - количество серверов (горутин)) 124 | * `stack` - стек с методами 125 | * `JWT` - парсинг и создание токена 126 | * `userJWT` - работа с sessionStorage и JWT 127 | * `templateMessage` - структура и ее методы (для работы с шаблонизатором) 128 | 5. `cmd` 129 | * `main.go` **_(app)_** - запуск сервера, раньше там же был и файл handlers, но разумнее оказалось его закинуть в отдельный модуль 130 | 6. `tests` - тесты, везде говорящие имена 131 | 7. `utils` **_- internal_** 132 | * `utils.go` - вспомогательные функции, в основном проверка корректности выражения 133 | 8. `cacheMaster` **_- internal_** 134 | * `cacheMaster.go` - кэш для хранения времени операций, чтобы часто не обращаться к базе данных. Удобно, так как время операций назначает пользователь (само не меняется) 135 | 9. `queueMaster` **_- internal_** 136 | * `queueMaster.go` - реализация очереди 137 | 10. `calculator` **_- internal_** 138 | * `calculator.go` - расчет простых выражений - не используется после 1.02 139 | * `changeNotation.go` - изменение нотации выражения 140 | 141 | --- 142 | 143 | ## Что делать и как работать? 144 | * Видеоинструкция-экскурсия 145 | 146 | 147 | https://github.com/KFN002/distributed-arithmetic-expression-evaluator/assets/119512897/d8048a68-eed3-49e9-87ac-0406d7336c5b 148 | 149 | 150 | --- 151 | 152 | ## Примеры для ввода в поле выражения: 153 | 154 | 1. _2+2_ 155 | 2. _3+4_ 156 | 3. _3*8_ 157 | 4. _7/7_ 158 | 5. _3-2_ 159 | 6. _2-3_ 160 | 7. _6/9_ 161 | 8. _6/0_ 162 | 9. _7**2_ 163 | 10. _((2-1)))_ 164 | 11. _2+2*2_ 165 | 12. _(3-4/2)_ 166 | 13. _17/(2-2)_ 167 | 14. _11-4*(3+5)_ 168 | 15. _81+12*(11+1)_ 169 | 170 | --- 171 | 172 | ## FAQ 173 | 174 | 1. Программа стартует и тут же закрывается. 175 | * Проверьте свободность портов 8080 и 8050 176 | 2. Сервер падает после изменения бд руками. 177 | * Удалите все данные в БД, но не саму БД. 178 | 3. Не могу найти выражение с ID... 179 | * Вам доступны только Ваши выражения, чужие смотреть нельзя. 180 | 4. Сколько живет JWT? 181 | * 24 часа. 182 | 5. Как почистить JWT руками вне программы? 183 | * Используйте ClearJWTSessionStorage (в `pkg/models`) 184 | 6. Логи и их смысл. 185 | * Проверка работоспособности всех частей кода (все работает как надо), отладка, перешедшая в базовый набор логов. 186 | 7. Где хранится JWT? 187 | * В Session Storage пользователя. 188 | 8. Как общается основной сервер с вычислительным? 189 | * С помощью gRPC. 190 | 9. Что делать после истечения срока работы JWT? 191 | * Ничего, система автоматически (с помощью проверки в middleware) попросит вас заново пройти аутентификацию. 192 | 10. Я изменил БД (стер все данные) и зашел обратно на сайт, но меня выбросило на страницу login. 193 | * Все верно, когда система не находит Вас среди пользователей в БД, она автоматически удаляет Ваш JWT и просит заново пройти авторизацию. 194 | 195 | --- 196 | ## Что-то непонятно или не работает - лучше звоните Солу! 197 | 198 | ### TG: @RR7B7 199 | 200 | 201 | -------------------------------------------------------------------------------- /app.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KFN002/distributed-arithmetic-expression-evaluator/2afca86a33e27a96ee0ed50316e9aed3eb968737/app.exe -------------------------------------------------------------------------------- /backend/cmd/app/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a base image with Golang installed 2 | FROM golang:1.21.0 AS build 3 | 4 | # Set the working directory 5 | WORKDIR /app 6 | 7 | # Copy the go.mod and go.sum files 8 | COPY go.mod . 9 | COPY go.sum . 10 | 11 | # Copy the source code into the container 12 | COPY ./backend/cmd/app . 13 | 14 | # Build the server binary 15 | RUN CGO_ENABLED=1 go build -o backend-server . 16 | 17 | # Use a lightweight base image for the final container 18 | FROM alpine:latest 19 | 20 | # Set the working directory 21 | WORKDIR /app 22 | 23 | # Copy the server binary from the build stage 24 | COPY --from=build /app/backend-server /app/ 25 | 26 | # Expose the port used by your backend server 27 | EXPOSE 8080 28 | 29 | # Command to run the backend server 30 | CMD ["./backend-server"] 31 | -------------------------------------------------------------------------------- /backend/cmd/app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/handlers" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 7 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/orchestratorAndAgents" 8 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/queueMaster" 9 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/middleware" 10 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 11 | "github.com/gorilla/mux" 12 | "log" 13 | "net/http" 14 | "os" 15 | ) 16 | 17 | func main() { 18 | r := mux.NewRouter() 19 | 20 | r.HandleFunc("/", middleware.JWTMiddleware(handlers.HandleExpressions)) 21 | r.HandleFunc("/expressions", middleware.JWTMiddleware(handlers.HandleExpressions)) 22 | r.HandleFunc("/change-calc-time", middleware.JWTMiddleware(handlers.HandleChangeCalcTime)) 23 | r.HandleFunc("/add-expression", middleware.JWTMiddleware(handlers.HandleAddExpression)) 24 | r.HandleFunc("/current-servers", middleware.JWTMiddleware(handlers.HandleCurrentServers)) 25 | r.HandleFunc("/expression-by-id", middleware.JWTMiddleware(handlers.HandleGetExpressionByID)) 26 | r.HandleFunc("/scheme", middleware.JWTMiddleware(handlers.HandleGetScheme)) 27 | r.HandleFunc("/logout", middleware.JWTMiddleware(handlers.HandleLogout)) 28 | 29 | r.HandleFunc("/login", handlers.HandleLogin) 30 | r.HandleFunc("/signup", handlers.HandleRegister) 31 | 32 | fileServer := http.FileServer(http.Dir("static/assets/")) 33 | r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", fileServer)) 34 | 35 | log.Println("Перейти в интерфейс:", "http://localhost:8080/") 36 | 37 | // подгрузка заданий для калькуляции 38 | data, err := databaseManager.DB.ToCalculate() 39 | if err != nil { 40 | log.Println("Expression error") 41 | log.Println("Error fetching data from the database:", err) 42 | return 43 | } 44 | 45 | err = cacheMaster.LoadOperationTimesIntoCache() 46 | if err != nil { 47 | log.Println("Error while caching data and updating user info:", err) 48 | return 49 | } 50 | 51 | log.Println(cacheMaster.OperationCache) 52 | 53 | // подключение "серверов" 54 | go models.Servers.InitServers() 55 | go models.Servers.RunServers() 56 | 57 | go queueMaster.ExpressionsQueue.EnqueueList(data) // загрузка в очередь выражений, которые мы не посчитали 58 | 59 | go orchestratorAndAgents.QueueHandler() // начало работы обработчика данных - постоянно читает данные из очереди 60 | 61 | port := os.Getenv("PORT") 62 | if port == "" { 63 | port = "8080" 64 | } 65 | log.Printf("Server started on port %s", port) 66 | log.Fatal(http.ListenAndServe(":"+port, r)) 67 | } 68 | -------------------------------------------------------------------------------- /backend/handlers/addExpression.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/queueMaster" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 7 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/utils" 8 | "html/template" 9 | "log" 10 | "net/http" 11 | "regexp" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | var ( 18 | mu sync.Mutex 19 | exampleExpression = models.Expression{ 20 | ID: 0, 21 | Expression: "0", 22 | Status: "example", 23 | CreatedAt: time.Now().Format("02-01-2006 15:04:05"), 24 | } 25 | ) 26 | 27 | // HandleAddExpression добавление выражения 28 | func HandleAddExpression(w http.ResponseWriter, r *http.Request) { 29 | 30 | userID := r.Context().Value("userID").(float64) 31 | login := r.Context().Value("login").(string) 32 | 33 | log.Println("User request:", userID, login) 34 | 35 | tmpl, err := template.ParseFiles("static/assets/create_expression.html") 36 | if err != nil { 37 | log.Println("Error parsing create_expression.html template:", err) 38 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | if r.Method == http.MethodPost { 43 | input := r.FormValue("expression") 44 | 45 | input = strings.ReplaceAll(input, " ", "") 46 | 47 | validPattern := `^[0-9+\-*/()]+$` 48 | match, err := regexp.MatchString(validPattern, input) 49 | if err != nil { 50 | log.Println("Error in regular expression matching:", err) 51 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | var status string 56 | 57 | if !match || !utils.CheckExpression(input) { 58 | status = "parsing error" 59 | } else { 60 | status = "processing" 61 | } 62 | 63 | hasDuplicate, err := databaseManager.DB.CheckDuplicate(input, int(userID)) 64 | if err != nil { 65 | log.Println("Error fetching data", err) 66 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 67 | return 68 | } 69 | 70 | expression := models.NewExpression(input, status, int(userID)) 71 | 72 | if hasDuplicate { 73 | var newExpression models.Expression 74 | newExpression.ID, err = databaseManager.DB.GetId(input) 75 | if err != nil { 76 | log.Println("Error fetching data", err) 77 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 78 | return 79 | } 80 | 81 | originalExpression, err := databaseManager.DB.FetchExpressionByID(newExpression.ID, int(userID)) 82 | if err != nil { 83 | log.Println("Error fetching data", err) 84 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 85 | return 86 | } 87 | 88 | newExpression.Status = originalExpression.Status 89 | err = tmpl.Execute(w, newExpression) 90 | if err != nil { 91 | log.Println("Error executing create_expression.html template:", err) 92 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 93 | return 94 | } 95 | return 96 | } 97 | 98 | mu.Lock() 99 | defer mu.Unlock() 100 | 101 | result, err := databaseManager.DB.DB.Exec("INSERT INTO expressions (expression, status, time_start, user_id) VALUES (?, ?, ?, ?)", 102 | expression.Expression, expression.Status, expression.CreatedAt, userID) // добавление бд в валидного выражения 103 | 104 | if err != nil { 105 | log.Println("Error inserting expression into database:", err) 106 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 107 | return 108 | } 109 | 110 | expressionID, err := result.LastInsertId() 111 | if err != nil { 112 | log.Println("Error getting last insert ID:", err) 113 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 114 | return 115 | } 116 | 117 | expression.ID = int(expressionID) 118 | if expression.Status == "processing" { // добавление в очередь валидного выражения 119 | queueMaster.ExpressionsQueue.Enqueue(expression) 120 | log.Println("added to queue") 121 | } 122 | 123 | err = tmpl.Execute(w, expression) 124 | if err != nil { 125 | log.Println("Error executing create_expression.html template:", err) 126 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 127 | return 128 | } 129 | 130 | return 131 | } 132 | 133 | err = tmpl.Execute(w, nil) 134 | if err != nil { 135 | log.Println("Error executing create_expression.html template:", err) 136 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 137 | return 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /backend/handlers/getCalcTimes.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 7 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 8 | "html/template" 9 | "log" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | func HandleChangeCalcTime(w http.ResponseWriter, r *http.Request) { 15 | userID := r.Context().Value("userID").(float64) 16 | 17 | if r.Method == http.MethodPost { 18 | timeValues := map[int]int{} 19 | 20 | for i := 1; i <= 4; i++ { 21 | field := strconv.Itoa(i) 22 | timeStr := r.FormValue(field) 23 | timeValue, err := strconv.Atoi(timeStr) 24 | if err != nil || timeValue <= 0 { 25 | http.Error(w, fmt.Sprintf("Invalid input for %s", field), http.StatusBadRequest) 26 | return 27 | } 28 | timeValues[i] = timeValue 29 | } 30 | 31 | log.Println("Got from input form", timeValues) 32 | 33 | for id, value := range timeValues { 34 | err := databaseManager.DB.UpdateOperationTime(value, cacheMaster.OperatorByID[id-1], int(userID)) 35 | if err != nil { 36 | log.Println("Error updating database:", err) 37 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 38 | return 39 | } 40 | cacheMaster.OperationCache.Set(int(userID), id-1, value) 41 | } 42 | 43 | log.Println("After post cache:", cacheMaster.OperationCache.GetList(int(userID))) 44 | 45 | http.Redirect(w, r, "/change-calc-time", http.StatusSeeOther) 46 | return 47 | 48 | } else if r.Method == http.MethodGet { 49 | tmpl, err := template.ParseFiles("static/assets/edit_time.html") 50 | if err != nil { 51 | log.Println("Error parsing edit_time.html template:", err) 52 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 53 | return 54 | } 55 | 56 | operationTimes := cacheMaster.OperationCache.GetList(int(userID)) 57 | 58 | if len(operationTimes) == 0 { 59 | operationTimes, err = databaseManager.DB.GetTimes(int(userID)) 60 | if err != nil { 61 | log.Println("Error fetching times", err) 62 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 63 | return 64 | } 65 | cacheMaster.OperationCache.SetList(int(userID), operationTimes) 66 | } 67 | 68 | log.Println("Got from cache in get request:", operationTimes) 69 | 70 | operationsData := models.OperationTimes{ 71 | Time1: operationTimes[0], 72 | Time2: operationTimes[1], 73 | Time3: operationTimes[2], 74 | Time4: operationTimes[3], 75 | } 76 | 77 | err = tmpl.Execute(w, operationsData) 78 | if err != nil { 79 | log.Println("Error executing edit_time.html template:", err) 80 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 81 | return 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /backend/handlers/getExpressionByID.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | "strconv" 10 | ) 11 | 12 | // HandleGetExpressionByID получение выражения по id 13 | func HandleGetExpressionByID(w http.ResponseWriter, r *http.Request) { 14 | 15 | userID := r.Context().Value("userID").(float64) 16 | 17 | tmpl, err := template.ParseFiles("static/assets/expression_by_id.html") 18 | if err != nil { 19 | log.Println("Error parsing expression_by_id.html template:", err) 20 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 21 | return 22 | } 23 | 24 | userMessage := models.CreateNewTemplateMessage() 25 | userMessage.AddData("", &exampleExpression) 26 | 27 | if r.Method == http.MethodPost { 28 | 29 | idStr := r.FormValue("id") 30 | id, err := strconv.Atoi(idStr) 31 | if err != nil { 32 | userMessage.ChangeMessage("Invalid ID") 33 | err = tmpl.Execute(w, userMessage) 34 | if err != nil { 35 | log.Println("Error executing expression_by_id.html template:", err) 36 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 37 | return 38 | } 39 | return 40 | } 41 | 42 | expression, err := databaseManager.DB.FetchExpressionByID(id, int(userID)) 43 | if err != nil { 44 | userMessage.ChangeMessage("Expression not found or invalid user status") 45 | err = tmpl.Execute(w, userMessage) 46 | if err != nil { 47 | log.Println("Error executing expression_by_id.html template:", err) 48 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 49 | return 50 | } 51 | return 52 | } 53 | 54 | userMessage.ChangeExpression(expression) 55 | userMessage.ChangeMessage("Expression found!") 56 | 57 | err = tmpl.Execute(w, userMessage) 58 | if err != nil { 59 | log.Println("Error executing expression_by_id.html template:", err) 60 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 61 | return 62 | } 63 | } else if r.Method == http.MethodGet { 64 | err = tmpl.Execute(w, userMessage) 65 | if err != nil { 66 | log.Println("Error executing expression_by_id.html template:", err) 67 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 68 | return 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /backend/handlers/getExpressions.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/utils" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | // HandleExpressions возвращает страницу с данными выражений 12 | func HandleExpressions(w http.ResponseWriter, r *http.Request) { 13 | 14 | userID := r.Context().Value("userID").(float64) 15 | 16 | if r.Method != http.MethodGet { 17 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 18 | return 19 | } 20 | 21 | tmpl, err := template.ParseFiles("static/assets/view_expressions.html") 22 | if err != nil { 23 | log.Println("Error parsing expressions.html template:", err) 24 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 25 | return 26 | } 27 | 28 | mu.Lock() 29 | defer mu.Unlock() 30 | 31 | expressions, err := databaseManager.DB.GetExpressions(int(userID)) // получаем выражения 32 | if err != nil { 33 | log.Println("Error getting expressions:", err) 34 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 35 | return 36 | } 37 | 38 | if len(expressions) == 0 { 39 | expressions = append(expressions, exampleExpression) // если выражений нет, то передадим пример 40 | } 41 | 42 | err = tmpl.Execute(w, utils.FlipList(expressions)) 43 | if err != nil { 44 | log.Println("Error executing expressions.html template:", err) 45 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 46 | return 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/handlers/getScheme.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | // HandleGetScheme Схема проекта 10 | func HandleGetScheme(w http.ResponseWriter, r *http.Request) { 11 | tmpl, err := template.ParseFiles("static/assets/scheme.html") 12 | if err != nil { 13 | log.Println("Error parsing expression_by_id.html template:", err) 14 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 15 | return 16 | } 17 | if r.Method == http.MethodGet { 18 | err = tmpl.Execute(w, nil) 19 | if err != nil { 20 | log.Println("Error executing expression_by_id.html template:", err) 21 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 22 | return 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/handlers/getServers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "html/template" 6 | "log" 7 | "net/http" 8 | "sort" 9 | ) 10 | 11 | // HandleCurrentServers получение данных о серверах 12 | func HandleCurrentServers(w http.ResponseWriter, r *http.Request) { 13 | 14 | userID := r.Context().Value("userID").(float64) 15 | 16 | log.Println(userID) 17 | 18 | if r.Method != http.MethodGet { 19 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 20 | return 21 | } 22 | 23 | tmpl, err := template.ParseFiles("static/assets/server_data.html") 24 | if err != nil { 25 | log.Println("Error parsing server_data.html template:", err) 26 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 27 | return 28 | } 29 | 30 | models.Servers.Servers.Mu.Lock() 31 | 32 | var serverList []*models.Server 33 | 34 | for _, server := range models.Servers.Servers.Servers { 35 | serverList = append(serverList, server) 36 | } 37 | 38 | sort.Slice(serverList, func(i, j int) bool { 39 | return serverList[i].ID < serverList[j].ID 40 | }) 41 | 42 | models.Servers.Servers.Mu.Unlock() 43 | 44 | err = tmpl.Execute(w, serverList) 45 | if err != nil { 46 | log.Println("Error executing server_data.html template:", err) 47 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/handlers/login.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | func HandleLogin(w http.ResponseWriter, r *http.Request) { 12 | tmpl, err := template.ParseFiles("static/assets/login.html") 13 | 14 | if err != nil { 15 | log.Println("Error parsing expression_by_id.html template:", err) 16 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 17 | return 18 | } 19 | 20 | if r.Method == http.MethodGet { 21 | err = tmpl.Execute(w, nil) 22 | 23 | if err != nil { 24 | log.Println("Error executing expression_by_id.html template:", err) 25 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 26 | return 27 | } 28 | } else if r.Method == http.MethodPost { 29 | 30 | login := r.FormValue("username") 31 | password := r.FormValue("password") 32 | 33 | jwtToken, err := databaseManager.LogInUser(login, password) 34 | 35 | message := models.CreateNewTemplateMessage() 36 | 37 | if err != nil { 38 | message.ChangeMessage(err.Error()) 39 | 40 | err := models.ClearJWTSessionStorage(w, r) 41 | if err != nil { 42 | log.Println(err) 43 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 44 | return 45 | } 46 | } else { 47 | 48 | message.ChangeMessage("Login successful!") 49 | 50 | err = models.SetJWTSessionStorage(w, r, jwtToken) 51 | if err != nil { 52 | log.Println(err) 53 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 54 | return 55 | } 56 | } 57 | 58 | err = tmpl.Execute(w, message) 59 | if err != nil { 60 | log.Println("Error executing create_expression.html template:", err) 61 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 62 | return 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /backend/handlers/logout.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "html/template" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func HandleLogout(w http.ResponseWriter, r *http.Request) { 11 | tmpl, err := template.ParseFiles("static/assets/logout.html") 12 | 13 | if err != nil { 14 | log.Println("Error parsing expression_by_id.html template:", err) 15 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 16 | return 17 | } 18 | 19 | if r.Method == http.MethodGet { 20 | err = tmpl.Execute(w, nil) 21 | 22 | if err != nil { 23 | log.Println("Error executing expression_by_id.html template:", err) 24 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 25 | return 26 | } 27 | } else if r.Method == http.MethodPost { 28 | err := models.ClearJWTSessionStorage(w, r) 29 | if err != nil { 30 | http.Error(w, err.Error(), http.StatusInternalServerError) 31 | return 32 | } 33 | http.Redirect(w, r, "/login", http.StatusSeeOther) 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /backend/handlers/register.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 7 | "html/template" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | func HandleRegister(w http.ResponseWriter, r *http.Request) { 13 | tmpl, err := template.ParseFiles("static/assets/register.html") 14 | if err != nil { 15 | log.Println("Error parsing expression_by_id.html template:", err) 16 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 17 | return 18 | } 19 | if r.Method == http.MethodGet { 20 | err = tmpl.Execute(w, nil) 21 | if err != nil { 22 | log.Println("Error executing expression_by_id.html template:", err) 23 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 24 | return 25 | } 26 | } else if r.Method == http.MethodPost { 27 | 28 | login := r.FormValue("username") 29 | password := r.FormValue("password") 30 | 31 | err, userID := databaseManager.SignUpUser(login, password) 32 | 33 | message := models.CreateNewTemplateMessage() 34 | 35 | if err != nil { 36 | message.ChangeMessage(err.Error()) 37 | 38 | } else { 39 | message.ChangeMessage("Sign up successful!") 40 | cacheMaster.OperationCache.SetList(userID, []int{1, 1, 1, 1}) 41 | } 42 | 43 | err = tmpl.Execute(w, message) 44 | if err != nil { 45 | log.Println("Error executing create_expression.html template:", err) 46 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 47 | return 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/internal/cacheMaster/cacheManager.go: -------------------------------------------------------------------------------- 1 | package cacheMaster 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 5 | "log" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | OperationCache = NewCache() 11 | Operations = map[string]int{"+": 0, "-": 1, "*": 2, "/": 3} 12 | OperatorByID = map[int]string{0: "+", 1: "-", 2: "*", 3: "/"} 13 | ) 14 | 15 | type Cache struct { 16 | userOperationTimes map[int]map[int]int 17 | mu sync.Mutex 18 | } 19 | 20 | func NewCache() *Cache { 21 | return &Cache{ 22 | userOperationTimes: make(map[int]map[int]int), 23 | } 24 | } 25 | 26 | func (c *Cache) Get(userID, operationID int) (int, bool) { 27 | 28 | log.Println("Getting user cache") 29 | 30 | c.mu.Lock() 31 | defer c.mu.Unlock() 32 | 33 | userTimes, found := c.userOperationTimes[userID] 34 | if !found { 35 | return 0, false 36 | } 37 | 38 | time, found := userTimes[operationID] 39 | return time, found 40 | } 41 | 42 | func (c *Cache) Set(userID, operationID, time int) { 43 | 44 | log.Println("Setting user cache") 45 | 46 | c.mu.Lock() 47 | defer c.mu.Unlock() 48 | 49 | if _, ok := c.userOperationTimes[userID]; !ok { 50 | c.userOperationTimes[userID] = make(map[int]int) 51 | } 52 | 53 | c.userOperationTimes[userID][operationID] = time 54 | } 55 | 56 | func (c *Cache) SetList(userID int, times []int) { 57 | 58 | log.Println("Setting a list of user cache") 59 | 60 | for ind, time := range times { 61 | c.Set(userID, ind, time) 62 | } 63 | } 64 | 65 | func (c *Cache) GetList(userID int) []int { 66 | 67 | log.Println("Getting a list of user cache") 68 | 69 | var times []int 70 | 71 | for ind := 0; ind < 4; ind++ { 72 | time, found := c.Get(userID, ind) 73 | if found { 74 | times = append(times, time) 75 | } 76 | } 77 | return times 78 | } 79 | 80 | // LoadOperationTimesIntoCache загружает данные из бд в кэш 81 | func LoadOperationTimesIntoCache() error { 82 | 83 | log.Println("Loading a list of user cache") 84 | 85 | userIDs, err := databaseManager.DB.GetUserIDs() 86 | if err != nil { 87 | log.Println("Error fetching user IDs from the database:", err) 88 | return err 89 | } 90 | 91 | for _, userID := range userIDs { 92 | times, err := databaseManager.DB.GetTimes(userID) 93 | if err != nil { 94 | log.Printf("Error fetching data from the database for userID %d: %v\n", userID, err) 95 | return err 96 | } 97 | OperationCache.SetList(userID, times) 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /backend/internal/calculator/calculator.go: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import "errors" 4 | 5 | // Calculate Подсчет простого выражения с обработкой случаев с делением на ноль, другими ошибками (убран в 1.02) 6 | func Calculate(number1 float64, number2 float64, operation string) (float64, error) { 7 | switch operation { 8 | case "+": 9 | return number1 + number2, nil 10 | case "-": 11 | return number1 - number2, nil 12 | case "*": 13 | return number1 * number2, nil 14 | case "/": 15 | if number2 == 0 { 16 | return 0, errors.New("division by zero") 17 | } 18 | return number1 / number2, nil 19 | default: 20 | return 0, errors.New("invalid operation") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/internal/calculator/changeNotation.go: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import "strings" 4 | 5 | // priority определяет приоритет оператора 6 | func priority(operator rune) int { 7 | switch operator { 8 | case '+', '-': 9 | return 1 10 | case '*', '/': 11 | return 2 12 | default: 13 | return 0 14 | } 15 | } 16 | 17 | // InfixToPostfix конвертирует инфиксное выражение в постфиксное 18 | func InfixToPostfix(expression string) []string { 19 | var postfixExpression []string // постфиксное выражение 20 | var operators []string // стек операторов 21 | 22 | expression = strings.ReplaceAll(expression, " ", "") // убираем пробелы 23 | number := "" 24 | 25 | for _, char := range expression { 26 | switch { 27 | case char >= '0' && char <= '9': 28 | number += string(char) 29 | case char == '(': 30 | handleNumber(&postfixExpression, &number) // добавляем число в выражение 31 | operators = append(operators, "(") // добавляем открывающую скобку в стек 32 | case char == ')': 33 | handleNumber(&postfixExpression, &number) // добавляем число в выражение 34 | handleClosingParenthesis(&postfixExpression, &operators) // обрабатываем закрывающую скобку 35 | default: 36 | handleNumber(&postfixExpression, &number) // добавляем число в выражение 37 | handleOperator(char, &postfixExpression, &operators) // обрабатываем оператор 38 | } 39 | } 40 | 41 | handleNumber(&postfixExpression, &number) // добавляем оставшееся число в выражение 42 | handleRemainingOperators(&postfixExpression, &operators) // обрабатываем оставшиеся операторы 43 | 44 | return postfixExpression 45 | } 46 | 47 | // handleNumber добавляет число в постфиксное выражение, если оно есть 48 | func handleNumber(postfixExpression *[]string, number *string) { 49 | if *number != "" { 50 | *postfixExpression = append(*postfixExpression, *number) 51 | *number = "" 52 | } 53 | } 54 | 55 | // handleClosingParenthesis обрабатывает закрывающую скобку 56 | func handleClosingParenthesis(postfixExpression *[]string, operators *[]string) { 57 | for len(*operators) > 0 && (*operators)[len(*operators)-1] != "(" { 58 | *postfixExpression = append(*postfixExpression, (*operators)[len(*operators)-1]) 59 | *operators = (*operators)[:len(*operators)-1] 60 | } 61 | *operators = (*operators)[:len(*operators)-1] // удаляем открывающую скобку из стека 62 | } 63 | 64 | // handleOperator обрабатывает оператор 65 | func handleOperator(char rune, postfixExpression *[]string, operators *[]string) { 66 | for len(*operators) > 0 && priority(rune((*operators)[len(*operators)-1][0])) >= priority(char) { 67 | *postfixExpression = append(*postfixExpression, (*operators)[len(*operators)-1]) 68 | *operators = (*operators)[:len(*operators)-1] 69 | } 70 | *operators = append(*operators, string(char)) // добавляем текущий оператор в стек 71 | } 72 | 73 | // handleRemainingOperators обрабатывает оставшиеся операторы 74 | func handleRemainingOperators(postfixExpression *[]string, operators *[]string) { 75 | for len(*operators) > 0 { 76 | *postfixExpression = append(*postfixExpression, (*operators)[len(*operators)-1]) 77 | *operators = (*operators)[:len(*operators)-1] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /backend/internal/database/database.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KFN002/distributed-arithmetic-expression-evaluator/2afca86a33e27a96ee0ed50316e9aed3eb968737/backend/internal/database/database.db -------------------------------------------------------------------------------- /backend/internal/databaseManager/librarian.go: -------------------------------------------------------------------------------- 1 | package databaseManager 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 7 | _ "github.com/mattn/go-sqlite3" 8 | "log" 9 | "sync" 10 | ) 11 | 12 | var DB *DataBase 13 | 14 | type DataBase struct { 15 | DB *sql.DB 16 | mu sync.Mutex 17 | } 18 | 19 | func init() { 20 | var err error 21 | DB, err = NewDataBase("backend/internal/database/database.db") 22 | if err != nil { 23 | log.Fatalf("Failed to connect to database: %v", err) 24 | } 25 | } 26 | 27 | func NewDataBase(dataSourceName string) (*DataBase, error) { 28 | db, err := sql.Open("sqlite3", dataSourceName) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &DataBase{DB: db}, nil 33 | } 34 | 35 | func CloseDB() { 36 | if DB != nil { 37 | if err := DB.DB.Close(); err != nil { 38 | log.Fatalf("Error closing database connection: %v", err) 39 | } 40 | } 41 | } 42 | 43 | func (db *DataBase) GetTimes(userID int) ([]int, error) { 44 | db.mu.Lock() 45 | defer db.mu.Unlock() 46 | 47 | rows, err := db.DB.Query("SELECT time FROM operations WHERE user_id = ?", userID) 48 | if err != nil { 49 | return nil, fmt.Errorf("error querying database: %v", err) 50 | } 51 | defer rows.Close() 52 | 53 | var operationTimes []int 54 | for rows.Next() { 55 | var time int 56 | if err := rows.Scan(&time); err != nil { 57 | return nil, fmt.Errorf("error scanning row: %v", err) 58 | } 59 | operationTimes = append(operationTimes, time) 60 | } 61 | 62 | if err := rows.Err(); err != nil { 63 | return nil, fmt.Errorf("error iterating over rows: %v", err) 64 | } 65 | 66 | return operationTimes, nil 67 | } 68 | 69 | func (db *DataBase) CheckDuplicate(expression string, userID int) (bool, error) { 70 | db.mu.Lock() 71 | defer db.mu.Unlock() 72 | 73 | var count int 74 | err := db.DB.QueryRow("SELECT COUNT(*) FROM expressions WHERE expression = ? AND user_id = ?", expression, userID).Scan(&count) 75 | if err != nil { 76 | log.Println("Error querying database:", err) 77 | return false, fmt.Errorf("error querying database: %v", err) 78 | } 79 | return count > 0, nil 80 | } 81 | 82 | func (db *DataBase) GetId(expression string) (int, error) { 83 | db.mu.Lock() 84 | defer db.mu.Unlock() 85 | 86 | var id int 87 | err := db.DB.QueryRow("SELECT id FROM expressions WHERE expression = ?", expression).Scan(&id) 88 | if err != nil { 89 | if err == sql.ErrNoRows { 90 | return 0, nil 91 | } 92 | log.Println("Error querying database:", err) 93 | return 0, fmt.Errorf("error querying database: %v", err) 94 | } 95 | 96 | return id, nil 97 | } 98 | 99 | func (db *DataBase) GetExpressions(userID int) ([]models.Expression, error) { 100 | db.mu.Lock() 101 | defer db.mu.Unlock() 102 | 103 | rows, err := db.DB.Query("SELECT * FROM expressions WHERE user_id = ?", userID) 104 | if err != nil { 105 | log.Println("Error querying database:", err) 106 | return nil, fmt.Errorf("error querying database: %v", err) 107 | } 108 | defer rows.Close() 109 | 110 | var expressions []models.Expression 111 | 112 | for rows.Next() { 113 | var expression models.Expression 114 | if err := rows.Scan( 115 | &expression.ID, 116 | &expression.Expression, 117 | &expression.Status, 118 | &expression.Result, 119 | &expression.CreatedAt, 120 | &expression.FinishedAt, 121 | &expression.UserID); err != nil { 122 | log.Println("Error scanning row:", err) 123 | return nil, fmt.Errorf("error scanning row: %v", err) 124 | } 125 | expressions = append(expressions, expression) 126 | } 127 | 128 | if err := rows.Err(); err != nil { 129 | log.Println("Error iterating over rows:", err) 130 | return nil, fmt.Errorf("error iterating over rows: %v", err) 131 | } 132 | 133 | return expressions, nil 134 | } 135 | 136 | func (db *DataBase) FetchExpressionByID(id, userID int) (*models.Expression, error) { 137 | db.mu.Lock() 138 | defer db.mu.Unlock() 139 | 140 | row := db.DB.QueryRow("SELECT * FROM expressions WHERE ID = ? AND user_id = ?", id, userID) 141 | 142 | var expression models.Expression 143 | err := row.Scan( 144 | &expression.ID, 145 | &expression.Expression, 146 | &expression.Status, 147 | &expression.Result, 148 | &expression.CreatedAt, 149 | &expression.FinishedAt, 150 | &expression.UserID, 151 | ) 152 | if err != nil { 153 | if err == sql.ErrNoRows { 154 | return nil, fmt.Errorf("expression with ID %d not found", id) 155 | } 156 | log.Println("Error scanning row:", err) 157 | return nil, fmt.Errorf("error scanning row: %v", err) 158 | } 159 | 160 | return &expression, nil 161 | } 162 | 163 | func (db *DataBase) ToCalculate() ([]models.Expression, error) { 164 | db.mu.Lock() 165 | defer db.mu.Unlock() 166 | 167 | rows, err := db.DB.Query("SELECT * FROM expressions WHERE status = ?", "processing") 168 | if err != nil { 169 | log.Println("Error querying database:", err) 170 | return nil, fmt.Errorf("error querying database: %v", err) 171 | } 172 | defer rows.Close() 173 | 174 | var expressions []models.Expression 175 | 176 | for rows.Next() { 177 | var expression models.Expression 178 | if err := rows.Scan( 179 | &expression.ID, 180 | &expression.Expression, 181 | &expression.Status, 182 | &expression.Result, 183 | &expression.CreatedAt, 184 | &expression.FinishedAt, 185 | &expression.UserID); err != nil { 186 | log.Println("Error scanning row:", err) 187 | return nil, fmt.Errorf("error scanning row: %v", err) 188 | } 189 | expressions = append(expressions, expression) 190 | } 191 | 192 | if err := rows.Err(); err != nil { 193 | log.Println("Error iterating over rows:", err) 194 | return nil, fmt.Errorf("error iterating over rows: %v", err) 195 | } 196 | 197 | return expressions, nil 198 | } 199 | 200 | func (db *DataBase) UpdateExpressionAfterCalc(expression *models.Expression) error { 201 | db.mu.Lock() 202 | defer db.mu.Unlock() 203 | 204 | stmt, err := db.DB.Prepare("UPDATE expressions SET status = ?, result = ?, time_finish = ? WHERE id = ?") 205 | if err != nil { 206 | log.Println("Error preparing update statement:", err) 207 | return fmt.Errorf("error preparing update statement: %v", err) 208 | } 209 | 210 | defer func(stmt *sql.Stmt) { 211 | err := stmt.Close() 212 | if err != nil { 213 | log.Println("error closing file") 214 | } 215 | }(stmt) 216 | 217 | _, err = stmt.Exec(expression.Status, expression.Result, expression.FinishedAt, expression.ID) 218 | if err != nil { 219 | log.Println("Error updating expression:", err) 220 | return fmt.Errorf("error updating expression: %v", err) 221 | } 222 | 223 | return nil 224 | } 225 | 226 | func (db *DataBase) AddOperations(userID int) error { 227 | db.mu.Lock() 228 | defer db.mu.Unlock() 229 | 230 | stmt, err := db.DB.Prepare("INSERT INTO operations (name, time, user_id) VALUES (?, ?, ?)") 231 | if err != nil { 232 | return fmt.Errorf("error preparing SQL statement: %v", err) 233 | } 234 | defer stmt.Close() 235 | 236 | operations := []models.Operations{ 237 | {Name: "+", Time: 1, UserID: userID}, 238 | {Name: "-", Time: 1, UserID: userID}, 239 | {Name: "*", Time: 1, UserID: userID}, 240 | {Name: "/", Time: 1, UserID: userID}, 241 | } 242 | 243 | for _, op := range operations { 244 | _, err := stmt.Exec(op.Name, op.Time, op.UserID) 245 | if err != nil { 246 | return fmt.Errorf("error inserting operation into database: %v", err) 247 | } 248 | } 249 | 250 | return nil 251 | } 252 | 253 | func (db *DataBase) UpdateOperationTime(value int, name string, userID int) error { 254 | db.mu.Lock() 255 | defer db.mu.Unlock() 256 | 257 | _, err := db.DB.Exec("UPDATE operations SET time=? WHERE name=? AND user_id=?", value, name, userID) 258 | if err != nil { 259 | return fmt.Errorf("error updating operation time: %v", err) 260 | } 261 | return nil 262 | } 263 | 264 | func (db *DataBase) GetUserIDs() ([]int, error) { 265 | var userIDs []int 266 | 267 | rows, err := db.DB.Query("SELECT DISTINCT id FROM users") 268 | if err != nil { 269 | return nil, fmt.Errorf("error querying user IDs from the database: %v", err) 270 | } 271 | defer rows.Close() 272 | 273 | for rows.Next() { 274 | var userID int 275 | if err := rows.Scan(&userID); err != nil { 276 | return nil, fmt.Errorf("error scanning user ID row: %v", err) 277 | } 278 | userIDs = append(userIDs, userID) 279 | } 280 | 281 | if err := rows.Err(); err != nil { 282 | return nil, fmt.Errorf("error iterating over user ID rows: %v", err) 283 | } 284 | 285 | return userIDs, nil 286 | } 287 | -------------------------------------------------------------------------------- /backend/internal/databaseManager/userManager.go: -------------------------------------------------------------------------------- 1 | package databaseManager 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 7 | "golang.org/x/crypto/bcrypt" 8 | ) 9 | 10 | // SignUpUser Регистрация пользователя и проверка его данных 11 | func SignUpUser(login string, password string) (error, int) { 12 | var count int 13 | err := DB.DB.QueryRow("SELECT COUNT(*) FROM users WHERE login = ?", login).Scan(&count) 14 | if err != nil { 15 | return err, 0 16 | } 17 | 18 | if count > 0 { 19 | return fmt.Errorf("User with this nickname already exists"), 0 20 | } 21 | 22 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 23 | if err != nil { 24 | return fmt.Errorf("failed to hash password: %v", err), 0 25 | } 26 | 27 | result, err := DB.DB.Exec("INSERT INTO users (login, password) VALUES (?, ?)", login, string(hashedPassword)) 28 | if err != nil { 29 | return err, 0 30 | } 31 | 32 | userID, err := result.LastInsertId() 33 | if err != nil { 34 | return err, 0 35 | } 36 | 37 | err = DB.AddOperations(int(userID)) 38 | if err != nil { 39 | return err, 0 40 | } 41 | 42 | return nil, int(userID) 43 | } 44 | 45 | // LogInUser Вход и проверка данных пользователя 46 | func LogInUser(login string, password string) (string, error) { 47 | var storedPasswordHash string 48 | var userID int 49 | 50 | err := DB.DB.QueryRow("SELECT id, password FROM users WHERE login = ?", login).Scan(&userID, &storedPasswordHash) 51 | if err != nil { 52 | if err == sql.ErrNoRows { 53 | return "", fmt.Errorf("Incorrect username") 54 | } 55 | return "", err 56 | } 57 | 58 | err = bcrypt.CompareHashAndPassword([]byte(storedPasswordHash), []byte(password)) 59 | if err != nil { 60 | return "", fmt.Errorf("Incorrect password") 61 | } 62 | 63 | tokenString, err := models.GenerateJWT(userID, login) 64 | if err != nil { 65 | return "", fmt.Errorf("failed to generate token: %v", err) 66 | } 67 | 68 | return tokenString, nil 69 | } 70 | 71 | // CheckUser Проверка на наличие пользователя 72 | func CheckUser(userID float64, userName string) (bool, error) { 73 | var exists bool 74 | query := "SELECT EXISTS(SELECT 1 FROM users WHERE id = ? AND login = ?)" 75 | 76 | err := DB.DB.QueryRow(query, userID, userName).Scan(&exists) 77 | if err != nil { 78 | if err == sql.ErrNoRows { 79 | return false, nil 80 | } 81 | return false, fmt.Errorf("error checking user existence: %v", err) 82 | } 83 | 84 | return exists, nil 85 | } 86 | -------------------------------------------------------------------------------- /backend/internal/orchestratorAndAgents/orchestratorAndAgent.go: -------------------------------------------------------------------------------- 1 | package orchestratorAndAgents 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 8 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/calculator" 9 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 10 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/queueMaster" 11 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 12 | pb "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/proto" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/credentials/insecure" 15 | "log" 16 | "strconv" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | // QueueHandler Получение выражений из очереди 22 | func QueueHandler() { 23 | for { 24 | expression, gotExpr := queueMaster.ExpressionsQueue.Dequeue() 25 | if gotExpr { 26 | answerCh := make(chan float64) 27 | errCh := make(chan error) 28 | 29 | go Orchestrator(expression, answerCh, errCh) 30 | 31 | select { 32 | case ans := <-answerCh: 33 | expression.ChangeData("finished", ans) 34 | if err := databaseManager.DB.UpdateExpressionAfterCalc(&expression); err != nil { 35 | log.Println("Error occurred when writing data:", err) 36 | queueMaster.ExpressionsQueue.Enqueue(expression) 37 | } 38 | 39 | case errCalc := <-errCh: 40 | log.Println(errCalc.Error()) 41 | if errCalc.Error() == "division by zero or else" { 42 | log.Println("division 0") 43 | expression.ChangeData("calc error", 0) 44 | if err := databaseManager.DB.UpdateExpressionAfterCalc(&expression); err != nil { 45 | log.Println("Error occurred when writing data:", err) 46 | queueMaster.ExpressionsQueue.Enqueue(expression) 47 | } 48 | } else { 49 | log.Println("Error occurred:", errCalc) 50 | queueMaster.ExpressionsQueue.Enqueue(expression) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | // Orchestrator Разделение выражения на подзадачи, подсчет выражения 58 | func Orchestrator(expression models.Expression, answerCh chan float64, errCh chan error) { 59 | defer close(answerCh) 60 | 61 | postfixExpression := calculator.InfixToPostfix(expression.Expression) 62 | 63 | var answers []float64 64 | var freeServers = models.Servers.ServersQuantity 65 | var serversUsing = 0 66 | wg := &sync.WaitGroup{} 67 | 68 | for _, elem := range postfixExpression { 69 | if elem == "+" || elem == "-" || elem == "*" || elem == "/" { 70 | if freeServers > 0 { 71 | wg.Add(1) 72 | resCh := make(chan float64) 73 | errSubCh := make(chan error) 74 | firstNum := answers[len(answers)-2] 75 | secondNum := answers[len(answers)-1] 76 | answers = answers[:len(answers)-2] 77 | go func(firstNum, secondNum float64, op string) { 78 | defer wg.Done() 79 | Agent(serversUsing, firstNum, secondNum, op, resCh, errSubCh, expression.UserID) 80 | }(firstNum, secondNum, elem) 81 | freeServers-- 82 | serversUsing++ 83 | select { 84 | case err := <-errSubCh: 85 | errCh <- err 86 | return 87 | case result := <-resCh: 88 | answers = append(answers, result) 89 | } 90 | freeServers++ 91 | serversUsing-- 92 | } else { 93 | wg.Wait() 94 | } 95 | } else { 96 | num, _ := strconv.Atoi(elem) 97 | answers = append(answers, float64(num)) 98 | } 99 | } 100 | wg.Wait() 101 | answerCh <- answers[0] 102 | } 103 | 104 | // Agent Подсчет мелкого выражения 105 | func Agent(id int, firstNum float64, secondNum float64, operation string, subResCh chan float64, errCh chan error, userID int) { 106 | subExpression := fmt.Sprintf("%f %s %f", firstNum, operation, secondNum) 107 | log.Println(subExpression) 108 | 109 | go models.Servers.UpdateServers(id, subExpression, "Online, processing subExpression") 110 | 111 | operationID := cacheMaster.Operations[operation] 112 | operationTime, found := cacheMaster.OperationCache.Get(userID, operationID) 113 | if !found { 114 | errCh <- fmt.Errorf("operation time not found in cache for user ID %d and operation %s", userID, operation) 115 | return 116 | } 117 | 118 | go func() { 119 | ticker := time.NewTicker(time.Second) 120 | defer ticker.Stop() 121 | 122 | for { 123 | select { 124 | case <-ticker.C: 125 | models.Servers.SendHeartbeat(id) 126 | } 127 | } 128 | }() 129 | 130 | select { 131 | case <-time.After(time.Duration(operationTime) * time.Second): 132 | // связь с gRPC сервером 133 | conn, err := grpc.Dial("localhost:8050", grpc.WithTransportCredentials(insecure.NewCredentials())) 134 | if err != nil { 135 | log.Fatalf("failed to dial server: %v", err) 136 | } 137 | defer conn.Close() 138 | 139 | grpcClient := pb.NewAgentServiceClient(conn) 140 | 141 | // запрос gRPC серверу 142 | result, err := grpcClient.Calculate(context.Background(), &pb.CalculationRequest{ 143 | FirstNumber: float32(firstNum), 144 | SecondNumber: float32(secondNum), 145 | Operation: operation, 146 | }) 147 | 148 | if result == nil { 149 | log.Fatal("grpcClient.Calculate returned nil result") 150 | } 151 | 152 | log.Println("Got gRPC response!") 153 | 154 | if err != nil { 155 | errCh <- errors.New("division by zero or else") 156 | go models.Servers.UpdateServers(id, subExpression, "Restarting, error occurred while processing") 157 | log.Println("calculating error - Agent calc") 158 | } 159 | 160 | go models.Servers.UpdateServers(id, "", "Online, finished processing") 161 | subResCh <- float64(result.Result) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /backend/internal/queueMaster/queueManager.go: -------------------------------------------------------------------------------- 1 | package queueMaster 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | var ExpressionsQueue = ExpressionQueue() 10 | 11 | // Queue реализация очереди с выражениями через атомики 12 | type Queue interface { 13 | Enqueue(element models.Expression) 14 | EnqueueList(data []models.Expression) 15 | Dequeue() (models.Expression, bool) 16 | } 17 | 18 | type QueueNode struct { 19 | expression models.Expression 20 | next unsafe.Pointer 21 | } 22 | 23 | type LockFreeQueue struct { 24 | head unsafe.Pointer 25 | tail unsafe.Pointer 26 | } 27 | 28 | func ExpressionQueue() *LockFreeQueue { 29 | dummy := &QueueNode{} 30 | return &LockFreeQueue{ 31 | head: unsafe.Pointer(dummy), 32 | tail: unsafe.Pointer(dummy), 33 | } 34 | } 35 | 36 | func (q *LockFreeQueue) Enqueue(element models.Expression) { 37 | newNode := &QueueNode{expression: element} 38 | 39 | for { 40 | tail := atomic.LoadPointer(&q.tail) 41 | next := atomic.LoadPointer(&((*QueueNode)(tail)).next) 42 | 43 | if tail == atomic.LoadPointer(&q.tail) { 44 | if next == nil { 45 | if atomic.CompareAndSwapPointer(&((*QueueNode)(tail)).next, nil, unsafe.Pointer(newNode)) { 46 | atomic.CompareAndSwapPointer(&q.tail, tail, unsafe.Pointer(newNode)) 47 | return 48 | } 49 | } else { 50 | atomic.CompareAndSwapPointer(&q.tail, tail, next) 51 | } 52 | } 53 | } 54 | } 55 | 56 | func (q *LockFreeQueue) EnqueueList(data []models.Expression) { 57 | for _, expr := range data { 58 | q.Enqueue(expr) 59 | } 60 | } 61 | 62 | func (q *LockFreeQueue) Dequeue() (models.Expression, bool) { 63 | for { 64 | head := atomic.LoadPointer(&q.head) 65 | next := atomic.LoadPointer(&((*QueueNode)(head)).next) 66 | 67 | if head == atomic.LoadPointer(&q.head) { 68 | if next == nil { 69 | return models.Expression{}, false 70 | } 71 | if atomic.CompareAndSwapPointer(&q.head, head, next) { 72 | return (*QueueNode)(next).expression, true 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /backend/middleware/checkLogin.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/databaseManager" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 11 | ) 12 | 13 | func JWTMiddleware(next http.HandlerFunc) http.HandlerFunc { 14 | return func(w http.ResponseWriter, r *http.Request) { 15 | tokenString, err := models.GetJWTFromSessionStorage(r) 16 | if err != nil { 17 | http.Error(w, fmt.Sprintf("Failed to get JWT token from session storage: %v", err), http.StatusInternalServerError) 18 | return 19 | } 20 | 21 | // аноним 22 | if tokenString == "" { 23 | ctx := context.WithValue(r.Context(), "userID", 0) 24 | ctx = context.WithValue(ctx, "login", "") 25 | http.Redirect(w, r, "/login", http.StatusSeeOther) 26 | return 27 | } 28 | 29 | userID, login, err := models.ParseJWT(tokenString) 30 | 31 | // ошибка или сессия истекла 32 | if err != nil { 33 | err := models.ClearJWTSessionStorage(w, r) 34 | if err != nil { 35 | http.Error(w, fmt.Sprintf("Failed to clear JWT token: %v", err), http.StatusInternalServerError) 36 | return 37 | } 38 | http.Redirect(w, r, "/login", http.StatusSeeOther) 39 | return 40 | } 41 | 42 | ctx := context.WithValue(r.Context(), "userID", userID) 43 | ctx = context.WithValue(ctx, "login", login) 44 | 45 | ok, err := databaseManager.CheckUser(userID, login) 46 | 47 | // пользователь не найден или ошибка поиска 48 | if ok != true || err != nil { 49 | err := models.ClearJWTSessionStorage(w, r) 50 | if err != nil { 51 | http.Error(w, fmt.Sprintf("Failed to clear JWT token: %v", err), http.StatusInternalServerError) 52 | return 53 | } 54 | http.Redirect(w, r, "/login", http.StatusSeeOther) 55 | return 56 | } 57 | 58 | log.Println(userID, login) 59 | 60 | // все окей 61 | next.ServeHTTP(w, r.WithContext(ctx)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /backend/pkg/models/JWT.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang-jwt/jwt/v5" 6 | "time" 7 | ) 8 | 9 | const JWTSecretKey = "kogda_ya_na_pochte_sluzhil_yamshikom_ko_mne_postuchalsya_kosmatiy_geolog" 10 | 11 | func GenerateJWT(userID int, login string) (string, error) { 12 | now := time.Now() 13 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 14 | "userID": userID, 15 | "name": login, 16 | "exp": now.Add(24 * time.Hour).Unix(), 17 | }) 18 | 19 | tokenString, err := token.SignedString([]byte(JWTSecretKey)) 20 | if err != nil { 21 | return "", err 22 | } 23 | 24 | return tokenString, nil 25 | } 26 | 27 | func ParseJWT(tokenString string) (float64, string, error) { 28 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 29 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 30 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 31 | } 32 | 33 | return []byte(JWTSecretKey), nil 34 | }) 35 | if err != nil { 36 | return 0, "", err 37 | } 38 | 39 | if !token.Valid { 40 | return 0, "", fmt.Errorf("invalid token") 41 | } 42 | 43 | claims, ok := token.Claims.(jwt.MapClaims) 44 | if !ok { 45 | return 0, "", fmt.Errorf("invalid token claims") 46 | } 47 | 48 | userID, ok := claims["userID"].(float64) 49 | if !ok { 50 | return 0, "", fmt.Errorf("user ID not found in token claims") 51 | } 52 | name, ok := claims["name"].(string) 53 | if !ok { 54 | return 0, "", fmt.Errorf("name not found in token claims") 55 | } 56 | return userID, name, nil 57 | } 58 | -------------------------------------------------------------------------------- /backend/pkg/models/expression.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | // Expression выражение 6 | type Expression struct { 7 | ID int 8 | Expression string 9 | Status string 10 | Result *float64 11 | CreatedAt string 12 | FinishedAt *string 13 | UserID int 14 | } 15 | 16 | // ChangeData Изменение данных выражения 17 | func (e *Expression) ChangeData(status string, result float64) { 18 | e.Status = status 19 | e.Result = &result 20 | finish := time.Now().Format("02-01-2006 15:04:05") 21 | e.FinishedAt = &finish 22 | } 23 | 24 | // NewExpression создание нового экземпляра класса выражение 25 | func NewExpression(expression string, status string, userID int) Expression { 26 | var e Expression 27 | e.Status = status 28 | e.Expression = expression 29 | start := time.Now().Format("02-01-2006 15:04:05") 30 | e.CreatedAt = start 31 | e.UserID = userID 32 | return e 33 | } 34 | -------------------------------------------------------------------------------- /backend/pkg/models/operations.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // OperationTimes Данные операций - время 4 | type OperationTimes struct { 5 | Time1 int 6 | Time2 int 7 | Time3 int 8 | Time4 int 9 | } 10 | 11 | type Operations struct { 12 | Name string 13 | Time int 14 | UserID int 15 | } 16 | -------------------------------------------------------------------------------- /backend/pkg/models/serversData.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | var Servers = NewServersManager(1) // Серверов - 1, меняя переменную будет больше и меньше серверов 10 | 11 | // Server структура данных сервера (воркера) 12 | type Server struct { 13 | ID int 14 | Status string 15 | Tasks string 16 | LastPing string 17 | } 18 | 19 | // ServersData структура данных серверов (воркеров) 20 | type ServersData struct { 21 | Mu sync.Mutex 22 | Servers map[int]*Server 23 | } 24 | 25 | // ServersManager структура менеджера серверов (воркеров) 26 | type ServersManager struct { 27 | ServersQuantity int 28 | Servers ServersData 29 | } 30 | 31 | // NewServersManager создание посредника между всеми серверами (воркерами), для удобной работы 32 | func NewServersManager(quantity int) *ServersManager { 33 | return &ServersManager{ 34 | ServersQuantity: quantity, 35 | Servers: ServersData{Servers: make(map[int]*Server)}, 36 | } 37 | } 38 | 39 | // InitServers Добавление воркеров (серверов) исходя из их количества - переменная окружения 40 | func (sm *ServersManager) InitServers() { 41 | sm.Servers.Mu.Lock() 42 | defer sm.Servers.Mu.Unlock() 43 | for serverID := 1; serverID <= sm.ServersQuantity; serverID++ { 44 | server := &Server{ID: serverID, Status: "Online, standing by", Tasks: "", LastPing: time.Now().Format("02-01-2006 15:04:05")} 45 | sm.Servers.Servers[serverID] = server 46 | } 47 | } 48 | 49 | // UpdateServers изменение данных воркера 50 | func (sm *ServersManager) UpdateServers(id int, operation string, status string) { 51 | sm.Servers.Mu.Lock() 52 | defer sm.Servers.Mu.Unlock() 53 | server, exists := sm.Servers.Servers[id] 54 | if !exists { 55 | log.Println("Server with ID", id, "not found") 56 | return 57 | } 58 | server.Status = status 59 | server.Tasks = operation 60 | server.LastPing = time.Now().Format("02-01-2006 15:04:05") 61 | } 62 | 63 | // SendHeartbeat Посыл ответа от воркера 64 | func (sm *ServersManager) SendHeartbeat(id int) { 65 | sm.Servers.Mu.Lock() 66 | defer sm.Servers.Mu.Unlock() 67 | 68 | server, exists := sm.Servers.Servers[id] 69 | if !exists { 70 | log.Println("Server with ID", id, "not found") 71 | return 72 | } 73 | 74 | server.LastPing = time.Now().Format("02-01-2006 15:04:05") 75 | } 76 | 77 | // RunServers запуск работы посыла ответа от воркеров 78 | func (sm *ServersManager) RunServers() { 79 | sm.Servers.Mu.Lock() 80 | defer sm.Servers.Mu.Unlock() 81 | 82 | for id := range sm.Servers.Servers { 83 | go func(id int) { 84 | ticker := time.NewTicker(time.Second) 85 | defer ticker.Stop() 86 | 87 | for { 88 | select { 89 | case <-ticker.C: 90 | Servers.SendHeartbeat(id) 91 | } 92 | } 93 | }(id) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /backend/pkg/models/stack.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Stack стек и его логика - база 4 | type Stack []string 5 | 6 | func (st *Stack) IsEmpty() bool { 7 | return len(*st) == 0 8 | } 9 | 10 | func (st *Stack) Push(str string) { 11 | *st = append(*st, str) 12 | } 13 | 14 | func (st *Stack) Pop() bool { 15 | if st.IsEmpty() { 16 | return false 17 | } else { 18 | index := len(*st) - 1 19 | *st = (*st)[:index] 20 | return true 21 | } 22 | } 23 | 24 | func (st *Stack) Top() string { 25 | if st.IsEmpty() { 26 | return "" 27 | } else { 28 | index := len(*st) - 1 29 | element := (*st)[index] 30 | return element 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/pkg/models/templateMessage.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type TemplateMessage struct { 4 | Expression *Expression 5 | Message string 6 | } 7 | 8 | func (m *TemplateMessage) AddData(msg string, expression *Expression) { 9 | m.Expression = expression 10 | m.Message = msg 11 | } 12 | 13 | func (m *TemplateMessage) ChangeExpression(expression *Expression) { 14 | m.Expression = expression 15 | } 16 | 17 | func (m *TemplateMessage) ChangeMessage(msg string) { 18 | m.Message = msg 19 | } 20 | 21 | func CreateNewTemplateMessage() *TemplateMessage { 22 | return &TemplateMessage{} 23 | } 24 | -------------------------------------------------------------------------------- /backend/pkg/models/userJWT.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/gorilla/sessions" 5 | "net/http" 6 | ) 7 | 8 | var Store = sessions.NewCookieStore([]byte(JWTSecretKey)) 9 | 10 | func SetJWTSessionStorage(w http.ResponseWriter, r *http.Request, token string) error { 11 | session, err := Store.Get(r, "jwt_session") 12 | if err != nil { 13 | return err 14 | } 15 | session.Values["jwt_token"] = token 16 | 17 | err = session.Save(r, w) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | 25 | func GetJWTFromSessionStorage(r *http.Request) (string, error) { 26 | session, err := Store.Get(r, "jwt_session") 27 | if err != nil { 28 | return "", err 29 | } 30 | if token, ok := session.Values["jwt_token"].(string); ok { 31 | return token, nil 32 | } 33 | return "", nil 34 | } 35 | 36 | func ClearJWTSessionStorage(w http.ResponseWriter, r *http.Request) error { 37 | session, err := Store.Get(r, "jwt_session") 38 | if err != nil { 39 | return err 40 | } 41 | delete(session.Values, "jwt_token") 42 | 43 | err = session.Save(r, w) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /backend/pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "regexp" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | func SumList(data []float64) float64 { 11 | var total float64 12 | for _, elem := range data { 13 | total += elem 14 | } 15 | return total 16 | } 17 | 18 | // CheckExpression Проверяет выражение на сбалансированность скобок и на отсутствие двух или более арифметических знаков рядом. 19 | func CheckExpression(expression string) bool { 20 | if !checkPrefixSuffix(expression) { 21 | return false 22 | } 23 | if !areParenthesesBalanced(expression) { 24 | return false 25 | } 26 | if !containsOperator(expression) { 27 | return false 28 | } 29 | if hasConsecutiveOperators(expression) { 30 | return false 31 | } 32 | if hasDivisionByZero(expression) { 33 | return false 34 | } 35 | if !hasOperatorNearParentheses(expression) { 36 | return false 37 | } 38 | // Если обе проверки пройдены, возвращаем true 39 | return true 40 | } 41 | 42 | func containsOperator(input string) bool { 43 | operatorRegex := regexp.MustCompile(`[+\-*\/]`) 44 | 45 | return operatorRegex.MatchString(input) 46 | } 47 | 48 | func hasDivisionByZero(expression string) bool { 49 | operands := strings.Split(expression, "/") 50 | 51 | for _, op := range operands { 52 | if op == "0" { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | // Проверяет, сбалансированы ли скобки в выражении 60 | func areParenthesesBalanced(expression string) bool { 61 | stack := make([]rune, 0) 62 | 63 | for _, char := range expression { 64 | if char == '(' { 65 | stack = append(stack, '(') 66 | } else if char == ')' { 67 | if len(stack) == 0 { 68 | return false 69 | } 70 | stack = stack[:len(stack)-1] 71 | } 72 | } 73 | 74 | return len(stack) == 0 75 | } 76 | 77 | // Проверяет, есть ли в выражении два или более арифметических знаков рядом 78 | func hasConsecutiveOperators(expression string) bool { 79 | operators := "+-*/" 80 | for i := 0; i < len(expression)-1; i++ { 81 | if strings.ContainsAny(string(expression[i]), operators) && strings.ContainsAny(string(expression[i+1]), operators) { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | // Проверяет правильность операторов у скобок - не используется с 1.02 89 | func isValidExpression(expr string) bool { 90 | 91 | if strings.HasPrefix(expr, "(") || strings.HasSuffix(expr, ")") { 92 | return false 93 | } 94 | 95 | if strings.Contains(expr, "(") { 96 | if !unicode.IsDigit(rune(expr[0])) || !unicode.IsDigit(rune(expr[len(expr)-1])) { 97 | return false 98 | } 99 | } 100 | 101 | parts := strings.Split(expr, "(") 102 | for _, part := range parts { 103 | if strings.Contains(part, ")") { 104 | if strings.ContainsAny(part, "+-*/") { 105 | return true 106 | } else { 107 | return false 108 | } 109 | } 110 | } 111 | 112 | return false 113 | } 114 | 115 | // Проверяет правильность расстановки операторов в выражении 116 | func checkPrefixSuffix(expression string) bool { 117 | var operators = []string{"+", "-", "/", "*"} 118 | for _, operator := range operators { 119 | if strings.HasPrefix(expression, operator) || strings.HasSuffix(expression, operator) { 120 | return false 121 | } 122 | } 123 | return true 124 | } 125 | 126 | // Проверка правильности знаков у скобок 127 | func hasOperatorNearParentheses(expression string) bool { 128 | for i := 1; i < len(expression)-1; i++ { 129 | if expression[i] == '(' { 130 | if !isOperator(expression[i-1]) && !isOperator(expression[i+1]) { 131 | return false 132 | } 133 | } else if expression[i] == ')' { 134 | if !isOperator(expression[i-1]) && !isOperator(expression[i+1]) { 135 | return false 136 | } 137 | } 138 | } 139 | return true 140 | } 141 | 142 | func isOperator(char byte) bool { 143 | return char == '+' || char == '-' || char == '*' || char == '/' 144 | } 145 | 146 | // FlipList переворачивает список с данными - reverse 147 | func FlipList(list []models.Expression) []models.Expression { 148 | for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { 149 | list[i], list[j] = list[j], list[i] 150 | } 151 | return list 152 | } 153 | -------------------------------------------------------------------------------- /backend/tests/JWT_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "github.com/golang-jwt/jwt/v5" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestGenerateJWT(t *testing.T) { 11 | userID := 123 12 | login := "testuser" 13 | 14 | token, err := models.GenerateJWT(userID, login) 15 | if err != nil { 16 | t.Fatalf("GenerateJWT() failed: %v", err) 17 | } 18 | 19 | if token == "" { 20 | t.Error("GenerateJWT() failed: empty token") 21 | } 22 | } 23 | 24 | func TestParseJWT(t *testing.T) { 25 | userID := 123 26 | login := "testuser" 27 | token, err := models.GenerateJWT(userID, login) 28 | if err != nil { 29 | t.Fatalf("GenerateJWT() failed: %v", err) 30 | } 31 | 32 | parsedUserID, parsedLogin, err := models.ParseJWT(token) 33 | if err != nil { 34 | t.Fatalf("ParseJWT() failed: %v", err) 35 | } 36 | 37 | if parsedUserID != float64(userID) { 38 | t.Errorf("ParseJWT() failed: parsed user ID mismatch. Expected: %d, Got: %f", userID, parsedUserID) 39 | } 40 | 41 | if parsedLogin != login { 42 | t.Errorf("ParseJWT() failed: parsed login mismatch. Expected: %s, Got: %s", login, parsedLogin) 43 | } 44 | 45 | expiredToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 46 | "userID": userID, 47 | "name": login, 48 | "exp": time.Now().Add(-1 * time.Hour).Unix(), 49 | }).SignedString([]byte(models.JWTSecretKey)) 50 | if err != nil { 51 | t.Fatalf("Failed to create expired token: %v", err) 52 | } 53 | 54 | _, _, err = models.ParseJWT(expiredToken) 55 | if err == nil { 56 | t.Error("ParseJWT() failed to detect expired token") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/tests/cache_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 5 | "reflect" 6 | "sync" 7 | "testing" 8 | ) 9 | 10 | func TestCache_SetGet(t *testing.T) { 11 | cache := cacheMaster.NewCache() 12 | 13 | cache.Set(1, 0, 10) 14 | cache.Set(1, 1, 20) 15 | cache.Set(2, 0, 30) 16 | 17 | time, found := cache.Get(1, 0) 18 | if !found { 19 | t.Errorf("Expected to find time for userID 1 and operationID 0") 20 | } 21 | if time != 10 { 22 | t.Errorf("Expected time to be 10, got %d", time) 23 | } 24 | 25 | time, found = cache.Get(1, 1) 26 | if !found { 27 | t.Errorf("Expected to find time for userID 1 and operationID 1") 28 | } 29 | if time != 20 { 30 | t.Errorf("Expected time to be 20, got %d", time) 31 | } 32 | 33 | time, found = cache.Get(2, 0) 34 | if !found { 35 | t.Errorf("Expected to find time for userID 2 and operationID 0") 36 | } 37 | if time != 30 { 38 | t.Errorf("Expected time to be 30, got %d", time) 39 | } 40 | 41 | time, found = cache.Get(3, 0) 42 | if found { 43 | t.Errorf("Expected not to find time for userID 3 and operationID 0") 44 | } 45 | } 46 | 47 | func TestCache_SetGetList(t *testing.T) { 48 | cache := cacheMaster.NewCache() 49 | 50 | cache.Set(1, 0, 10) 51 | cache.Set(1, 1, 20) 52 | cache.Set(1, 2, 30) 53 | cache.Set(1, 3, 40) 54 | 55 | cache.SetList(2, []int{1, 2, 3, 4}) 56 | 57 | expected := map[int][]int{ 58 | 1: {10, 20, 30, 40}, 59 | 2: {1, 2, 3, 4}, 60 | } 61 | 62 | for userID, expectedList := range expected { 63 | result := cache.GetList(userID) 64 | if !reflect.DeepEqual(result, expectedList) { 65 | t.Errorf("Expected list for userID %d to be %v, got %v", userID, expectedList, result) 66 | } 67 | } 68 | } 69 | 70 | func TestCache_SetGetConcurrency(t *testing.T) { 71 | cache := cacheMaster.NewCache() 72 | var wg sync.WaitGroup 73 | numRoutines := 100 74 | 75 | wg.Add(numRoutines) 76 | for i := 0; i < numRoutines; i++ { 77 | go func(userID, operationID, time int) { 78 | defer wg.Done() 79 | cache.Set(userID, operationID, time) 80 | }(i, 0, i*10) 81 | } 82 | 83 | wg.Wait() 84 | 85 | for i := 0; i < numRoutines; i++ { 86 | time, found := cache.Get(i, 0) 87 | if !found { 88 | t.Errorf("Expected to find time for userID %d and operationID 0", i) 89 | } 90 | expected := i * 10 91 | if time != expected { 92 | t.Errorf("Expected time for userID %d to be %d, got %d", i, expected, time) 93 | } 94 | } 95 | } 96 | 97 | func TestCache_GetNonExistingUser(t *testing.T) { 98 | cache := cacheMaster.NewCache() 99 | 100 | time, found := cache.Get(999, 0) 101 | if found || time != 0 { 102 | t.Errorf("Expected not to find time for non-existing user, got %d, found: %v", time, found) 103 | } 104 | } 105 | 106 | func TestCache_GetNonExistingOperation(t *testing.T) { 107 | cache := cacheMaster.NewCache() 108 | cache.Set(123, 0, 10) 109 | 110 | time, found := cache.Get(123, 1) 111 | if found || time != 0 { 112 | t.Errorf("Expected not to find time for non-existing operation, got %d, found: %v", time, found) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /backend/tests/calculator_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "errors" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/calculator" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestCalculate(t *testing.T) { 11 | tests := []struct { 12 | number1 float64 13 | number2 float64 14 | operation string 15 | expected float64 16 | err error 17 | }{ 18 | {5, 2, "+", 7, nil}, 19 | {5, 2, "-", 3, nil}, 20 | {5, 2, "*", 10, nil}, 21 | {5, 2, "/", 2.5, nil}, 22 | {5, 0, "/", 0, errors.New("division by zero")}, 23 | {5, 2, "%", 0, errors.New("invalid operation")}, 24 | } 25 | 26 | for _, test := range tests { 27 | result, err := calculator.Calculate(test.number1, test.number2, test.operation) 28 | if err != nil && err.Error() != test.err.Error() { 29 | t.Errorf("Calculate(%f, %f, %s) returned unexpected error: got %v, want %v", test.number1, test.number2, test.operation, err, test.err) 30 | } 31 | if result != test.expected { 32 | t.Errorf("Calculate(%f, %f, %s) returned unexpected result: got %f, want %f", test.number1, test.number2, test.operation, result, test.expected) 33 | } 34 | } 35 | } 36 | 37 | func TestInfixToPostfix(t *testing.T) { 38 | tests := []struct { 39 | expression string 40 | expected []string 41 | }{ 42 | {"1+2", []string{"1", "2", "+"}}, 43 | {"(1+2)*3", []string{"1", "2", "+", "3", "*"}}, 44 | {"1*2+3", []string{"1", "2", "*", "3", "+"}}, 45 | {"(1+2)*3+4", []string{"1", "2", "+", "3", "*", "4", "+"}}, 46 | } 47 | 48 | for _, test := range tests { 49 | result := calculator.InfixToPostfix(test.expression) 50 | if !reflect.DeepEqual(result, test.expected) { 51 | t.Errorf("InfixToPostfix(%s) returned unexpected result: got %v, want %v", test.expression, result, test.expected) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /backend/tests/models_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestChangeData(t *testing.T) { 10 | expr := models.Expression{ 11 | ID: 1, 12 | Expression: "2+3", 13 | Status: "pending", 14 | Result: nil, 15 | CreatedAt: "01-01-2022 10:00:00", 16 | FinishedAt: nil, 17 | UserID: 1, 18 | } 19 | 20 | expectedStatus := "completed" 21 | expectedResult := 5.0 22 | expr.ChangeData(expectedStatus, expectedResult) 23 | 24 | if expr.Status != expectedStatus { 25 | t.Errorf("ChangeData() failed to update status. Got: %s, Expected: %s", expr.Status, expectedStatus) 26 | } 27 | 28 | if *expr.Result != expectedResult { 29 | t.Errorf("ChangeData() failed to update result. Got: %f, Expected: %f", *expr.Result, expectedResult) 30 | } 31 | 32 | currentTime := time.Now().Format("02-01-2006 15:04:05") 33 | if *expr.FinishedAt != currentTime { 34 | t.Errorf("ChangeData() failed to update finished time. Got: %s, Expected: %s", *expr.FinishedAt, currentTime) 35 | } 36 | } 37 | 38 | func TestNewExpression(t *testing.T) { 39 | expression := "2*3" 40 | status := "pending" 41 | userID := 1 42 | startTime := time.Now().Format("02-01-2006 15:04:05") 43 | expr := models.NewExpression(expression, status, userID) 44 | 45 | if expr.Expression != expression { 46 | t.Errorf("NewExpression() failed to set expression. Got: %s, Expected: %s", expr.Expression, expression) 47 | } 48 | 49 | if expr.Status != status { 50 | t.Errorf("NewExpression() failed to set status. Got: %s, Expected: %s", expr.Status, status) 51 | } 52 | 53 | if expr.UserID != userID { 54 | t.Errorf("NewExpression() failed to set userID. Got: %d, Expected: %d", expr.UserID, userID) 55 | } 56 | 57 | if expr.CreatedAt != startTime { 58 | t.Errorf("NewExpression() failed to set created time. Got: %s, Expected: %s", expr.CreatedAt, startTime) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /backend/tests/orchestra_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/cacheMaster" 6 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/orchestratorAndAgents" 7 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 8 | "log" 9 | "sync" 10 | "testing" 11 | ) 12 | 13 | func TestOrchestrator(t *testing.T) { 14 | expression := models.Expression{ 15 | Expression: "2 + 3 * 4", 16 | UserID: 1, 17 | } 18 | 19 | answerCh := make(chan float64) 20 | errCh := make(chan error) 21 | 22 | cacheMaster.OperationCache.Set(expression.UserID, cacheMaster.Operations["+"], 1) 23 | 24 | var wg sync.WaitGroup 25 | wg.Add(1) 26 | go func() { 27 | defer wg.Done() 28 | orchestratorAndAgents.Orchestrator(expression, answerCh, errCh) 29 | }() 30 | 31 | select { 32 | case result := <-answerCh: 33 | fmt.Println("Result:", result) 34 | case err := <-errCh: 35 | log.Println("Error:", err) 36 | } 37 | 38 | wg.Wait() 39 | } 40 | -------------------------------------------------------------------------------- /backend/tests/queueManager_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/internal/queueMaster" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 6 | "testing" 7 | ) 8 | 9 | func TestEnqueueDequeue(t *testing.T) { 10 | q := queueMaster.ExpressionQueue() 11 | 12 | q.Enqueue(models.Expression{Expression: "1+2"}) 13 | q.Enqueue(models.Expression{Expression: "3*4"}) 14 | q.Enqueue(models.Expression{Expression: "(5-6)/2"}) 15 | 16 | expected := []string{"1+2", "3*4", "(5-6)/2"} 17 | for _, expr := range expected { 18 | result, ok := q.Dequeue() 19 | if !ok { 20 | t.Errorf("Dequeue failed unexpectedly") 21 | } 22 | if result.Expression != expr { 23 | t.Errorf("Dequeue() = %s; want %s", result.Expression, expr) 24 | } 25 | } 26 | 27 | _, ok := q.Dequeue() 28 | if ok { 29 | t.Errorf("Dequeue should fail on empty queue") 30 | } 31 | } 32 | 33 | func TestEnqueueList(t *testing.T) { 34 | q := queueMaster.ExpressionQueue() 35 | 36 | data := []models.Expression{ 37 | {Expression: "1+2"}, 38 | {Expression: "3*4"}, 39 | {Expression: "(5-6)/2"}, 40 | } 41 | q.EnqueueList(data) 42 | 43 | expected := []string{"1+2", "3*4", "(5-6)/2"} 44 | for _, expr := range expected { 45 | result, ok := q.Dequeue() 46 | if !ok { 47 | t.Errorf("Dequeue failed unexpectedly") 48 | } 49 | if result.Expression != expr { 50 | t.Errorf("Dequeue() = %s; want %s", result.Expression, expr) 51 | } 52 | } 53 | 54 | _, ok := q.Dequeue() 55 | if ok { 56 | t.Errorf("Dequeue should fail on empty queue") 57 | } 58 | 59 | q.EnqueueList([]models.Expression{}) 60 | _, ok = q.Dequeue() 61 | if ok { 62 | t.Errorf("Dequeue should fail on empty queue after enqueuing an empty list") 63 | } 64 | } 65 | 66 | func TestEmptyDequeue(t *testing.T) { 67 | q := queueMaster.ExpressionQueue() 68 | 69 | _, ok := q.Dequeue() 70 | if ok { 71 | t.Errorf("Dequeue should fail on empty queue") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /backend/tests/servers_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestNewServersManager(t *testing.T) { 10 | quantity := 3 11 | sm := models.NewServersManager(quantity) 12 | 13 | if sm.ServersQuantity != quantity { 14 | t.Errorf("NewServersManager() failed to set ServersQuantity. Got: %d, Expected: %d", sm.ServersQuantity, quantity) 15 | } 16 | 17 | if sm.Servers.Servers == nil { 18 | t.Error("NewServersManager() failed to initialize Servers map") 19 | } 20 | } 21 | 22 | func TestInitServers(t *testing.T) { 23 | quantity := 3 24 | sm := models.NewServersManager(quantity) 25 | 26 | sm.InitServers() 27 | 28 | if len(sm.Servers.Servers) != quantity { 29 | t.Errorf("InitServers() failed to initialize correct number of servers. Got: %d, Expected: %d", len(sm.Servers.Servers), quantity) 30 | } 31 | } 32 | 33 | func TestUpdateServers(t *testing.T) { 34 | sm := models.NewServersManager(1) 35 | sm.InitServers() 36 | 37 | id := 1 38 | operation := "test operation" 39 | status := "test status" 40 | 41 | sm.UpdateServers(id, operation, status) 42 | 43 | server, exists := sm.Servers.Servers[id] 44 | if !exists { 45 | t.Fatalf("UpdateServers() failed to find server with ID: %d", id) 46 | } 47 | 48 | if server.Tasks != operation { 49 | t.Errorf("UpdateServers() failed to set correct operation. Got: %s, Expected: %s", server.Tasks, operation) 50 | } 51 | 52 | if server.Status != status { 53 | t.Errorf("UpdateServers() failed to set correct status. Got: %s, Expected: %s", server.Status, status) 54 | } 55 | 56 | lastPing := time.Now().Format("02-01-2006 15:04:05") 57 | if server.LastPing != lastPing { 58 | t.Errorf("UpdateServers() failed to update LastPing. Got: %s, Expected: %s", server.LastPing, lastPing) 59 | } 60 | } 61 | 62 | func TestSendHeartbeat(t *testing.T) { 63 | sm := models.NewServersManager(1) 64 | sm.InitServers() 65 | 66 | id := 1 67 | 68 | sm.SendHeartbeat(id) 69 | 70 | server, exists := sm.Servers.Servers[id] 71 | if !exists { 72 | t.Fatalf("SendHeartbeat() failed to find server with ID: %d", id) 73 | } 74 | 75 | lastPing := time.Now().Format("02-01-2006 15:04:05") 76 | if server.LastPing != lastPing { 77 | t.Errorf("SendHeartbeat() failed to update LastPing. Got: %s, Expected: %s", server.LastPing, lastPing) 78 | } 79 | } 80 | 81 | func TestRunServers(t *testing.T) { 82 | sm := models.NewServersManager(1) 83 | sm.InitServers() 84 | 85 | go sm.RunServers() 86 | 87 | time.Sleep(3 * time.Second) 88 | 89 | for _, server := range sm.Servers.Servers { 90 | lastPing := time.Now().Add(-3 * time.Second).Format("02-01-2006 15:04:05") 91 | if server.LastPing != lastPing { 92 | t.Errorf("RunServers() failed to update LastPing for server %d. Got: %s, Expected: %s", server.ID, server.LastPing, lastPing) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /backend/tests/stack_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "testing" 6 | ) 7 | 8 | func TestStack_IsEmpty(t *testing.T) { 9 | stack := models.Stack{} 10 | 11 | if !stack.IsEmpty() { 12 | t.Errorf("IsEmpty() failed for empty stack. Expected: true") 13 | } 14 | 15 | stack.Push("item") 16 | 17 | if stack.IsEmpty() { 18 | t.Errorf("IsEmpty() failed for non-empty stack. Expected: false") 19 | } 20 | } 21 | 22 | func TestStack_Push(t *testing.T) { 23 | stack := models.Stack{} 24 | 25 | stack.Push("item") 26 | 27 | if len(stack) != 1 { 28 | t.Errorf("Push() failed. Stack length should be 1 after pushing, got %d", len(stack)) 29 | } 30 | 31 | if stack[0] != "item" { 32 | t.Errorf("Push() failed. Top element should be 'item', got %s", stack[0]) 33 | } 34 | } 35 | 36 | func TestStack_Pop(t *testing.T) { 37 | stack := models.Stack{"item1", "item2", "item3"} 38 | 39 | popped := stack.Pop() 40 | 41 | if !popped { 42 | t.Errorf("Pop() failed. Should return true for non-empty stack") 43 | } 44 | 45 | if len(stack) != 2 { 46 | t.Errorf("Pop() failed. Stack length should be 2 after popping, got %d", len(stack)) 47 | } 48 | 49 | if stack.Top() != "item2" { 50 | t.Errorf("Pop() failed. Top element should be 'item2', got %s", stack.Top()) 51 | } 52 | 53 | emptyStack := models.Stack{} 54 | poppedEmpty := emptyStack.Pop() 55 | 56 | if poppedEmpty { 57 | t.Errorf("Pop() failed. Should return false for empty stack") 58 | } 59 | } 60 | 61 | func TestStack_Top(t *testing.T) { 62 | stack := models.Stack{"item1", "item2", "item3"} 63 | 64 | top := stack.Top() 65 | 66 | if top != "item3" { 67 | t.Errorf("Top() failed. Top element should be 'item3', got %s", top) 68 | } 69 | 70 | emptyStack := models.Stack{} 71 | topEmpty := emptyStack.Top() 72 | 73 | if topEmpty != "" { 74 | t.Errorf("Top() failed. Should return empty string for empty stack") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /backend/tests/templateMessage_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "testing" 6 | ) 7 | 8 | func TestTemplateMessage_AddData(t *testing.T) { 9 | expr := &models.Expression{ID: 1, Expression: "2+3", Status: "pending", Result: nil, CreatedAt: "01-01-2022 10:00:00", FinishedAt: nil, UserID: 1} 10 | msg := "Test message" 11 | 12 | m := &models.TemplateMessage{} 13 | m.AddData(msg, expr) 14 | 15 | if m.Expression != expr { 16 | t.Errorf("AddData() failed to set Expression correctly. Got: %v, Expected: %v", m.Expression, expr) 17 | } 18 | 19 | if m.Message != msg { 20 | t.Errorf("AddData() failed to set Message correctly. Got: %s, Expected: %s", m.Message, msg) 21 | } 22 | } 23 | 24 | func TestTemplateMessage_ChangeExpression(t *testing.T) { 25 | expr1 := &models.Expression{ID: 1, Expression: "2+3", Status: "pending", Result: nil, CreatedAt: "01-01-2022 10:00:00", FinishedAt: nil, UserID: 1} 26 | expr2 := &models.Expression{ID: 2, Expression: "3*4", Status: "pending", Result: nil, CreatedAt: "01-01-2022 10:00:00", FinishedAt: nil, UserID: 1} 27 | 28 | m := &models.TemplateMessage{Expression: expr1} 29 | m.ChangeExpression(expr2) 30 | 31 | if m.Expression != expr2 { 32 | t.Errorf("ChangeExpression() failed to change Expression. Got: %v, Expected: %v", m.Expression, expr2) 33 | } 34 | } 35 | 36 | func TestTemplateMessage_ChangeMessage(t *testing.T) { 37 | msg1 := "Test message 1" 38 | msg2 := "Test message 2" 39 | 40 | m := &models.TemplateMessage{Message: msg1} 41 | m.ChangeMessage(msg2) 42 | 43 | if m.Message != msg2 { 44 | t.Errorf("ChangeMessage() failed to change Message. Got: %s, Expected: %s", m.Message, msg2) 45 | } 46 | } 47 | 48 | func TestCreateNewTemplateMessage(t *testing.T) { 49 | m := models.CreateNewTemplateMessage() 50 | 51 | if m.Expression != nil { 52 | t.Errorf("CreateNewTemplateMessage() failed to initialize Expression. Got: %v, Expected: nil", m.Expression) 53 | } 54 | 55 | if m.Message != "" { 56 | t.Errorf("CreateNewTemplateMessage() failed to initialize Message. Got: %s, Expected: ''", m.Message) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/tests/test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KFN002/distributed-arithmetic-expression-evaluator/2afca86a33e27a96ee0ed50316e9aed3eb968737/backend/tests/test.db -------------------------------------------------------------------------------- /backend/tests/utils_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/models" 5 | "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/backend/pkg/utils" 6 | "testing" 7 | ) 8 | 9 | func TestSumList(t *testing.T) { 10 | tests := []struct { 11 | data []float64 12 | expected float64 13 | }{ 14 | {[]float64{1, 2, 3}, 6}, 15 | {[]float64{-1, -2, -3}, -6}, 16 | {[]float64{}, 0}, 17 | } 18 | 19 | for _, test := range tests { 20 | result := utils.SumList(test.data) 21 | if result != test.expected { 22 | t.Errorf("SumList(%v) = %f; want %f", test.data, result, test.expected) 23 | } 24 | } 25 | } 26 | 27 | // Проверка после регулярного выражения 28 | func TestCheckExpression(t *testing.T) { 29 | tests := []struct { 30 | expression string 31 | expected bool 32 | }{ 33 | {"(2+3)*5", true}, 34 | {"2+3+5", true}, 35 | {"2+3/1", true}, 36 | {"2*3-1", true}, 37 | {"(2+3*5)", true}, 38 | {"(2+3)*(5-1)", true}, 39 | {"(2+3++5)", false}, 40 | {"(2+(3++5))", false}, 41 | {"2+3+5)", false}, 42 | {"2+3+(5", false}, 43 | {"2+3/0", false}, 44 | {"2+3/", false}, 45 | {"2++3", false}, 46 | {"2+*3", false}, 47 | {"(2+3", false}, 48 | {"2+3*5)", false}, 49 | {"2+(3++5)", false}, 50 | {"+2+3", false}, 51 | {"2+3+", false}, 52 | } 53 | 54 | for _, test := range tests { 55 | result := utils.CheckExpression(test.expression) 56 | if result != test.expected { 57 | t.Errorf("CheckExpression(%s) = %t; want %t", test.expression, result, test.expected) 58 | } 59 | } 60 | } 61 | 62 | func TestFlipList(t *testing.T) { 63 | tests := []struct { 64 | list []models.Expression 65 | expected []models.Expression 66 | }{ 67 | {[]models.Expression{{Expression: "a"}, {Expression: "b"}, {Expression: "c"}}, []models.Expression{{Expression: "c"}, {Expression: "b"}, {Expression: "a"}}}, 68 | {[]models.Expression{}, []models.Expression{}}, 69 | } 70 | 71 | for _, test := range tests { 72 | result := utils.FlipList(test.list) 73 | for i := range result { 74 | if result[i].Expression != test.expected[i].Expression { 75 | t.Errorf("FlipList(%v) = %v; want %v", test.list, result, test.expected) 76 | break 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /calculationServer/cmd/server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a base image with Golang installed 2 | FROM golang:1.21.0 AS build 3 | 4 | # Set the working directory 5 | WORKDIR /app 6 | 7 | # Copy the source code into the container 8 | COPY ./calculationServer . 9 | 10 | # Build the server binary 11 | RUN CGO_ENABLED=1 go build -o calculation-server ./cmd/server 12 | 13 | # Use a lightweight base image for the final container 14 | FROM alpine:latest 15 | 16 | # Set the working directory 17 | WORKDIR /app 18 | 19 | # Copy the server binary from the build stage 20 | COPY --from=build /app/calculation-server /app/ 21 | 22 | # Expose the port used by your calculation server 23 | EXPOSE 8050 24 | 25 | # Command to run the calculation server 26 | CMD ["./calculation-server"] 27 | -------------------------------------------------------------------------------- /calculationServer/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | pb "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/proto" 8 | "google.golang.org/grpc" 9 | "log" 10 | "net" 11 | ) 12 | 13 | type server struct { 14 | pb.AgentServiceServer 15 | } 16 | 17 | func (s *server) Calculate(ctx context.Context, req *pb.CalculationRequest) (*pb.CalculationResponse, error) { 18 | log.Println("gRPC server live!") 19 | 20 | number1 := float64(req.GetFirstNumber()) 21 | number2 := float64(req.GetSecondNumber()) 22 | operation := req.GetOperation() 23 | 24 | var result float64 25 | 26 | switch operation { 27 | case "+": 28 | result = number1 + number2 29 | case "-": 30 | result = number1 - number2 31 | case "*": 32 | result = number1 * number2 33 | case "/": 34 | if number2 == 0 { 35 | return nil, errors.New("division by zero") 36 | } 37 | result = number1 / number2 38 | default: 39 | return nil, errors.New("invalid operation") 40 | } 41 | 42 | log.Println(result) 43 | 44 | return &pb.CalculationResponse{Result: float32(result)}, nil 45 | } 46 | 47 | func main() { 48 | host := "localhost" 49 | port := "8050" 50 | 51 | addr := fmt.Sprintf("%s:%s", host, port) 52 | lis, err := net.Listen("tcp", addr) 53 | if err != nil { 54 | log.Fatalf("error starting TCP listener: %v", err) 55 | } 56 | 57 | log.Printf("TCP listener started at port: %s", port) 58 | 59 | grpcServer := grpc.NewServer() 60 | pb.RegisterAgentServiceServer(grpcServer, &server{}) 61 | 62 | log.Printf("gRPC server listening at %s", addr) 63 | 64 | if err := grpcServer.Serve(lis); err != nil { 65 | log.Fatalf("error serving gRPC: %v", err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | calculation-server: 5 | build: 6 | context: . 7 | dockerfile: calculationServer/cmd/server/Dockerfile 8 | ports: 9 | - "8050:8050" 10 | 11 | backend-server: 12 | build: 13 | context: . 14 | dockerfile: backend/cmd/app/Dockerfile 15 | ports: 16 | - "8080:8080" 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/KFN002/distributed-arithmetic-expression-evaluator.git 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/golang-jwt/jwt/v5 v5.2.1 7 | github.com/gorilla/mux v1.8.1 8 | github.com/gorilla/sessions v1.2.2 9 | github.com/mattn/go-sqlite3 v1.14.22 10 | golang.org/x/crypto v0.22.0 11 | google.golang.org/grpc v1.63.2 12 | google.golang.org/protobuf v1.33.0 13 | ) 14 | 15 | require ( 16 | github.com/gorilla/securecookie v1.1.2 // indirect 17 | golang.org/x/net v0.21.0 // indirect 18 | golang.org/x/sys v0.19.0 // indirect 19 | golang.org/x/text v0.14.0 // indirect 20 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 2 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 3 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 6 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 7 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 8 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 9 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 10 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 11 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 12 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 13 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 14 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 15 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 16 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 17 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 18 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 19 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 20 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 21 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 22 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 23 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= 24 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= 25 | google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= 26 | google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= 27 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 28 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 29 | -------------------------------------------------------------------------------- /proto/agent.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.28.1 4 | // protoc v5.26.0 5 | // source: proto/agent.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type CalculationRequest struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | FirstNumber float32 `protobuf:"fixed32,1,opt,name=first_number,json=firstNumber,proto3" json:"first_number,omitempty"` 29 | SecondNumber float32 `protobuf:"fixed32,2,opt,name=second_number,json=secondNumber,proto3" json:"second_number,omitempty"` 30 | Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"` 31 | } 32 | 33 | func (x *CalculationRequest) Reset() { 34 | *x = CalculationRequest{} 35 | if protoimpl.UnsafeEnabled { 36 | mi := &file_proto_agent_proto_msgTypes[0] 37 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 38 | ms.StoreMessageInfo(mi) 39 | } 40 | } 41 | 42 | func (x *CalculationRequest) String() string { 43 | return protoimpl.X.MessageStringOf(x) 44 | } 45 | 46 | func (*CalculationRequest) ProtoMessage() {} 47 | 48 | func (x *CalculationRequest) ProtoReflect() protoreflect.Message { 49 | mi := &file_proto_agent_proto_msgTypes[0] 50 | if protoimpl.UnsafeEnabled && x != nil { 51 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 52 | if ms.LoadMessageInfo() == nil { 53 | ms.StoreMessageInfo(mi) 54 | } 55 | return ms 56 | } 57 | return mi.MessageOf(x) 58 | } 59 | 60 | // Deprecated: Use CalculationRequest.ProtoReflect.Descriptor instead. 61 | func (*CalculationRequest) Descriptor() ([]byte, []int) { 62 | return file_proto_agent_proto_rawDescGZIP(), []int{0} 63 | } 64 | 65 | func (x *CalculationRequest) GetFirstNumber() float32 { 66 | if x != nil { 67 | return x.FirstNumber 68 | } 69 | return 0 70 | } 71 | 72 | func (x *CalculationRequest) GetSecondNumber() float32 { 73 | if x != nil { 74 | return x.SecondNumber 75 | } 76 | return 0 77 | } 78 | 79 | func (x *CalculationRequest) GetOperation() string { 80 | if x != nil { 81 | return x.Operation 82 | } 83 | return "" 84 | } 85 | 86 | type CalculationResponse struct { 87 | state protoimpl.MessageState 88 | sizeCache protoimpl.SizeCache 89 | unknownFields protoimpl.UnknownFields 90 | 91 | Result float32 `protobuf:"fixed32,1,opt,name=result,proto3" json:"result,omitempty"` 92 | } 93 | 94 | func (x *CalculationResponse) Reset() { 95 | *x = CalculationResponse{} 96 | if protoimpl.UnsafeEnabled { 97 | mi := &file_proto_agent_proto_msgTypes[1] 98 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 | ms.StoreMessageInfo(mi) 100 | } 101 | } 102 | 103 | func (x *CalculationResponse) String() string { 104 | return protoimpl.X.MessageStringOf(x) 105 | } 106 | 107 | func (*CalculationResponse) ProtoMessage() {} 108 | 109 | func (x *CalculationResponse) ProtoReflect() protoreflect.Message { 110 | mi := &file_proto_agent_proto_msgTypes[1] 111 | if protoimpl.UnsafeEnabled && x != nil { 112 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 113 | if ms.LoadMessageInfo() == nil { 114 | ms.StoreMessageInfo(mi) 115 | } 116 | return ms 117 | } 118 | return mi.MessageOf(x) 119 | } 120 | 121 | // Deprecated: Use CalculationResponse.ProtoReflect.Descriptor instead. 122 | func (*CalculationResponse) Descriptor() ([]byte, []int) { 123 | return file_proto_agent_proto_rawDescGZIP(), []int{1} 124 | } 125 | 126 | func (x *CalculationResponse) GetResult() float32 { 127 | if x != nil { 128 | return x.Result 129 | } 130 | return 0 131 | } 132 | 133 | var File_proto_agent_proto protoreflect.FileDescriptor 134 | 135 | var file_proto_agent_proto_rawDesc = []byte{ 136 | 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 137 | 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x7a, 0x0a, 0x12, 0x43, 0x61, 138 | 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 139 | 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 140 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4e, 0x75, 0x6d, 141 | 0x62, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x6e, 0x75, 142 | 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0c, 0x73, 0x65, 0x63, 0x6f, 143 | 0x6e, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 144 | 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 145 | 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2d, 0x0a, 0x13, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 146 | 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 147 | 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x72, 148 | 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0x52, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 149 | 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 150 | 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x61, 0x6c, 0x63, 0x75, 151 | 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 152 | 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 153 | 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x49, 0x5a, 0x47, 0x67, 0x69, 0x74, 154 | 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4b, 0x46, 0x4e, 0x30, 0x30, 0x32, 0x2f, 0x64, 155 | 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x72, 0x69, 0x74, 0x68, 156 | 0x6d, 0x65, 0x74, 0x69, 0x63, 0x2d, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 157 | 0x2d, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x67, 0x69, 0x74, 0x2f, 0x70, 158 | 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 159 | } 160 | 161 | var ( 162 | file_proto_agent_proto_rawDescOnce sync.Once 163 | file_proto_agent_proto_rawDescData = file_proto_agent_proto_rawDesc 164 | ) 165 | 166 | func file_proto_agent_proto_rawDescGZIP() []byte { 167 | file_proto_agent_proto_rawDescOnce.Do(func() { 168 | file_proto_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_agent_proto_rawDescData) 169 | }) 170 | return file_proto_agent_proto_rawDescData 171 | } 172 | 173 | var file_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 174 | var file_proto_agent_proto_goTypes = []interface{}{ 175 | (*CalculationRequest)(nil), // 0: agent.CalculationRequest 176 | (*CalculationResponse)(nil), // 1: agent.CalculationResponse 177 | } 178 | var file_proto_agent_proto_depIdxs = []int32{ 179 | 0, // 0: agent.AgentService.Calculate:input_type -> agent.CalculationRequest 180 | 1, // 1: agent.AgentService.Calculate:output_type -> agent.CalculationResponse 181 | 1, // [1:2] is the sub-list for method output_type 182 | 0, // [0:1] is the sub-list for method input_type 183 | 0, // [0:0] is the sub-list for extension type_name 184 | 0, // [0:0] is the sub-list for extension extendee 185 | 0, // [0:0] is the sub-list for field type_name 186 | } 187 | 188 | func init() { file_proto_agent_proto_init() } 189 | func file_proto_agent_proto_init() { 190 | if File_proto_agent_proto != nil { 191 | return 192 | } 193 | if !protoimpl.UnsafeEnabled { 194 | file_proto_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 195 | switch v := v.(*CalculationRequest); i { 196 | case 0: 197 | return &v.state 198 | case 1: 199 | return &v.sizeCache 200 | case 2: 201 | return &v.unknownFields 202 | default: 203 | return nil 204 | } 205 | } 206 | file_proto_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 207 | switch v := v.(*CalculationResponse); i { 208 | case 0: 209 | return &v.state 210 | case 1: 211 | return &v.sizeCache 212 | case 2: 213 | return &v.unknownFields 214 | default: 215 | return nil 216 | } 217 | } 218 | } 219 | type x struct{} 220 | out := protoimpl.TypeBuilder{ 221 | File: protoimpl.DescBuilder{ 222 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 223 | RawDescriptor: file_proto_agent_proto_rawDesc, 224 | NumEnums: 0, 225 | NumMessages: 2, 226 | NumExtensions: 0, 227 | NumServices: 1, 228 | }, 229 | GoTypes: file_proto_agent_proto_goTypes, 230 | DependencyIndexes: file_proto_agent_proto_depIdxs, 231 | MessageInfos: file_proto_agent_proto_msgTypes, 232 | }.Build() 233 | File_proto_agent_proto = out.File 234 | file_proto_agent_proto_rawDesc = nil 235 | file_proto_agent_proto_goTypes = nil 236 | file_proto_agent_proto_depIdxs = nil 237 | } 238 | -------------------------------------------------------------------------------- /proto/agent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package agent; 4 | 5 | option go_package = "github.com/KFN002/distributed-arithmetic-expression-evaluator.git/proto"; 6 | 7 | message CalculationRequest { 8 | float first_number = 1; 9 | float second_number = 2; 10 | string operation = 3; 11 | } 12 | 13 | message CalculationResponse { 14 | float result = 1; 15 | } 16 | 17 | service AgentService { 18 | rpc Calculate(CalculationRequest) returns (CalculationResponse); 19 | } 20 | -------------------------------------------------------------------------------- /proto/agent_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v5.26.0 5 | // source: proto/agent.proto 6 | 7 | package proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // AgentServiceClient is the client API for AgentService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type AgentServiceClient interface { 25 | Calculate(ctx context.Context, in *CalculationRequest, opts ...grpc.CallOption) (*CalculationResponse, error) 26 | } 27 | 28 | type agentServiceClient struct { 29 | cc grpc.ClientConnInterface 30 | } 31 | 32 | func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient { 33 | return &agentServiceClient{cc} 34 | } 35 | 36 | func (c *agentServiceClient) Calculate(ctx context.Context, in *CalculationRequest, opts ...grpc.CallOption) (*CalculationResponse, error) { 37 | out := new(CalculationResponse) 38 | err := c.cc.Invoke(ctx, "/agent.AgentService/Calculate", in, out, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return out, nil 43 | } 44 | 45 | // AgentServiceServer is the server API for AgentService service. 46 | // All implementations must embed UnimplementedAgentServiceServer 47 | // for forward compatibility 48 | type AgentServiceServer interface { 49 | Calculate(context.Context, *CalculationRequest) (*CalculationResponse, error) 50 | mustEmbedUnimplementedAgentServiceServer() 51 | } 52 | 53 | // UnimplementedAgentServiceServer must be embedded to have forward compatible implementations. 54 | type UnimplementedAgentServiceServer struct { 55 | } 56 | 57 | func (UnimplementedAgentServiceServer) Calculate(context.Context, *CalculationRequest) (*CalculationResponse, error) { 58 | return nil, status.Errorf(codes.Unimplemented, "method Calculate not implemented") 59 | } 60 | func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {} 61 | 62 | // UnsafeAgentServiceServer may be embedded to opt out of forward compatibility for this service. 63 | // Use of this interface is not recommended, as added methods to AgentServiceServer will 64 | // result in compilation errors. 65 | type UnsafeAgentServiceServer interface { 66 | mustEmbedUnimplementedAgentServiceServer() 67 | } 68 | 69 | func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer) { 70 | s.RegisterService(&AgentService_ServiceDesc, srv) 71 | } 72 | 73 | func _AgentService_Calculate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 74 | in := new(CalculationRequest) 75 | if err := dec(in); err != nil { 76 | return nil, err 77 | } 78 | if interceptor == nil { 79 | return srv.(AgentServiceServer).Calculate(ctx, in) 80 | } 81 | info := &grpc.UnaryServerInfo{ 82 | Server: srv, 83 | FullMethod: "/agent.AgentService/Calculate", 84 | } 85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 86 | return srv.(AgentServiceServer).Calculate(ctx, req.(*CalculationRequest)) 87 | } 88 | return interceptor(ctx, in, info, handler) 89 | } 90 | 91 | // AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service. 92 | // It's only intended for direct use with grpc.RegisterService, 93 | // and not to be introspected or modified (even as a copy) 94 | var AgentService_ServiceDesc = grpc.ServiceDesc{ 95 | ServiceName: "agent.AgentService", 96 | HandlerType: (*AgentServiceServer)(nil), 97 | Methods: []grpc.MethodDesc{ 98 | { 99 | MethodName: "Calculate", 100 | Handler: _AgentService_Calculate_Handler, 101 | }, 102 | }, 103 | Streams: []grpc.StreamDesc{}, 104 | Metadata: "proto/agent.proto", 105 | } 106 | -------------------------------------------------------------------------------- /server.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KFN002/distributed-arithmetic-expression-evaluator/2afca86a33e27a96ee0ed50316e9aed3eb968737/server.exe -------------------------------------------------------------------------------- /start_app.bat: -------------------------------------------------------------------------------- 1 | ::[Bat To Exe Converter] 2 | :: 3 | ::YAwzoRdxOk+EWAjk 4 | ::fBw5plQjdCyDJGyX8VAjFB1RSAuWAEy1CrQS7Njp4OCCoVkOaOY2a5vJ07abNOUXp0T2fIIl239OkchBCQNIbBe4fQY7pyNHoGWJIsaIvB3dWVyI8kIzJ2RnlGbEnxcyY9xmy40K0C/e 5 | ::YAwzuBVtJxjWCl3EqQJgSA== 6 | ::ZR4luwNxJguZRRnk 7 | ::Yhs/ulQjdF+5 8 | ::cxAkpRVqdFKZSDk= 9 | ::cBs/ulQjdF+5 10 | ::ZR41oxFsdFKZSDk= 11 | ::eBoioBt6dFKZSDk= 12 | ::cRo6pxp7LAbNWATEpCI= 13 | ::egkzugNsPRvcWATEpCI= 14 | ::dAsiuh18IRvcCxnZtBJQ 15 | ::cRYluBh/LU+EWAnk 16 | ::YxY4rhs+aU+JeA== 17 | ::cxY6rQJ7JhzQF1fEqQJQ 18 | ::ZQ05rAF9IBncCkqN+0xwdVs0 19 | ::ZQ05rAF9IAHYFVzEqQJQ 20 | ::eg0/rx1wNQPfEVWB+kM9LVsJDGQ= 21 | ::fBEirQZwNQPfEVWB+kM9LVsJDGQ= 22 | ::cRolqwZ3JBvQF1fEqQJQ 23 | ::dhA7uBVwLU+EWDk= 24 | ::YQ03rBFzNR3SWATElA== 25 | ::dhAmsQZ3MwfNWATElA== 26 | ::ZQ0/vhVqMQ3MEVWAtB9wSA== 27 | ::Zg8zqx1/OA3MEVWAtB9wSA== 28 | ::dhA7pRFwIByZRRnk 29 | ::Zh4grVQjdCyDJGyX8VAjFB1RSAuWAEy1CrQS7Njp4OCCoVkOaOY2a5vJ07abNOUXp0T2fIIl239OkchBCQNIbBe4fQY7pyNHoGWJIsaIvB3daUmF5V48GnF7lS3VlC5b 30 | ::YB416Ek+ZG8= 31 | :: 32 | :: 33 | ::978f952a14a936cc963da21a135fa983 34 | @echo off 35 | start /B cmd /C "server.exe" 36 | start /B cmd /C "app.exe" 37 | timeout /t 5 /nobreak >nul 2>&1 38 | start http://localhost:8080 39 | -------------------------------------------------------------------------------- /static/assets/create_expression.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |ID | 75 |Status | 76 |Expression | 77 |Result | 78 |Creation Date | 79 |Completion Date | 80 |
---|---|---|---|---|---|
{{ .Expression.ID }} | 83 |{{ .Expression.Status }} | 84 |{{ .Expression.Expression }} | 85 |{{ .Expression.Result }} | 86 |{{ .Expression.CreatedAt }} | 87 |{{ .Expression.FinishedAt }} | 88 |
ID: {{.ID}}
45 |Status: {{.Status}}
46 |Current Task: {{.Tasks}}
47 |Last Ping: {{.LastPing}}
48 |ID | 48 |Status | 49 |Expression | 50 |Result | 51 |Creation Date | 52 |Completion Date | 53 |
---|---|---|---|---|---|
{{ .ID }} | 57 |{{ .Status }} | 58 |{{ .Expression }} | 59 |{{ .Result }} | 60 |{{ .CreatedAt }} | 61 |{{ .FinishedAt }} | 62 |