diff --git a/.gitignore b/.gitignore index 9f11b75..e5a4ede 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .idea/ +build +.gradle diff --git a/.gradle/7.5.1/checksums/checksums.lock b/.gradle/7.5.1/checksums/checksums.lock deleted file mode 100644 index a03e8ed..0000000 Binary files a/.gradle/7.5.1/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/7.5.1/checksums/md5-checksums.bin b/.gradle/7.5.1/checksums/md5-checksums.bin deleted file mode 100644 index a9dfcd7..0000000 Binary files a/.gradle/7.5.1/checksums/md5-checksums.bin and /dev/null differ diff --git a/.gradle/7.5.1/checksums/sha1-checksums.bin b/.gradle/7.5.1/checksums/sha1-checksums.bin deleted file mode 100644 index 1fa5fcd..0000000 Binary files a/.gradle/7.5.1/checksums/sha1-checksums.bin and /dev/null differ diff --git a/.gradle/7.5.1/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.5.1/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index 09280f7..0000000 Binary files a/.gradle/7.5.1/dependencies-accessors/dependencies-accessors.lock and /dev/null differ diff --git a/.gradle/7.5.1/dependencies-accessors/gc.properties b/.gradle/7.5.1/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/7.5.1/executionHistory/executionHistory.bin b/.gradle/7.5.1/executionHistory/executionHistory.bin deleted file mode 100644 index 955abc9..0000000 Binary files a/.gradle/7.5.1/executionHistory/executionHistory.bin and /dev/null differ diff --git a/.gradle/7.5.1/executionHistory/executionHistory.lock b/.gradle/7.5.1/executionHistory/executionHistory.lock deleted file mode 100644 index 9323e89..0000000 Binary files a/.gradle/7.5.1/executionHistory/executionHistory.lock and /dev/null differ diff --git a/.gradle/7.5.1/fileChanges/last-build.bin b/.gradle/7.5.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/.gradle/7.5.1/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/7.5.1/fileHashes/fileHashes.bin b/.gradle/7.5.1/fileHashes/fileHashes.bin deleted file mode 100644 index e958afe..0000000 Binary files a/.gradle/7.5.1/fileHashes/fileHashes.bin and /dev/null differ diff --git a/.gradle/7.5.1/fileHashes/fileHashes.lock b/.gradle/7.5.1/fileHashes/fileHashes.lock deleted file mode 100644 index 8ca5f3d..0000000 Binary files a/.gradle/7.5.1/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/7.5.1/fileHashes/resourceHashesCache.bin b/.gradle/7.5.1/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 73577d1..0000000 Binary files a/.gradle/7.5.1/fileHashes/resourceHashesCache.bin and /dev/null differ diff --git a/.gradle/7.5.1/gc.properties b/.gradle/7.5.1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 555ade8..0000000 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 79c6d9f..0000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Thu Apr 11 12:53:51 GMT+04:00 2024 -gradle.version=7.5.1 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 580bcee..0000000 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and /dev/null differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe deleted file mode 100644 index 6368ebf..0000000 Binary files a/.gradle/file-system.probe and /dev/null differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/build.gradle b/build.gradle index 3cae2d7..8e37491 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,13 @@ repositories { dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + implementation(group: 'com.h2database', name: 'h2', version: '2.1.214') + + testImplementation(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.2.4') + implementation(group: 'org.springframework.boot', name: 'spring-boot-starter', version: '3.2.4') + implementation(group: 'org.springframework.boot', name: 'spring-boot-starter-jetty', version: '3.2.4') + implementation(group: 'org.springframework.boot', name: 'spring-boot-starter-web-services', version: '3.2.4') + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.2.4' } test { diff --git a/build/classes/java/main/org/example/Main.class b/build/classes/java/main/org/example/Main.class deleted file mode 100644 index fb64510..0000000 Binary files a/build/classes/java/main/org/example/Main.class and /dev/null differ diff --git a/build/classes/java/main/org/example/database/Database.class b/build/classes/java/main/org/example/database/Database.class deleted file mode 100644 index 46c1bbf..0000000 Binary files a/build/classes/java/main/org/example/database/Database.class and /dev/null differ diff --git a/build/classes/java/main/org/example/exception/UnknownShortLinkException.class b/build/classes/java/main/org/example/exception/UnknownShortLinkException.class deleted file mode 100644 index 5a091dd..0000000 Binary files a/build/classes/java/main/org/example/exception/UnknownShortLinkException.class and /dev/null differ diff --git a/build/classes/java/main/org/example/service/MainService.class b/build/classes/java/main/org/example/service/MainService.class deleted file mode 100644 index 8fd3685..0000000 Binary files a/build/classes/java/main/org/example/service/MainService.class and /dev/null differ diff --git a/build/classes/java/main/org/example/service/MainServiceImpl.class b/build/classes/java/main/org/example/service/MainServiceImpl.class deleted file mode 100644 index 89d029b..0000000 Binary files a/build/classes/java/main/org/example/service/MainServiceImpl.class and /dev/null differ diff --git a/build/classes/java/test/org/example/service/MainServiceTest.class b/build/classes/java/test/org/example/service/MainServiceTest.class deleted file mode 100644 index 0d03df1..0000000 Binary files a/build/classes/java/test/org/example/service/MainServiceTest.class and /dev/null differ diff --git a/build/reports/tests/test/classes/org.example.service.MainServiceTest.html b/build/reports/tests/test/classes/org.example.service.MainServiceTest.html deleted file mode 100644 index b338007..0000000 --- a/build/reports/tests/test/classes/org.example.service.MainServiceTest.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - -Test results - MainServiceTest - - - - - -
-

MainServiceTest

- -
- - - - - -
-
- - - - - - - -
-
-
4
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
3.620s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestDurationResult
testException()0.039spassed
testGetLongLink()0.005spassed
testGetShortLink()0.019spassed
testUniqueness()3.557spassed
-
-
- -
- - diff --git a/build/reports/tests/test/css/base-style.css b/build/reports/tests/test/css/base-style.css deleted file mode 100644 index 4afa73e..0000000 --- a/build/reports/tests/test/css/base-style.css +++ /dev/null @@ -1,179 +0,0 @@ - -body { - margin: 0; - padding: 0; - font-family: sans-serif; - font-size: 12pt; -} - -body, a, a:visited { - color: #303030; -} - -#content { - padding-left: 50px; - padding-right: 50px; - padding-top: 30px; - padding-bottom: 30px; -} - -#content h1 { - font-size: 160%; - margin-bottom: 10px; -} - -#footer { - margin-top: 100px; - font-size: 80%; - white-space: nowrap; -} - -#footer, #footer a { - color: #a0a0a0; -} - -#line-wrapping-toggle { - vertical-align: middle; -} - -#label-for-line-wrapping-toggle { - vertical-align: middle; -} - -ul { - margin-left: 0; -} - -h1, h2, h3 { - white-space: nowrap; -} - -h2 { - font-size: 120%; -} - -ul.tabLinks { - padding-left: 0; - padding-top: 10px; - padding-bottom: 10px; - overflow: auto; - min-width: 800px; - width: auto !important; - width: 800px; -} - -ul.tabLinks li { - float: left; - height: 100%; - list-style: none; - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; - margin-bottom: 0; - -moz-border-radius: 7px; - border-radius: 7px; - margin-right: 25px; - border: solid 1px #d4d4d4; - background-color: #f0f0f0; -} - -ul.tabLinks li:hover { - background-color: #fafafa; -} - -ul.tabLinks li.selected { - background-color: #c5f0f5; - border-color: #c5f0f5; -} - -ul.tabLinks a { - font-size: 120%; - display: block; - outline: none; - text-decoration: none; - margin: 0; - padding: 0; -} - -ul.tabLinks li h2 { - margin: 0; - padding: 0; -} - -div.tab { -} - -div.selected { - display: block; -} - -div.deselected { - display: none; -} - -div.tab table { - min-width: 350px; - width: auto !important; - width: 350px; - border-collapse: collapse; -} - -div.tab th, div.tab table { - border-bottom: solid #d0d0d0 1px; -} - -div.tab th { - text-align: left; - white-space: nowrap; - padding-left: 6em; -} - -div.tab th:first-child { - padding-left: 0; -} - -div.tab td { - white-space: nowrap; - padding-left: 6em; - padding-top: 5px; - padding-bottom: 5px; -} - -div.tab td:first-child { - padding-left: 0; -} - -div.tab td.numeric, div.tab th.numeric { - text-align: right; -} - -span.code { - display: inline-block; - margin-top: 0em; - margin-bottom: 1em; -} - -span.code pre { - font-size: 11pt; - padding-top: 10px; - padding-bottom: 10px; - padding-left: 10px; - padding-right: 10px; - margin: 0; - background-color: #f7f7f7; - border: solid 1px #d0d0d0; - min-width: 700px; - width: auto !important; - width: 700px; -} - -span.wrapped pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: break-all; -} - -label.hidden { - display: none; -} \ No newline at end of file diff --git a/build/reports/tests/test/css/style.css b/build/reports/tests/test/css/style.css deleted file mode 100644 index 3dc4913..0000000 --- a/build/reports/tests/test/css/style.css +++ /dev/null @@ -1,84 +0,0 @@ - -#summary { - margin-top: 30px; - margin-bottom: 40px; -} - -#summary table { - border-collapse: collapse; -} - -#summary td { - vertical-align: top; -} - -.breadcrumbs, .breadcrumbs a { - color: #606060; -} - -.infoBox { - width: 110px; - padding-top: 15px; - padding-bottom: 15px; - text-align: center; -} - -.infoBox p { - margin: 0; -} - -.counter, .percent { - font-size: 120%; - font-weight: bold; - margin-bottom: 8px; -} - -#duration { - width: 125px; -} - -#successRate, .summaryGroup { - border: solid 2px #d0d0d0; - -moz-border-radius: 10px; - border-radius: 10px; -} - -#successRate { - width: 140px; - margin-left: 35px; -} - -#successRate .percent { - font-size: 180%; -} - -.success, .success a { - color: #008000; -} - -div.success, #successRate.success { - background-color: #bbd9bb; - border-color: #008000; -} - -.failures, .failures a { - color: #b60808; -} - -.skipped, .skipped a { - color: #c09853; -} - -div.failures, #successRate.failures { - background-color: #ecdada; - border-color: #b60808; -} - -ul.linkList { - padding-left: 0; -} - -ul.linkList li { - list-style: none; - margin-bottom: 5px; -} diff --git a/build/reports/tests/test/index.html b/build/reports/tests/test/index.html deleted file mode 100644 index a2a465e..0000000 --- a/build/reports/tests/test/index.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - -Test results - Test Summary - - - - - -
-

Test Summary

-
- - - - - -
-
- - - - - - - -
-
-
4
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
3.620s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Packages

- - - - - - - - - - - - - - - - - - - - - -
PackageTestsFailuresIgnoredDurationSuccess rate
-org.example.service -4003.620s100%
-
-
-

Classes

- - - - - - - - - - - - - - - - - - - - - -
ClassTestsFailuresIgnoredDurationSuccess rate
-org.example.service.MainServiceTest -4003.620s100%
-
-
- -
- - diff --git a/build/reports/tests/test/js/report.js b/build/reports/tests/test/js/report.js deleted file mode 100644 index 83bab4a..0000000 --- a/build/reports/tests/test/js/report.js +++ /dev/null @@ -1,194 +0,0 @@ -(function (window, document) { - "use strict"; - - var tabs = {}; - - function changeElementClass(element, classValue) { - if (element.getAttribute("className")) { - element.setAttribute("className", classValue); - } else { - element.setAttribute("class", classValue); - } - } - - function getClassAttribute(element) { - if (element.getAttribute("className")) { - return element.getAttribute("className"); - } else { - return element.getAttribute("class"); - } - } - - function addClass(element, classValue) { - changeElementClass(element, getClassAttribute(element) + " " + classValue); - } - - function removeClass(element, classValue) { - changeElementClass(element, getClassAttribute(element).replace(classValue, "")); - } - - function initTabs() { - var container = document.getElementById("tabs"); - - tabs.tabs = findTabs(container); - tabs.titles = findTitles(tabs.tabs); - tabs.headers = findHeaders(container); - tabs.select = select; - tabs.deselectAll = deselectAll; - tabs.select(0); - - return true; - } - - function getCheckBox() { - return document.getElementById("line-wrapping-toggle"); - } - - function getLabelForCheckBox() { - return document.getElementById("label-for-line-wrapping-toggle"); - } - - function findCodeBlocks() { - var spans = document.getElementById("tabs").getElementsByTagName("span"); - var codeBlocks = []; - for (var i = 0; i < spans.length; ++i) { - if (spans[i].className.indexOf("code") >= 0) { - codeBlocks.push(spans[i]); - } - } - return codeBlocks; - } - - function forAllCodeBlocks(operation) { - var codeBlocks = findCodeBlocks(); - - for (var i = 0; i < codeBlocks.length; ++i) { - operation(codeBlocks[i], "wrapped"); - } - } - - function toggleLineWrapping() { - var checkBox = getCheckBox(); - - if (checkBox.checked) { - forAllCodeBlocks(addClass); - } else { - forAllCodeBlocks(removeClass); - } - } - - function initControls() { - if (findCodeBlocks().length > 0) { - var checkBox = getCheckBox(); - var label = getLabelForCheckBox(); - - checkBox.onclick = toggleLineWrapping; - checkBox.checked = false; - - removeClass(label, "hidden"); - } - } - - function switchTab() { - var id = this.id.substr(1); - - for (var i = 0; i < tabs.tabs.length; i++) { - if (tabs.tabs[i].id === id) { - tabs.select(i); - break; - } - } - - return false; - } - - function select(i) { - this.deselectAll(); - - changeElementClass(this.tabs[i], "tab selected"); - changeElementClass(this.headers[i], "selected"); - - while (this.headers[i].firstChild) { - this.headers[i].removeChild(this.headers[i].firstChild); - } - - var h2 = document.createElement("H2"); - - h2.appendChild(document.createTextNode(this.titles[i])); - this.headers[i].appendChild(h2); - } - - function deselectAll() { - for (var i = 0; i < this.tabs.length; i++) { - changeElementClass(this.tabs[i], "tab deselected"); - changeElementClass(this.headers[i], "deselected"); - - while (this.headers[i].firstChild) { - this.headers[i].removeChild(this.headers[i].firstChild); - } - - var a = document.createElement("A"); - - a.setAttribute("id", "ltab" + i); - a.setAttribute("href", "#tab" + i); - a.onclick = switchTab; - a.appendChild(document.createTextNode(this.titles[i])); - - this.headers[i].appendChild(a); - } - } - - function findTabs(container) { - return findChildElements(container, "DIV", "tab"); - } - - function findHeaders(container) { - var owner = findChildElements(container, "UL", "tabLinks"); - return findChildElements(owner[0], "LI", null); - } - - function findTitles(tabs) { - var titles = []; - - for (var i = 0; i < tabs.length; i++) { - var tab = tabs[i]; - var header = findChildElements(tab, "H2", null)[0]; - - header.parentNode.removeChild(header); - - if (header.innerText) { - titles.push(header.innerText); - } else { - titles.push(header.textContent); - } - } - - return titles; - } - - function findChildElements(container, name, targetClass) { - var elements = []; - var children = container.childNodes; - - for (var i = 0; i < children.length; i++) { - var child = children.item(i); - - if (child.nodeType === 1 && child.nodeName === name) { - if (targetClass && child.className.indexOf(targetClass) < 0) { - continue; - } - - elements.push(child); - } - } - - return elements; - } - - // Entry point. - - window.onload = function() { - initTabs(); - initControls(); - }; -} (window, window.document)); \ No newline at end of file diff --git a/build/reports/tests/test/packages/org.example.service.html b/build/reports/tests/test/packages/org.example.service.html deleted file mode 100644 index f22e014..0000000 --- a/build/reports/tests/test/packages/org.example.service.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - -Test results - Package org.example.service - - - - - -
-

Package org.example.service

- -
- - - - - -
-
- - - - - - - -
-
-
4
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
3.620s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Classes

- - - - - - - - - - - - - - - - - - - -
ClassTestsFailuresIgnoredDurationSuccess rate
-MainServiceTest -4003.620s100%
-
-
- -
- - diff --git a/build/test-results/test/TEST-org.example.service.MainServiceTest.xml b/build/test-results/test/TEST-org.example.service.MainServiceTest.xml deleted file mode 100644 index 79b5ccf..0000000 --- a/build/test-results/test/TEST-org.example.service.MainServiceTest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/build/test-results/test/binary/output.bin b/build/test-results/test/binary/output.bin deleted file mode 100644 index e69de29..0000000 diff --git a/build/test-results/test/binary/output.bin.idx b/build/test-results/test/binary/output.bin.idx deleted file mode 100644 index f76dd23..0000000 Binary files a/build/test-results/test/binary/output.bin.idx and /dev/null differ diff --git a/build/test-results/test/binary/results.bin b/build/test-results/test/binary/results.bin deleted file mode 100644 index 7f5e71f..0000000 Binary files a/build/test-results/test/binary/results.bin and /dev/null differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin deleted file mode 100644 index 0826bd6..0000000 Binary files a/build/tmp/compileJava/previous-compilation-data.bin and /dev/null differ diff --git a/build/tmp/compileTestJava/previous-compilation-data.bin b/build/tmp/compileTestJava/previous-compilation-data.bin deleted file mode 100644 index 19a83b1..0000000 Binary files a/build/tmp/compileTestJava/previous-compilation-data.bin and /dev/null differ diff --git a/h2-2.1.214.jar b/h2-2.1.214.jar new file mode 100644 index 0000000..1d527cb Binary files /dev/null and b/h2-2.1.214.jar differ diff --git a/src/main/java/org/example/LinkShortenerApplication.java b/src/main/java/org/example/LinkShortenerApplication.java new file mode 100644 index 0000000..6a3628a --- /dev/null +++ b/src/main/java/org/example/LinkShortenerApplication.java @@ -0,0 +1,11 @@ +package org.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LinkShortenerApplication { + public static void main(String... args) { + SpringApplication.run(LinkShortenerApplication.class); + } +} diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java deleted file mode 100644 index 689f5c0..0000000 --- a/src/main/java/org/example/Main.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.example; - - - -import org.example.exception.UnknownShortLinkException; -import org.example.service.MainService; -import org.example.service.MainServiceImpl; - -import java.util.Scanner; - -import static org.example.service.MainServiceImpl.shortLinksDomain; - -public class Main { - public static Scanner sc = new Scanner(System.in); - public static MainService service = new MainServiceImpl(); - public static void main(String[] args) { - while (true) { - printMenu(); - - String chosenService = sc.nextLine(); - - if (chosenService.equals("1")) { - System.out.println("Введите длинную ссылку"); - String longLink = null; - while (longLink == null) { - longLink = readAndValidateLongLink(); - } - String shortLink = service.getShortLink(longLink); - System.out.println("Короткая ссылка: " + shortLink); - - } else if (chosenService.equals("2")) { - System.out.println("Введите короткую ссылку:"); - String shortLink = null; - while (shortLink == null) { - shortLink = readAndValidateShortLink(); - } - try { - String longLink = service.getLongLink(shortLink); - System.out.println("Длинная ссылка: " + longLink); - } catch(UnknownShortLinkException ex) { - System.out.println("Данной короткой ссылке не соответствует никакая длинная"); - } - } else if (chosenService.equals("3")) { - return; - } else { - System.out.println("Выберите валидный вариант"); - } - } - } - - private static String readAndValidateLongLink() { - String link = sc.nextLine(); - - if (link == null || link.isBlank()) { - System.out.println("Некорректная ссылка, введите ещё раз"); - return null; - } - - return link; - } - - private static String readAndValidateShortLink() { - String link = sc.nextLine(); - int len = shortLinksDomain.length(); - - if (link == null || link.length() < len || !shortLinksDomain.equals(link.substring(0, len))) { - System.out.println("Короткая ссылка должна начинаться с \"" + shortLinksDomain + "\". Введите ссылку ещё раз"); - return null; - } - - return link; - } - - - private static void printMenu() { - System.out.println(""" - Выберите действие: - 1. Укоротить ссылку - 2. Восстановить длинную ссылку по короткой - 3. Выйти - """); - } - - -} \ No newline at end of file diff --git a/src/main/java/org/example/advice/MyControllerAdvice.java b/src/main/java/org/example/advice/MyControllerAdvice.java new file mode 100644 index 0000000..8923589 --- /dev/null +++ b/src/main/java/org/example/advice/MyControllerAdvice.java @@ -0,0 +1,34 @@ +package org.example.advice; + +import org.example.exception.BadRepositoryFunctionCallException; +import org.example.exception.IncorrectLongLinkException; +import org.example.exception.UnknownShortLinkException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.sql.SQLException; + +@ControllerAdvice +public class MyControllerAdvice { + @ExceptionHandler(IncorrectLongLinkException.class) + public ResponseEntity handleIncorrectLongLink(IncorrectLongLinkException e) { + return new ResponseEntity("Incorrect long link", HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(UnknownShortLinkException.class) + public ResponseEntity handleUnknownShortLink(UnknownShortLinkException e) { + return new ResponseEntity("There is no corresponding long link", HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(BadRepositoryFunctionCallException.class) + public ResponseEntity handleBadCall(BadRepositoryFunctionCallException e) { + return new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(SQLException.class) + public ResponseEntity handleSQLException(SQLException e) { + return new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/org/example/controller/MainController.java b/src/main/java/org/example/controller/MainController.java new file mode 100644 index 0000000..a054683 --- /dev/null +++ b/src/main/java/org/example/controller/MainController.java @@ -0,0 +1,34 @@ +package org.example.controller; + +import org.example.exception.BadRepositoryFunctionCallException; +import org.example.exception.IncorrectLongLinkException; +import org.example.exception.UnknownShortLinkException; +import org.example.service.MainService; +import org.example.service.MainServiceImpl; +import org.springframework.web.bind.annotation.*; + +import static org.example.service.MainServiceImpl.shortLinksDomain; + +@RestController +@RequestMapping("/url") +public class MainController { + private final MainService mainService; + public MainController(MainService mainService) { + this.mainService = mainService; + } + @PostMapping(value = "/create") + public String createShortLink(@RequestBody String longLink) + throws IncorrectLongLinkException, BadRepositoryFunctionCallException { + if(longLink == null || longLink.isBlank()) + throw new IncorrectLongLinkException(); + + return mainService.getShortLink(longLink); + } + + + @GetMapping("/{shortCode}") + public String getLongLink(@PathVariable("shortCode") String shortCode) + throws UnknownShortLinkException, BadRepositoryFunctionCallException { + return mainService.getLongLink(shortLinksDomain + shortCode); + } +} diff --git a/src/main/java/org/example/dao/entity/LinksEntity.java b/src/main/java/org/example/dao/entity/LinksEntity.java new file mode 100644 index 0000000..0ee198e --- /dev/null +++ b/src/main/java/org/example/dao/entity/LinksEntity.java @@ -0,0 +1,36 @@ +package org.example.dao.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity(name = "links") +public class LinksEntity { + @Id + @Column(name = "SHORT_CODE") + private String shortCode; + @Column(name = "LONG_LINK") + private String longLink; + + public LinksEntity() {} + public LinksEntity(String shortCode, String longLink) { + this.shortCode = shortCode; + this.longLink = longLink; + } + + public String getShortCode() { + return shortCode; + } + + public void setShortCode(String shortCode) { + this.shortCode = shortCode; + } + + public String getLongLink() { + return longLink; + } + + public void setLongLink(String longLink) { + this.longLink = longLink; + } +} diff --git a/src/main/java/org/example/dao/repository/LinksRepository.java b/src/main/java/org/example/dao/repository/LinksRepository.java new file mode 100644 index 0000000..0cebd84 --- /dev/null +++ b/src/main/java/org/example/dao/repository/LinksRepository.java @@ -0,0 +1,19 @@ +package org.example.dao.repository; + +import org.example.dao.entity.LinksEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface LinksRepository extends JpaRepository { + @Query(nativeQuery = true, + value = "SELECT LONG_LINK FROM links WHERE SHORT_CODE = :shortCode") + List findLongLinksByShortCode(@Param("shortCode") String shortCode); + @Query(nativeQuery = true, + value = "SELECT SHORT_CODE FROM links WHERE LONG_LINK = :longLink") + List findShortCodesByLongLink(@Param("longLink") String longLink); +} diff --git a/src/main/java/org/example/dao/repository/OldLinksRepository.java b/src/main/java/org/example/dao/repository/OldLinksRepository.java new file mode 100644 index 0000000..7aab4cd --- /dev/null +++ b/src/main/java/org/example/dao/repository/OldLinksRepository.java @@ -0,0 +1,49 @@ +package org.example.dao.repository; + +import org.example.dao.entity.LinksEntity; +import org.example.exception.BadRepositoryFunctionCallException; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +@Repository +public class OldLinksRepository { + private final LinksRepository linksRepository; + + public OldLinksRepository(LinksRepository linksRepository) { this.linksRepository = linksRepository; } + + public String getShortCode(String longLink) throws BadRepositoryFunctionCallException { + List shortCodes = linksRepository.findShortCodesByLongLink(longLink); + + if (!shortCodes.isEmpty()) + return shortCodes.get(0); + + throw new BadRepositoryFunctionCallException("Error: long link is not found"); + } + + public String getLongLink(String shortCode) throws BadRepositoryFunctionCallException { + List longLinks = linksRepository.findLongLinksByShortCode(shortCode); + + if(!longLinks.isEmpty()) + return longLinks.get(0); + + throw new BadRepositoryFunctionCallException("Error: short code is not found"); + } + + public boolean containsShortCode(String shortCode) { + List longLinks = linksRepository.findLongLinksByShortCode(shortCode); + return !longLinks.isEmpty(); + } + + public boolean containsLongLink(String longLink) { + List shortCodes = linksRepository.findShortCodesByLongLink(longLink); + return !shortCodes.isEmpty(); + } + + public void addPairOfLinks(String longLink, String shortCode) { + linksRepository.save(new LinksEntity(shortCode, longLink)); + } +} diff --git a/src/main/java/org/example/database/Database.java b/src/main/java/org/example/database/Database.java deleted file mode 100644 index 07f4921..0000000 --- a/src/main/java/org/example/database/Database.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.example.database; - -import java.util.HashMap; -import java.util.Map; - -public class Database { - private static final Database instance = new Database(); - private Database() {} - public static Database getInstance() { - return instance; - } - - private final Map shortCodes = new HashMap<>(); // key = long link, value = short code (without domain) - private final Map longLinks = new HashMap<>(); // key = short code, value = long link - - public String getShortCode(String longLink) { - return shortCodes.get(longLink); - } - - public String getLongLink(String shortCode) { - return longLinks.get(shortCode); - } - - public boolean containsLongLink(String longLink) { - return shortCodes.containsKey(longLink); - } - - public boolean containsShortCode(String shortCode) { - return longLinks.containsKey(shortCode); - } - - public void addPairOfLinks(String longLink, String shortCode) { - longLinks.put(shortCode, longLink); - shortCodes.put(longLink, shortCode); - } -} diff --git a/src/main/java/org/example/exception/BadRepositoryFunctionCallException.java b/src/main/java/org/example/exception/BadRepositoryFunctionCallException.java new file mode 100644 index 0000000..fc3f4f4 --- /dev/null +++ b/src/main/java/org/example/exception/BadRepositoryFunctionCallException.java @@ -0,0 +1,7 @@ +package org.example.exception; + +public class BadRepositoryFunctionCallException extends Exception { + public BadRepositoryFunctionCallException() {} + + public BadRepositoryFunctionCallException(String message) {super(message);} +} diff --git a/src/main/java/org/example/exception/IncorrectLongLinkException.java b/src/main/java/org/example/exception/IncorrectLongLinkException.java new file mode 100644 index 0000000..0654815 --- /dev/null +++ b/src/main/java/org/example/exception/IncorrectLongLinkException.java @@ -0,0 +1,12 @@ +package org.example.exception; + +public class IncorrectLongLinkException extends Exception { + + public IncorrectLongLinkException() { + } + + public IncorrectLongLinkException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/example/exception/UnknownShortLinkException.java b/src/main/java/org/example/exception/UnknownShortLinkException.java index 094959f..bc85ac2 100644 --- a/src/main/java/org/example/exception/UnknownShortLinkException.java +++ b/src/main/java/org/example/exception/UnknownShortLinkException.java @@ -9,4 +9,4 @@ public UnknownShortLinkException(String message) { super(message); } -} +} \ No newline at end of file diff --git a/src/main/java/org/example/jdbc/JdbcUtils.java b/src/main/java/org/example/jdbc/JdbcUtils.java new file mode 100644 index 0000000..601af32 --- /dev/null +++ b/src/main/java/org/example/jdbc/JdbcUtils.java @@ -0,0 +1,22 @@ +package org.example.jdbc; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +@Configuration +public class JdbcUtils { + private static final String DB_URL = "jdbc:h2:~/test"; + @Bean(value = "connection") + public Connection getConnection() { + try { + return DriverManager.getConnection(DB_URL, "admin", "admin"); + } catch (Exception ex) { + System.out.println("Error occurred while connection to database: " + ex.getMessage()); + throw new RuntimeException(ex); + } + } +} diff --git a/src/main/java/org/example/service/MainService.java b/src/main/java/org/example/service/MainService.java index 2a006ab..6de533e 100644 --- a/src/main/java/org/example/service/MainService.java +++ b/src/main/java/org/example/service/MainService.java @@ -1,8 +1,9 @@ package org.example.service; +import org.example.exception.BadRepositoryFunctionCallException; import org.example.exception.UnknownShortLinkException; public interface MainService { - String getShortLink(String longLink); - String getLongLink(String shortLink) throws UnknownShortLinkException; + String getShortLink(String longLink) throws BadRepositoryFunctionCallException; + String getLongLink(String shortLink) throws BadRepositoryFunctionCallException, UnknownShortLinkException; } diff --git a/src/main/java/org/example/service/MainServiceImpl.java b/src/main/java/org/example/service/MainServiceImpl.java index c348207..80aaa02 100644 --- a/src/main/java/org/example/service/MainServiceImpl.java +++ b/src/main/java/org/example/service/MainServiceImpl.java @@ -1,13 +1,21 @@ package org.example.service; -import org.example.database.Database; +import org.example.exception.BadRepositoryFunctionCallException; import org.example.exception.UnknownShortLinkException; +import org.example.dao.repository.OldLinksRepository; +import org.springframework.stereotype.Service; import java.util.Random; +@Service public class MainServiceImpl implements MainService { public static final int shortLinkLength = 5; public static final String shortLinksDomain = "localhost:8080/"; + private final OldLinksRepository linksRepository; + + public MainServiceImpl(OldLinksRepository linksRepository) { + this.linksRepository = linksRepository; + } char getSymbol(int code) { if(code < 26) @@ -17,30 +25,30 @@ else if(code < 52) else return (char)(code - 52 + '0'); } @Override - public String getShortLink(String longLink) { - if(Database.getInstance().containsLongLink(longLink)) - return shortLinksDomain + Database.getInstance().getShortCode(longLink); + public String getShortLink(String longLink) throws BadRepositoryFunctionCallException { + if(linksRepository.containsLongLink(longLink)) + return shortLinksDomain + linksRepository.getShortCode(longLink); String shortLink = null; Random random = new Random(); - while(shortLink == null || Database.getInstance().containsShortCode(shortLink)) { + while(shortLink == null || linksRepository.containsShortCode(shortLink)) { shortLink = ""; for(int i = 0; i < shortLinkLength; i++) shortLink += getSymbol(random.nextInt(62)); } - Database.getInstance().addPairOfLinks(longLink, shortLink); + linksRepository.addPairOfLinks(longLink, shortLink); return shortLinksDomain + shortLink; } @Override - public String getLongLink(String shortLink) throws UnknownShortLinkException { + public String getLongLink(String shortLink) throws UnknownShortLinkException, BadRepositoryFunctionCallException { shortLink = shortLink.substring(shortLinksDomain.length(), shortLink.length()); - if(!Database.getInstance().containsShortCode(shortLink)) + if(!linksRepository.containsShortCode(shortLink)) throw new UnknownShortLinkException("Короткая ссылка не найдена"); - else return Database.getInstance().getLongLink(shortLink); + else return linksRepository.getLongLink(shortLink); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..e9b42f4 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + datasource: + url: jdbc:h2:~/test + username: admin + password: admin + +logging: + level: + sql: INFO + diff --git a/src/test/java/org/example/controller/MainControllerTest.java b/src/test/java/org/example/controller/MainControllerTest.java new file mode 100644 index 0000000..999fe4e --- /dev/null +++ b/src/test/java/org/example/controller/MainControllerTest.java @@ -0,0 +1,156 @@ +package org.example.controller; + +import org.example.exception.UnknownShortLinkException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.HashMap; +import java.util.Random; + +import static org.example.service.MainServiceImpl.shortLinkLength; +import static org.example.service.MainServiceImpl.shortLinksDomain; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class MainControllerTest { + @Autowired + private MockMvc mockMvc; + private Random random = new Random(); + + @Test + void testGetShortLink() throws Exception { + String longLink = ""; + + for(int i = 0; i < 10; i++) + longLink += (char)random.nextInt('a', 'z' + 1); + + MvcResult mvcResult = mockMvc.perform(post("/url/create").content(longLink)) + .andExpect(status().isOk()).andReturn(); + + String shortLink = mvcResult.getResponse().getContentAsString(); + + mvcResult = mockMvc.perform(post("/url/create").content(longLink)) + .andExpect(status().isOk()).andReturn(); + + String shortLink2 = mvcResult.getResponse().getContentAsString(); + + assert(shortLink.length() == shortLinksDomain.length() + shortLinkLength); + assertEquals(shortLink.substring(0, shortLinksDomain.length()), shortLinksDomain); + assertEquals(shortLink, shortLink2); + } + + /*@Test + void testGetShortLink() throws Exception { + //given: + String longLink = "www.example.com"; + //when: + String shortLink = mainService.getShortLink(longLink); + String shortLink2 = mainService.getShortLink(longLink); + //then: + assert(shortLink.length() == shortLinksDomain.length() + shortLinkLength); + assertEquals(shortLink.substring(0, shortLinksDomain.length()), shortLinksDomain); + assertEquals(shortLink, shortLink2); + }*/ + + @Test + void testGetLongLink() throws Exception { + String longLink = ""; + + for(int i = 0; i < 10; i++) + longLink += (char)random.nextInt('a', 'z' + 1); + + MvcResult mvcResult = mockMvc.perform(post("/url/create").content(longLink)) + .andExpect(status().isOk()).andReturn(); + + String shortLink = mvcResult.getResponse().getContentAsString(); + + mvcResult = mockMvc.perform(get("/url" + shortLink.substring(shortLinksDomain.length() - 1, shortLink.length()))) + .andExpect(status().isOk()).andReturn(); + + String longLink2 = mvcResult.getResponse().getContentAsString(); + assertEquals(longLink, longLink2); + } + + /* @Test + void testGetLongLink() throws Exception { + //given: + String longLink = "www.example.com"; + //when: + String shortLink = mainService.getShortLink(longLink); + String longLink2 = mainService.getLongLink(shortLink); + //then: + assertEquals(longLink, longLink2); + }*/ + + @Test + void testUniqueness() throws Exception { + int N = 100000; + HashMap map = new HashMap<>(); + + for(int i = 0; i < N; i++) + { + String longLink = ""; + + for(int j = 0; j < 3; j++) + longLink += (char)random.nextInt('a', 'z' + 1); + + longLink += Integer.toString(i); + + MvcResult mvcResult = mockMvc.perform(post("/url/create").content(longLink)) + .andExpect(status().isOk()).andReturn(); + + String shortLink = mvcResult.getResponse().getContentAsString(); + map.put(shortLink, i); + } + + assertEquals(map.size(), N); + } + + /* @Test + void testUniqueness() throws Exception { + //given: + int N = 1000000; + HashMap map = new HashMap<>(); + //when: + for(int i = 0; i < N; i++) + { + String shortLink = mainService.getShortLink(Integer.toString(i)); + map.put(shortLink, i); + } + //then: + assertEquals(map.size(), N); + }*/ + + @Test + void testLinkNotFound() throws Exception { + + MvcResult mvcResult = mockMvc.perform(get("/url/a")) + .andExpect(status().isBadRequest()).andReturn(); + + String longLink = mvcResult.getResponse().getContentAsString(); + assertEquals(longLink, "There is no corresponding long link"); + } + + /*@Test + void testException() throws Exception { + //given: + String shortLink = shortLinksDomain; + //when: + boolean exceptionThrown = false; + try { + String longLink = mainService.getLongLink(shortLink); + } catch (UnknownShortLinkException e) { + exceptionThrown = true; + } + + assert(exceptionThrown); + }*/ +} diff --git a/src/test/java/org/example/service/MainServiceTest.java b/src/test/java/org/example/service/MainServiceTest.java deleted file mode 100644 index f442a1a..0000000 --- a/src/test/java/org/example/service/MainServiceTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.example.service; - -import org.example.exception.UnknownShortLinkException; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; - -import static org.example.service.MainServiceImpl.shortLinkLength; -import static org.example.service.MainServiceImpl.shortLinksDomain; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class MainServiceTest { - private final MainService service = new MainServiceImpl(); - - @Test - void testGetShortLink() throws Exception { - //given: - String longLink = "www.example.com"; - //when: - String shortLink = service.getShortLink(longLink); - String shortLink2 = service.getShortLink(longLink); - //then: - assert(shortLink.length() == shortLinksDomain.length() + shortLinkLength); - assertEquals(shortLink.substring(0, shortLinksDomain.length()), shortLinksDomain); - assertEquals(shortLink, shortLink2); - } - - @Test - void testGetLongLink() throws Exception { - //given: - String longLink = "www.example.com"; - //when: - String shortLink = service.getShortLink(longLink); - String longLink2 = service.getLongLink(shortLink); - //then: - assertEquals(longLink, longLink2); - } - - @Test - void testUniqueness() throws Exception { - //given: - int N = 1000000; - HashMap map = new HashMap<>(); - //when: - for(int i = 0; i < N; i++) - { - String shortLink = service.getShortLink(Integer.toString(i)); - map.put(shortLink, i); - } - //then: - assertEquals(map.size(), N); - } - - @Test - void testException() throws Exception { - //given: - String shortLink = shortLinksDomain; - //when: - boolean exceptionThrown = false; - try { - String longLink = service.getLongLink(shortLink); - } catch (UnknownShortLinkException e) { - exceptionThrown = true; - } - - assert(exceptionThrown); - } -}