Skip to content

Commit 17f94c6

Browse files
committed
fix lint issues
1 parent e233010 commit 17f94c6

8 files changed

Lines changed: 143 additions & 401 deletions

File tree

app/api/fast_api.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
import re
33
import traceback
44
from datetime import datetime, timezone
5+
from typing import TYPE_CHECKING
56

67
from fastapi import APIRouter, FastAPI, Request
78
from fastapi.responses import HTMLResponse, JSONResponse
89
from pydantic import BaseModel, Field
910

10-
from typing import TYPE_CHECKING
11-
1211
if TYPE_CHECKING:
1312
from pymongo.errors import PyMongoError
1413
else:
@@ -155,7 +154,7 @@ def shorten_url(payload: ShortenRequest):
155154
},
156155
)
157156

158-
if db_data.urls is None:
157+
if db_data.collection is None:
159158
cached_short = get_short_from_cache(original_url)
160159
short_code = cached_short or generate_code()
161160
set_cache_pair(short_code, original_url)
@@ -167,7 +166,7 @@ def shorten_url(payload: ShortenRequest):
167166
}
168167

169168
try:
170-
existing = db_data.urls.find_one({"original_url": original_url})
169+
existing = db_data.collection.find_one({"original_url": original_url})
171170
except PyMongoError:
172171
existing = None
173172

@@ -181,7 +180,7 @@ def shorten_url(payload: ShortenRequest):
181180

182181
short_code = generate_code()
183182
try:
184-
db_data.urls.insert_one(
183+
db_data.collection.insert_one(
185184
{
186185
"short_code": short_code,
187186
"original_url": original_url,

app/main.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
from app.utils import data as db_data
1313
from app.utils.cache import (
1414
get_from_cache,
15-
get_short_from_cache,
1615
get_recent_from_cache,
16+
get_short_from_cache,
1717
rev_cache,
1818
set_cache_pair,
1919
url_cache,
2020
)
21-
from app.utils.config import load_env, SESSION_SECRET, DOMAIN, MAX_RECENT_URLS
21+
from app.utils.config import DOMAIN, MAX_RECENT_URLS, SESSION_SECRET, load_env
2222
from app.utils.helper import (
2323
format_date,
2424
generate_code,
@@ -27,13 +27,14 @@
2727
)
2828
from app.utils.qr import generate_qr_with_logo
2929

30+
load_env()
31+
3032

3133
# -----------------------------
3234
# Lifespan: env + DB connect ONCE
3335
# -----------------------------
3436
@asynccontextmanager
3537
async def lifespan(app: FastAPI):
36-
load_env()
3738
db_data.connect_db()
3839
yield
3940

@@ -118,27 +119,40 @@ async def create_short_url(
118119
)
119120
return RedirectResponse("/", status_code=303)
120121

121-
cached_short = get_short_from_cache(original_url)
122-
if cached_short:
123-
short_code = cached_short
122+
# 1. Try Cache First
123+
short_code: Optional[str] = get_short_from_cache(original_url)
124+
125+
if short_code:
124126
session["info_message"] = "Already shortened before — fetched from cache."
125127
else:
126-
short_code = None
127-
128+
# 2. Try Database
128129
existing = db_data.find_by_original_url(original_url)
129-
if existing:
130-
short_code = existing["short_code"]
131-
set_cache_pair(short_code, original_url)
130+
# Pull the value and check it in one go
131+
db_code = existing.get("short_code") if existing else None
132+
if isinstance(db_code, str):
133+
short_code = db_code
134+
set_cache_pair(short_code, original_url) # Cache it for future
132135
session["info_message"] = (
133136
"Already shortened before — fetched from database."
134137
)
135138

139+
# 3. Generate New if still None
136140
if not short_code:
137141
short_code = generate_code()
138142
set_cache_pair(short_code, original_url)
139143
db_data.insert_url(short_code, original_url)
140144

145+
# --- TYPE GUARD FOR MYPY ---
146+
# At this point, short_code could still technically be Optional[str]
147+
# if generate_code() wasn't strictly typed. We cast or assert.
148+
if not isinstance(short_code, str):
149+
# This acts as a final safety net for production
150+
session["error"] = "Internal server error: Code generation failed."
151+
return RedirectResponse("/", status_code=303)
152+
153+
# Mypy now knows short_code is strictly 'str'
141154
new_short_url = build_short_url(short_code, str(request.base_url))
155+
142156
session.update(
143157
{
144158
"new_short_url": new_short_url,
@@ -201,8 +215,7 @@ async def redirect_short(request: Request, short_code: str):
201215
if doc:
202216
set_cache_pair(short_code, doc["original_url"])
203217
return RedirectResponse(doc["original_url"])
204-
205-
if not db_data.get_collection():
218+
if db_data.get_collection() is None:
206219
return PlainTextResponse("Database is not connected.", status_code=503)
207220

208221
return PlainTextResponse("Invalid or expired short URL", status_code=404)

app/static/css/tiny.css

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
body {
1111
background: var(--bg);
1212
color: var(--text-primary);
13-
font-family: 'Inter', system-ui, sans-serif;
13+
font-family: "Inter", system-ui, sans-serif;
1414
margin: 0;
1515
overflow-x: hidden;
1616
background-image: radial-gradient(circle at 50% -20%, #1e1e2e 0%, transparent 50%);
@@ -24,6 +24,7 @@ body {
2424
flex-direction: column;
2525
align-items: center;
2626
gap: 2rem;
27+
min-height: 52vh;
2728
}
2829

2930
/* --- LARGE HERO INPUT --- */
@@ -205,4 +206,51 @@ body {
205206
.footer-grid {
206207
grid-template-columns: 1fr 1fr;
207208
}
208-
}
209+
}
210+
211+
/* The container for the icon/button */
212+
.copy-btn {
213+
position: relative;
214+
cursor: pointer;
215+
transition: transform 0.1s ease;
216+
}
217+
218+
.copy-btn:active {
219+
transform: scale(0.9);
220+
}
221+
222+
/* The "Smart" Tooltip */
223+
.copy-btn.copy-success::after {
224+
content: attr(data-tooltip);
225+
position: absolute;
226+
top: -30px;
227+
left: 50%;
228+
transform: translateX(-50%);
229+
background: #333;
230+
color: #fff;
231+
padding: 4px 8px;
232+
border-radius: 4px;
233+
font-size: 12px;
234+
white-space: nowrap;
235+
animation: fadeInOut 2s forwards;
236+
}
237+
238+
@keyframes fadeInOut {
239+
0% {
240+
opacity: 0;
241+
transform: translate(-50%, 5px);
242+
}
243+
244+
15% {
245+
opacity: 1;
246+
transform: translate(-50%, 0);
247+
}
248+
249+
85% {
250+
opacity: 1;
251+
}
252+
253+
100% {
254+
opacity: 0;
255+
}
256+
}

app/templates/index.html

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ <h1>tiny URL</h1>
3939
</div>
4040
</div>
4141
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
42-
<button onclick="copyLink()" class="btn-copy"
43-
style="background: rgba(255,255,255,0.05); border: 1px solid var(--glass-border); color: white; padding: 0.8rem 1.5rem; border-radius: 1rem; cursor: pointer;">Copy
44-
Link</button>
45-
{% if qr_image %}<a href="{{ qr_image }}" download
46-
style="text-align: center; font-size: 0.7rem; color: var(--text-secondary); text-decoration: none;">Download
47-
QR</a>{% endif %}
42+
<button onclick="copyLink(this)" data-url="{{ new_short_url }}" class="btn-copy"
43+
style="background: rgba(255,255,255,0.05); border: 1px solid var(--glass-border); color: white; padding: 0.8rem 1.5rem; border-radius: 1rem; cursor: pointer; position: relative;">
44+
Copy Link
45+
</button>
46+
47+
{% if qr_image %}
48+
<a href="{{ qr_image }}" download
49+
style="text-align: center; font-size: 0.7rem; color: var(--text-secondary); text-decoration: none;">
50+
Download QR
51+
</a>
52+
{% endif %}
4853
</div>
4954
</div>
5055
{% endif %}
@@ -113,10 +118,30 @@ <h4>Legal</h4>
113118
</footer>
114119

115120
<script>
116-
function copyLink() {
117-
const text = document.getElementById('shortUrlDisplay').innerText;
118-
navigator.clipboard.writeText(text);
119-
alert('Copied to clipboard!');
121+
async function copyLink(btn) {
122+
// Get the dynamic value from the data attribute
123+
const url = btn.getAttribute('data-url');
124+
const originalText = btn.innerText;
125+
126+
try {
127+
await navigator.clipboard.writeText(url);
128+
129+
// Visual feedback: Change text and style
130+
btn.innerText = 'Copied!';
131+
btn.style.borderColor = '#4ade80'; // A subtle green (Tailwind-like)
132+
btn.style.color = '#4ade80';
133+
134+
// Reset after 2 seconds
135+
setTimeout(() => {
136+
btn.innerText = originalText;
137+
btn.style.borderColor = 'var(--glass-border)';
138+
btn.style.color = 'white';
139+
}, 2000);
140+
141+
} catch (err) {
142+
console.error('Copy failed', err);
143+
// Fallback if needed
144+
}
120145
}
121146
</script>
122147
{% endblock %}

0 commit comments

Comments
 (0)