diff --git a/docker-compose.yaml b/docker-compose.yaml index d3e538d..645128f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,22 @@ services: - apache-httpd: - container_name: httpd-server +# apache-httpd: +# container_name: httpd-server-apache +# build: +# context: ./httpd/ +# dockerfile: ./Dockerfile-apache +# args: +# - APACHE_LOG_DIR="/var/log/httpd-server/" +# restart: on-failure +# ports: +# - "8080:80" +# networks: +# - internal_net + + nginx-httpd: + container_name: httpd-server-nginx build: context: ./httpd/ - args: - - APACHE_LOG_DIR="/var/log/httpd-server/" + dockerfile: ./Dockerfile-nginx restart: on-failure ports: - "8080:80" @@ -17,7 +29,8 @@ services: context: ./server/ restart: on-failure depends_on: - - apache-httpd + # - apache-httpd + - nginx-httpd networks: - internal_net diff --git a/httpd/Dockerfile b/httpd/Dockerfile-apache similarity index 77% rename from httpd/Dockerfile rename to httpd/Dockerfile-apache index 555b51c..0319d6b 100644 --- a/httpd/Dockerfile +++ b/httpd/Dockerfile-apache @@ -6,7 +6,7 @@ COPY ./static/ /var/www/localhost/htdocs/static/ # add custom apache configs RUN mkdir -p /etc/apache2/conf-custom -COPY ./conf/custom/ /etc/apache2/conf-custom/ +COPY ./apache-conf/custom/ /etc/apache2/conf-custom/ # create logs directory (temporary disabled) ARG APACHE_LOG_DIR @@ -14,6 +14,6 @@ RUN mkdir -p ${APACHE_LOG_DIR} RUN chown www-data:www-data ${APACHE_LOG_DIR} # setup main apache configuration -COPY ./conf/httpd.conf /usr/local/apache2/conf/httpd.conf +COPY ./apache-conf/httpd.conf /usr/local/apache2/conf/httpd.conf EXPOSE 80 \ No newline at end of file diff --git a/httpd/Dockerfile-nginx b/httpd/Dockerfile-nginx new file mode 100644 index 0000000..0f7508b --- /dev/null +++ b/httpd/Dockerfile-nginx @@ -0,0 +1,7 @@ +FROM nginx:latest + +COPY /nginx-conf/httpd.conf /etc/nginx/conf.d/default.conf +COPY ./static/ /var/www/html/ + +EXPOSE 80 +#EXPOSE 443 \ No newline at end of file diff --git a/httpd/conf/custom/apache-httpd-lab1.conf b/httpd/apache-conf/custom/apache-httpd-lab1.conf similarity index 85% rename from httpd/conf/custom/apache-httpd-lab1.conf rename to httpd/apache-conf/custom/apache-httpd-lab1.conf index 4b9a4d7..d0cc1e2 100644 --- a/httpd/conf/custom/apache-httpd-lab1.conf +++ b/httpd/apache-conf/custom/apache-httpd-lab1.conf @@ -16,10 +16,10 @@ # FastCGI server setup start # ========================== - ProxyPass /fcgi-bin/ fcgi://fastcgi-server:55555/ - ProxyPassReverse /fcgi-bin/ fcgi://fastcgi-server:55555/ + ProxyPass /fcgi/ fcgi://fastcgi-server:55555/ + ProxyPassReverse /fcgi/ fcgi://fastcgi-server:55555/ - + Require all granted diff --git a/httpd/conf/httpd.conf b/httpd/apache-conf/httpd.conf similarity index 100% rename from httpd/conf/httpd.conf rename to httpd/apache-conf/httpd.conf diff --git a/httpd/nginx-conf/httpd.conf b/httpd/nginx-conf/httpd.conf new file mode 100644 index 0000000..de90b04 --- /dev/null +++ b/httpd/nginx-conf/httpd.conf @@ -0,0 +1,18 @@ +server { + listen 80; + + root /var/www/html; + + # Обработка статических файлов + location / { + try_files $uri $uri/ =404; + } + + location /fcgi/ { + # Базовые настройки FastCGI + include fastcgi_params; + + # Адрес FastCGI сервера + fastcgi_pass fastcgi-server:55555; + } +} \ No newline at end of file diff --git a/httpd/static/index.html b/httpd/static/index.html index 330cf34..96dd94a 100644 --- a/httpd/static/index.html +++ b/httpd/static/index.html @@ -27,6 +27,8 @@ --svg-axis-primary: oklch(75% 0.04 260); --svg-fill-area: oklch(52% 0.07 200); + --error-bg: oklch(55% 0.15 19); + /* Всякое разное */ --btn-active-translateY: -2px; @@ -231,13 +233,9 @@ } .container { - animation: fadeIn 0.6s ease-out; + animation: containerFadeIn 0.6s ease-out; } - .form-row { - animation: fadeIn 0.8s ease-out; - } - /* Адаптивность */ @media (max-width: 768px) { .form-grid { @@ -375,7 +373,28 @@ transform: translateY(var(--btn-active-translateY)); } - /* Адаптивность */ + /* Сообщение об ошибке */ + .error-message { + position: fixed; + bottom: 20px; + right: 20px; + + background-color: var(--error-bg); + color: var(--text-primary); + + padding: 15px 20px; + + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + + z-index: 1000; + + max-width: 350px; + + animation: errorSlideIn 0.3s ease-out, errorFadeOut 0.5s ease-in 4.5s forwards; + } + + /* Адаптивность */ @media (max-width: 968px) { .container { min-width: 95%; @@ -404,7 +423,7 @@ } /* Анимации */ - @keyframes fadeIn { + @keyframes containerFadeIn { from { opacity: 0; transform: translateY(20px); @@ -414,6 +433,27 @@ transform: translateY(0); } } + + @keyframes errorSlideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes errorFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + display: none; + } + } @@ -633,8 +673,34 @@

История введенных данных

// Хранение истории данных let formHistory = JSON.parse(localStorage.getItem('formHistory')) || []; + const showError = (message) => { + // Убираем существующие сообщения + const existingErrors = document.querySelectorAll('.error-message'); + existingErrors.forEach(error => error.remove()); + + // Слздаём элемент + const errorDiv = document.createElement('div'); + errorDiv.className = 'error-message'; + errorDiv.textContent = message; + + // Вставка + document.body.appendChild(errorDiv); + + // Убираем по прошествии 5-ти секунд + setTimeout(() => { + if (errorDiv.parentNode) { + errorDiv.parentNode.removeChild(errorDiv); + } + }, 5000); + }; + const FFCGIResponceHandler = (responce, X, Y, R) => { - const JSONresponce = JSON.parse(responce); + const JSONResponce = JSON.parse(responce); + + if (JSONResponce.error) { + showError(`Ошибка: ${JSONResponce.error}`); + return; + } // Сохраняем данные в историю const formData = { @@ -642,8 +708,8 @@

История введенных данных

X: X, Y: Y, R: R, - elapsedTimeNs: JSONresponce.elapsedTimeNs, - isHitted: JSONresponce.result + elapsedTimeNs: JSONResponce.elapsedTimeNs, + isHitted: JSONResponce.result }; formHistory.push(formData); @@ -666,7 +732,10 @@

История введенных данных

const Y = document.getElementById('Y').value; if (X && R && Y) { - sendAJAXGETRequest("/fcgi-bin/", {"x": X, "y": Y, "r": R}, (resp) => {FFCGIResponceHandler(resp, X, Y, R)}); + + // Stub + sendAJAXGETRequest("/fcgi/", {"x": X, "y": Y, "r": R}, (data) => {FFCGIResponceHandler(data, X,Y,R)}); + console.log(`Данные отправлены!\nX: ${X}\nY: ${Y}\nR: ${R}`); // Очищаем форму this.reset(); diff --git a/server/.idea/misc.xml b/server/.idea/misc.xml index 82dbec8..b2eea27 100644 --- a/server/.idea/misc.xml +++ b/server/.idea/misc.xml @@ -1,14 +1,7 @@ - - - - + \ No newline at end of file diff --git a/server/.idea/vcs.xml b/server/.idea/vcs.xml index 288b36b..6c0b863 100644 --- a/server/.idea/vcs.xml +++ b/server/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/server/src/main/java/org/validator/ValidatedRecordFactory.java b/server/src/main/java/org/validator/ValidatedRecordFactory.java new file mode 100644 index 0000000..3d3d75e --- /dev/null +++ b/server/src/main/java/org/validator/ValidatedRecordFactory.java @@ -0,0 +1,22 @@ +package org.validator; + +import org.validator.validation.ValidationController; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class ValidatedRecordFactory { + @SuppressWarnings("unchecked") + public static T create(Class recordClass, Object... args) { + try { + Constructor constructor = (Constructor) recordClass.getDeclaredConstructors()[0]; + T record = constructor.newInstance(args); + + ValidationController.validateObject(record); + + return record; + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Record can't be created", e); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/annotations/NotNull.java b/server/src/main/java/org/validator/annotations/NotNull.java new file mode 100644 index 0000000..731af81 --- /dev/null +++ b/server/src/main/java/org/validator/annotations/NotNull.java @@ -0,0 +1,12 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +public @interface NotNull { + String errorMsg() default "Field required"; +} diff --git a/server/src/main/java/org/validator/annotations/Number.java b/server/src/main/java/org/validator/annotations/Number.java new file mode 100644 index 0000000..e92023f --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Number.java @@ -0,0 +1,19 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@NotNull(errorMsg = "Number field is required") +@Size(min = 1, errorMsg = "Number must not be empty") +@Pattern(pattern = "^-?\\d+(\\.\\d+)?$", errorMsg = "Must be a valid number") +public @interface Number { + String rangeErrorMsg() default "Number must be between %s and %s"; + String stepErrorMsg() default "Number distance from minimum must be divisible by %s"; + double min() default Integer.MIN_VALUE; + double max() default Integer.MAX_VALUE; + double step() default 0; +} diff --git a/server/src/main/java/org/validator/annotations/Pattern.java b/server/src/main/java/org/validator/annotations/Pattern.java new file mode 100644 index 0000000..ca18e9c --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Pattern.java @@ -0,0 +1,14 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +@NotNull +public @interface Pattern { + String errorMsg() default "Pattern mismatch"; + String pattern(); +} diff --git a/server/src/main/java/org/validator/annotations/Size.java b/server/src/main/java/org/validator/annotations/Size.java new file mode 100644 index 0000000..e923d5b --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Size.java @@ -0,0 +1,15 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +@NotNull +public @interface Size { + String errorMsg() default "String size must be between %d and %d"; + int min() default 0; + long max() default Integer.MAX_VALUE; +} diff --git a/server/src/main/java/org/validator/validation/ValidationController.java b/server/src/main/java/org/validator/validation/ValidationController.java new file mode 100644 index 0000000..392e6f5 --- /dev/null +++ b/server/src/main/java/org/validator/validation/ValidationController.java @@ -0,0 +1,54 @@ +package org.validator.validation; + +import org.validator.annotations.*; +import org.validator.annotations.Number; +import org.validator.validation.validators.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.HashMap; + +public class ValidationController { + + private static final HashMap, Validator> validatorCache = new HashMap<>(); + + static { + validatorCache.put(NotNull.class, new NotNullValidator()); + validatorCache.put(Size.class, new SizeValidator()); + validatorCache.put(Pattern.class, new PatternValidator()); + validatorCache.put(Number.class, new NumberValidator()); + } + + public static void validateObject(T object) { + for (Field field : object.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + + Object value = field.get(object); + validateField(value, field.getAnnotations()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to validate field: " + field.getName(), e); + } + } + } + + private static void validateField(Object value, T[] annotations) { + for (Annotation annotation : annotations) { + validateAnnotation(annotation, value); + } + } + + @SuppressWarnings("unchecked") + private static void validateAnnotation(Annotation annotation, Object value) { + Class annotationType = annotation.annotationType(); + + Validator validator = (Validator) validatorCache.get(annotationType); + if (validator == null) return; + + for (Annotation metaAnnotation : annotationType.getAnnotations()) validateAnnotation(metaAnnotation, value); + + validator.validate(annotation, value); + } +} + + diff --git a/server/src/main/java/org/validator/validation/exceptions/ValidationException.java b/server/src/main/java/org/validator/validation/exceptions/ValidationException.java new file mode 100644 index 0000000..d889557 --- /dev/null +++ b/server/src/main/java/org/validator/validation/exceptions/ValidationException.java @@ -0,0 +1,7 @@ +package org.validator.validation.exceptions; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/server/src/main/java/org/validator/validation/validators/NotNullValidator.java b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java new file mode 100644 index 0000000..37381fc --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java @@ -0,0 +1,13 @@ +package org.validator.validation.validators; + +import org.validator.annotations.NotNull; +import org.validator.validation.exceptions.ValidationException; + +public class NotNullValidator implements Validator { + @Override + public void validate(NotNull annotation, Object value) { + if (value == null) { + throw new ValidationException(annotation.errorMsg()); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/NumberValidator.java b/server/src/main/java/org/validator/validation/validators/NumberValidator.java new file mode 100644 index 0000000..4f12131 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/NumberValidator.java @@ -0,0 +1,23 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Number; +import org.validator.validation.exceptions.ValidationException; + +public class NumberValidator implements Validator { + @Override + public void validate(Number annotation, Object value) { + if (value instanceof String str) { + double num = Double.parseDouble(str); + if (num < annotation.min() || num > annotation.max()) { + throw new ValidationException( + String.format(annotation.rangeErrorMsg(), annotation.min(), annotation.max()) + ); + } + if (annotation.step() != 0 && (num-annotation.min())%annotation.step() != 0) { + throw new ValidationException( + String.format(annotation.stepErrorMsg(), annotation.step()) + ); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/PatternValidator.java b/server/src/main/java/org/validator/validation/validators/PatternValidator.java new file mode 100644 index 0000000..450bcf6 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/PatternValidator.java @@ -0,0 +1,15 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Pattern; +import org.validator.validation.exceptions.ValidationException; + +public class PatternValidator implements Validator { + @Override + public void validate(Pattern annotation, Object value) { + if (value instanceof String str) { + if (!str.matches(annotation.pattern())) { + throw new ValidationException(annotation.errorMsg()); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/SizeValidator.java b/server/src/main/java/org/validator/validation/validators/SizeValidator.java new file mode 100644 index 0000000..9e96199 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/SizeValidator.java @@ -0,0 +1,21 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Size; +import org.validator.validation.exceptions.ValidationException; + +public class SizeValidator implements Validator { + @Override + public void validate(Size annotation, Object value) { + if (value instanceof String str) { + if (str.length() < annotation.min() || str.length() > annotation.max()) { + throw new ValidationException( + String.format( + annotation.errorMsg(), + annotation.min(), + annotation.max() + ) + ); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/Validator.java b/server/src/main/java/org/validator/validation/validators/Validator.java new file mode 100644 index 0000000..990eb59 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/Validator.java @@ -0,0 +1,7 @@ +package org.validator.validation.validators; +import java.lang.annotation.Annotation; + +@FunctionalInterface +public interface Validator { + void validate(A annotation, Object value); +} \ No newline at end of file diff --git a/server/src/main/java/org/web1/DTOs/RequestDTO.java b/server/src/main/java/org/web1/DTOs/RequestDTO.java new file mode 100644 index 0000000..d811ccd --- /dev/null +++ b/server/src/main/java/org/web1/DTOs/RequestDTO.java @@ -0,0 +1,9 @@ +package org.web1.DTOs; + +import org.validator.annotations.Number; + +public record RequestDTO( + @Number(min = -4, max = 4, step = 1) String X, + @Number(min = 1, max = 3, step = 0.5) String R, + @Number(min = -3, max = 5) String Y +) {} diff --git a/server/src/main/java/org/web1/Main.java b/server/src/main/java/org/web1/Main.java index a5181cc..077ec75 100644 --- a/server/src/main/java/org/web1/Main.java +++ b/server/src/main/java/org/web1/Main.java @@ -4,12 +4,15 @@ import java.util.HashMap; import java.util.function.Function; +import org.validator.ValidatedRecordFactory; +import org.validator.validation.exceptions.ValidationException; +import org.web1.DTOs.RequestDTO; import org.web1.checkers.Checker; import org.web1.checkers.CheckerFunction; -import org.web1.utils.JsonBuilder; -import org.web1.utils.QueryStringToHashmap; -import org.web1.utils.ResponseController; -import org.web1.utils.Timer; +import org.web1.netUtils.JsonBuilder; +import org.web1.netUtils.QueryStringToHashmap; +import org.web1.netUtils.ResponseFactory; +import org.web1.timer.Timer; public class Main { private static final Function> parseQuery = new QueryStringToHashmap(); @@ -22,18 +25,37 @@ public static void main(String[] args) { while (fastCGI.FCGIaccept() >= 0) { timer.start(); - HashMap queryParams = parseQuery.apply( + final HashMap queryParams = parseQuery.apply( FCGIInterface.request.params.getProperty("QUERY_STRING") ); - boolean checkResult = checker.test(queryParams); - String result = ResponseController.create( - new JsonBuilder() - .add("result", checkResult) - .add("elapsedTimeNs", timer.stop()) - ); - - System.out.println(result); + try { + RequestDTO requestData = ValidatedRecordFactory.create( + RequestDTO.class, + queryParams.get("x"), + queryParams.get("r"), + queryParams.get("y") + ); + + boolean checkResult = checker.test( + Integer.parseInt(requestData.X()), + Float.parseFloat(requestData.Y()), + Float.parseFloat(requestData.R()) + ); + + String result = ResponseFactory.create( + new JsonBuilder() + .add("result", checkResult) + .add("elapsedTimeNs", timer.stop()) + ); + + System.out.println(result); + } catch (ValidationException e) { + String result = ResponseFactory.create( + new JsonBuilder().add("error", '"'+e.getMessage()+'"') + ); + System.out.println(result); + } } } } \ No newline at end of file diff --git a/server/src/main/java/org/web1/checkers/Checker.java b/server/src/main/java/org/web1/checkers/Checker.java index 6885c88..c65bdd8 100644 --- a/server/src/main/java/org/web1/checkers/Checker.java +++ b/server/src/main/java/org/web1/checkers/Checker.java @@ -5,11 +5,7 @@ import java.util.HashMap; public class Checker implements CheckerFunction{ - public boolean test(HashMap data) { - int x = Integer.parseInt(data.get("x")); - float y = Float.parseFloat(data.get("y")); - float r = Float.parseFloat(data.get("r")); - + public boolean test(int x, float y, float r) { final GraphQuarters quarter = GraphUtils.getQuarter(x, y); if (quarter == GraphQuarters.FIRST_QUADRANT) return firstQuarterTester(x, y, r); diff --git a/server/src/main/java/org/web1/checkers/CheckerFunction.java b/server/src/main/java/org/web1/checkers/CheckerFunction.java index 38f03ec..6948e52 100644 --- a/server/src/main/java/org/web1/checkers/CheckerFunction.java +++ b/server/src/main/java/org/web1/checkers/CheckerFunction.java @@ -4,5 +4,5 @@ @FunctionalInterface public interface CheckerFunction { - boolean test(HashMap data); + boolean test(int x, float y, float r); } diff --git a/server/src/main/java/org/web1/utils/JsonBuilder.java b/server/src/main/java/org/web1/netUtils/JsonBuilder.java similarity index 95% rename from server/src/main/java/org/web1/utils/JsonBuilder.java rename to server/src/main/java/org/web1/netUtils/JsonBuilder.java index c87b3a2..809ae58 100644 --- a/server/src/main/java/org/web1/utils/JsonBuilder.java +++ b/server/src/main/java/org/web1/netUtils/JsonBuilder.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.netUtils; import java.util.HashMap; diff --git a/server/src/main/java/org/web1/utils/QueryStringToHashmap.java b/server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java similarity index 96% rename from server/src/main/java/org/web1/utils/QueryStringToHashmap.java rename to server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java index da22d77..2f775e9 100644 --- a/server/src/main/java/org/web1/utils/QueryStringToHashmap.java +++ b/server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.netUtils; import java.util.HashMap; import java.util.function.Function; diff --git a/server/src/main/java/org/web1/utils/ResponseController.java b/server/src/main/java/org/web1/netUtils/ResponseFactory.java similarity index 87% rename from server/src/main/java/org/web1/utils/ResponseController.java rename to server/src/main/java/org/web1/netUtils/ResponseFactory.java index 8032608..1b9a7f2 100644 --- a/server/src/main/java/org/web1/utils/ResponseController.java +++ b/server/src/main/java/org/web1/netUtils/ResponseFactory.java @@ -1,6 +1,6 @@ -package org.web1.utils; +package org.web1.netUtils; -public class ResponseController { +public class ResponseFactory { private static final String BASE_RESPONSE = """ Access-Control-Allow-Origin: * Connection: keep-alive diff --git a/server/src/main/java/org/web1/utils/Timer.java b/server/src/main/java/org/web1/timer/Timer.java similarity index 89% rename from server/src/main/java/org/web1/utils/Timer.java rename to server/src/main/java/org/web1/timer/Timer.java index 48adcff..9531368 100644 --- a/server/src/main/java/org/web1/utils/Timer.java +++ b/server/src/main/java/org/web1/timer/Timer.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.timer; public class Timer { long startTime;