Coverage for app / schemas / link_schemas.py: 100%
44 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-22 00:48 +0300
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-22 00:48 +0300
1from datetime import UTC, datetime, timedelta
2from typing import Annotated
4from pydantic import BaseModel, Field, HttpUrl, PlainSerializer, field_validator
6from app.core.config import settings
8short_code_pattern = r"^[0-9a-z_-]{5,30}$"
10CustomAlias = Annotated[
11 str | None,
12 Field(
13 default=None,
14 pattern=short_code_pattern,
15 description="Custom alias to use instead of a random short code",
16 ),
17]
19DatetimeUTC = Annotated[
20 datetime,
21 PlainSerializer(
22 lambda d: d.astimezone(tz=UTC).isoformat().replace("+00:00", "Z"),
23 when_used="json",
24 ),
25]
28class LinkShortenRequest(BaseModel):
29 long_url: HttpUrl
30 custom_alias: CustomAlias
31 auto_prolong: Annotated[
32 bool,
33 Field(
34 default=False,
35 description=(
36 "Extends access to the short link if set to True. "
37 "If there are less than 15 days left until the link's expiration date, "
38 "the first redirection to the long URL updates the expiration date "
39 f"by adding {settings.link_expire_days} day(s) to the redirection date."
40 ),
41 ),
42 ]
43 expires_at: Annotated[
44 datetime | None,
45 Field(
46 default=None,
47 examples=["2026-03-21T10:00Z"],
48 description=(
49 "Link expiration date in format 'YYYY-MM-DDTHH:MMZ'. "
50 f"Сannot exceed {settings.link_expire_days} days from current UTC date."
51 ),
52 ),
53 ]
55 @field_validator("expires_at")
56 @classmethod
57 def validate_link_expiration_date(csl, v: datetime | None):
58 if v is None:
59 return None
61 # обработаем возможный некорректный ввод даты и времени
63 # если дата без Z на конце, считаем, что это UTC
64 if v.tzinfo is None:
65 v = v.replace(tzinfo=UTC)
66 # если дата содержит часовой пояс, то приводим к UTC
67 else:
68 v = v.astimezone(tz=UTC)
70 now = datetime.now(tz=UTC) # текущее всемирное координированное время
72 if v < now:
73 raise ValueError("Expiration date cannot be less than current UTC date")
75 if v - now > timedelta(days=settings.link_expire_days):
76 raise ValueError(
77 f"Expiration date cannot exceed {settings.link_expire_days} days from current UTC date"
78 )
80 return v.replace(tzinfo=None)
83class LinkShortenResponse(BaseModel):
84 long_url: HttpUrl
85 short_url: HttpUrl
86 msg: str | None = None
89class LinkUpdateRequest(BaseModel):
90 custom_alias: CustomAlias
93class LinkUpdateResponse(BaseModel):
94 long_url: HttpUrl
95 old_short_url: HttpUrl
96 new_short_url: HttpUrl
97 msg: str | None = None
100class LinkSearchResponse(BaseModel):
101 short_urls: list[HttpUrl]
104class LinkStatsResponse(BaseModel):
105 long_url: HttpUrl
106 created_at: DatetimeUTC
107 visits_counter: int
108 last_visited_at: DatetimeUTC | None = None