Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit 5851d05

Browse files
Merge pull request #8 from RomanVassilchenko/development
2 parents 4ed7d88 + 24570e4 commit 5851d05

File tree

7 files changed

+278
-25
lines changed

7 files changed

+278
-25
lines changed

internal/http-server/handlers/url/save/mocks/URLSaver.go

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/http-server/handlers/url/save/save.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const aliasLength = 6
2929
//go:generate go run github.com/vektra/mockery/v2@v2.28.2 --name=URLSaver
3030
type URLSaver interface {
3131
SaveURL(urlToSave string, alias string) (int64, error)
32+
AliasExists(alias string) (bool, error)
33+
URLExists(urlToCheck string) (bool, error)
34+
GetAliasByURL(urlToFind string) (string, error)
3235
}
3336

3437
func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {
@@ -55,7 +58,8 @@ func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {
5558
log.Info("request body decoded", slog.Any("request", req))
5659

5760
if err := validator.New().Struct(req); err != nil {
58-
validateErr := err.(validator.ValidationErrors)
61+
var validateErr validator.ValidationErrors
62+
errors.As(err, &validateErr)
5963

6064
log.Error("invalid request", sl.Err(err))
6165

@@ -66,8 +70,47 @@ func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {
6670

6771
alias := req.Alias
6872
if alias == "" {
69-
//TODO Check that new alias doesn't exist
70-
alias = random.NewRandomString(aliasLength)
73+
74+
exists, err := urlSaver.URLExists(req.URL)
75+
if err != nil {
76+
log.Error("failed to check that URL exists in DB", sl.Err(err))
77+
render.JSON(w, r, resp.Error("failed to check that URL exists in DB"))
78+
return
79+
}
80+
81+
if exists {
82+
alias, err = urlSaver.GetAliasByURL(req.URL)
83+
if err != nil {
84+
log.Error("failed to get alias connected to URL", sl.Err(err))
85+
render.JSON(w, r, resp.Error("failed to get alias connected to URL"))
86+
return
87+
}
88+
89+
responseOK(w, r, alias)
90+
return
91+
}
92+
93+
const maxAttempts = 64 // Maximum number of generation attempts
94+
exists = true
95+
96+
for attempt := 1; attempt <= maxAttempts; attempt++ {
97+
alias = random.NewRandomString(aliasLength)
98+
exists, err = urlSaver.AliasExists(alias)
99+
if err != nil {
100+
log.Error("failed to generate alias", sl.Err(err))
101+
render.JSON(w, r, resp.Error("failed to generate url"))
102+
return
103+
}
104+
if !exists {
105+
break
106+
}
107+
}
108+
109+
if exists {
110+
log.Error("The number of attempts to create an alias has been exceeded", sl.Err(err))
111+
render.JSON(w, r, resp.Error("The number of attempts to create an alias has been exceeded. Try again after a while"))
112+
return
113+
}
71114
}
72115

73116
id, err := urlSaver.SaveURL(req.URL, alias)

internal/storage/sqlite/sqlite.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,65 @@ func New(storagePath string) (*Storage, error) {
4343
return &Storage{db: db}, nil
4444
}
4545

46+
// AliasExists checks whether the specified alias exists in the database.
47+
func (s *Storage) AliasExists(alias string) (bool, error) {
48+
const op = "storage.sqlite.AliasExists"
49+
50+
stmt, err := s.db.Prepare(`SELECT COUNT(*) FROM url WHERE alias = ?`)
51+
if err != nil {
52+
return false, fmt.Errorf("%s: prepare statement %w", op, err)
53+
}
54+
55+
var count int
56+
err = stmt.QueryRow(alias).Scan(&count)
57+
if err != nil {
58+
return false, fmt.Errorf("%s: execute statement: %w", op, err)
59+
}
60+
61+
return count > 0, nil
62+
}
63+
64+
// URLExists checks whether the specified URL exists in the database.
65+
func (s *Storage) URLExists(urlToCheck string) (bool, error) {
66+
const op = "storage.sqlite.URLExists"
67+
68+
stmt, err := s.db.Prepare(`SELECT COUNT(*) FROM url WHERE url = ?`)
69+
if err != nil {
70+
return false, fmt.Errorf("%s: prepare statement %w", op, err)
71+
}
72+
73+
var count int
74+
err = stmt.QueryRow(urlToCheck).Scan(&count)
75+
if err != nil {
76+
return false, fmt.Errorf("%s: execute statement: %w", op, err)
77+
}
78+
79+
return count > 0, nil
80+
}
81+
82+
// GetAliasByURL retrieves the alias associated with a given URL from the database.
83+
func (s *Storage) GetAliasByURL(urlToFind string) (string, error) {
84+
const op = "storage.sqlite.GetAliasByURL"
85+
86+
stmt, err := s.db.Prepare(`SELECT alias FROM url WHERE url = ?`)
87+
if err != nil {
88+
return "", fmt.Errorf("%s: prepare statement %w", op, err)
89+
}
90+
91+
var alias string
92+
err = stmt.QueryRow(urlToFind).Scan(&alias)
93+
94+
if err != nil {
95+
if errors.Is(err, sql.ErrNoRows) {
96+
return "", storage.ErrURLNotFound
97+
}
98+
99+
return "", fmt.Errorf("%s: execute statement: %w", op, err)
100+
}
101+
102+
return alias, nil
103+
}
104+
46105
func (s *Storage) SaveURL(urlToSave string, alias string) (int64, error) {
47106
const op = "storage.sqlite.SaveURL"
48107

static/image/icon.ico

255 KB
Binary file not shown.

static/index.css

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,24 @@
33
border-image-slice: 1;
44
}
55

6-
html,
7-
body {
8-
overflow: hidden;
6+
/* width */
7+
::-webkit-scrollbar {
8+
width: 10px;
9+
}
10+
11+
/* Track */
12+
::-webkit-scrollbar-track {
13+
background: #888;
14+
border-radius: 5px;
15+
}
16+
17+
/* Handle */
18+
::-webkit-scrollbar-thumb {
19+
background: #000;
20+
border-radius: 5px;
21+
}
22+
23+
/* Handle on hover */
24+
::-webkit-scrollbar-thumb:hover {
25+
background: #555;
926
}

static/index.html

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html lang="en" class="overflow-visible lg:overflow-hidden">
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -8,6 +8,7 @@
88
<meta name="description" content="" />
99
<meta name="keywords" content="" />
1010
<link rel="stylesheet" href="/static/index.css" />
11+
<link rel="icon" href="/static/image/icon.ico" type="image/x-icon" />
1112
<script src="/static/main.js"></script>
1213

1314
<link
@@ -121,16 +122,40 @@
121122
type="text"
122123
placeholder="https://example.com/anything_here"
123124
/>
125+
<div class="hidden" id="advancedSettings">
126+
<!--адвансед сеттингс-->
127+
<label
128+
for="aliasInput"
129+
class="block text-blue-300 mt-6 py-2 font-bold mb-2"
130+
>
131+
Type your custom URL:
132+
</label>
133+
<input
134+
class="shadow appearance-none border rounded w-full p-3 text-gray-700 leading-tight focus:ring transform transition hover:scale-105 duration-300 ease-in-out"
135+
id="aliasInput"
136+
type="text"
137+
placeholder="amogus"
138+
/>
139+
</div>
124140
</div>
125141

126-
<div class="flex items-center justify-between pt-4">
142+
<div
143+
class="flex items-center justify-between text-sm lg:text-base pt-4"
144+
>
127145
<button
128146
id="cut"
129-
class="bg-gradient-to-r from-purple-800 to-green-500 hover:from-pink-500 hover:to-green-500 text-white font-bold py-2 px-4 rounded focus:ring transform transition hover:scale-105 duration-300 ease-in-out"
147+
class="bg-gradient-to-r mr-10 lg:mr-0 from-purple-800 to-green-500 hover:from-pink-500 hover:to-green-500 text-white font-bold py-2 px-2 lg:px-4 rounded focus:ring transform transition hover:scale-105 duration-300 ease-in-out"
130148
type="button"
131149
>
132150
Cut it down!
133151
</button>
152+
<button
153+
id="advancedSettingsButton"
154+
class="bg-gradient-to-r from-purple-800 to-green-500 hover:from-pink-500 hover:to-green-500 text-white font-bold py-2 px-2 lg:px-4 rounded focus:ring transform transition hover:scale-105 duration-300 ease-in-out"
155+
type="button"
156+
>
157+
Advanced Settings
158+
</button>
134159
</div>
135160
</form>
136161
</div>
@@ -232,27 +257,30 @@ <h2 class="font-bold text-white pb-3">
232257
</div>
233258

234259
<!--Footer-->
235-
<div class="w-full pt-16 pb-6 text-sm text-center md:text-left fade-in">
236-
<a class="text-gray-500 no-underline hover:no-underline" href="#"
237-
>&copy; SUS.KZ</a
260+
<div
261+
class="w-full text-gray-500 pt-16 pb-6 text-sm text-center md:text-left fade-in"
262+
>
263+
<a class="no-underline hover:no-underline" href="#"
264+
>&copy; SUS.KZ, 2023</a
238265
>
239266
- service by
240-
<a
267+
<a
241268
class="text-gray-500 no-underline hover:no-underline"
242269
href="https://github.com/RomanVassilchenko"
243270
>Roman Vassilchenko
244271
</a>
245-
. Special thanks to
272+
. Special thanks to
246273
<a
247-
class="text-gray-500 no-underline hover:no-underline"
274+
class="no-underline hover:no-underline text-blue-400"
248275
href="https://github.com/vadimvalov"
249276
>Vadim Valov
250277
</a>
251-
and
278+
and
252279
<a
253280
href="https://github.com/SaraUlan"
254281
class="text-gray-500 no-underline hover:no-underline"
255-
> Sara Ulan, 2023</a
282+
>
283+
Sara Ulan, 2023</a
256284
>
257285
</div>
258286
</div>
@@ -285,5 +313,34 @@ <h2 class="font-bold text-white pb-3">
285313
modal.classList.add("hidden");
286314
});
287315
// modal window__close
316+
317+
const advancedSettings = document.getElementById("advancedSettings");
318+
const advancedSettingsButton = document.getElementById(
319+
"advancedSettingsButton"
320+
);
321+
322+
advancedSettingsButton.addEventListener("click", function () {
323+
advancedSettings.classList.toggle("hidden");
324+
if (advancedSettings.classList.contains("hidden")) {
325+
advancedSettingsButton.textContent = "Advanced Settings";
326+
} else {
327+
advancedSettingsButton.textContent = "Normal Settings";
328+
}
329+
});
330+
</script>
331+
332+
<!-- Google tag (gtag.js) -->
333+
<script
334+
async
335+
src="https://www.googletagmanager.com/gtag/js?id=G-EX4H5SNWB6"
336+
></script>
337+
<script>
338+
window.dataLayer = window.dataLayer || [];
339+
function gtag() {
340+
dataLayer.push(arguments);
341+
}
342+
gtag("js", new Date());
343+
344+
gtag("config", "G-EX4H5SNWB6");
288345
</script>
289346
</html>

0 commit comments

Comments
 (0)