{"id":27054875,"url":"https://github.com/daskwin/applied_python_hw3","last_synced_at":"2026-01-18T00:21:32.058Z","repository":{"id":285243341,"uuid":"957494983","full_name":"daskwin/Applied_python_HW3","owner":"daskwin","description":null,"archived":false,"fork":false,"pushed_at":"2025-03-30T14:15:50.000Z","size":2,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T15:24:30.330Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daskwin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-30T14:15:07.000Z","updated_at":"2025-03-30T14:15:53.000Z","dependencies_parsed_at":"2025-03-30T15:34:40.318Z","dependency_job_id":null,"html_url":"https://github.com/daskwin/Applied_python_HW3","commit_stats":null,"previous_names":["daskwin/applied_python_hw3"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daskwin%2FApplied_python_HW3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daskwin%2FApplied_python_HW3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daskwin%2FApplied_python_HW3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daskwin%2FApplied_python_HW3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daskwin","download_url":"https://codeload.github.com/daskwin/Applied_python_HW3/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312042,"owners_count":20918344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-05T09:17:36.920Z","updated_at":"2026-01-18T00:21:32.007Z","avatar_url":"https://github.com/daskwin.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simpler Linker API\n\nСервис для сокращения URL, позволяющий пользователям регистрироваться, создавать короткие ссылки с возможностью задания кастомного alias и срока действия, а также получать статистику по ссылкам.\n\n## Функциональные возможности сервиса\n\n### Обязательные функции ДЗ:\n- **Создание / удаление / изменение / получение информации по короткой ссылке:**\n  - POST `/links/shorten` – создание новой сокращённой ссылки для текущего пользователя.\n  - GET `/links/{short_code}` - получаение данных для конкретной сокращённой ссылки текущего пользователя.\n  - DELETE `/links/{short_code}` - удаление указанной сокращённой ссылки, принадлежащей текущему пользователю.\n  - PUT `/links/{short_code}` - обновление данных ссылки – оригинальный URL и/или срок действия.\n- **Статистика по ссылке:**\n  - GET `/links/{short_code}/stats` – получение статистики по сокращённой ссылке для текущего пользователя.\n- **Создание кастомных ссылок (уникальный alias):**\n  - POST `/links/shorten` (с передачей custom_alias) - через один endpoint реализуются разные функционалы (в этом пункте при указании custom_alias проверяется уникальность).\n- **Поиск ссылки по оригинальному URL:**\n  - GET `/links/search?original_url={url}` – поиск сокращенной ссылки по исходному URL для текущего пользователя.\n- **Указание времени жизни ссылки:**\n  - POST `/links/shorten` (с параметром expires_at) - через один endpoint реализуются разные функционалы (в этом пункте при указании expires_at указывается время жизни ссылки).\n- **Регистрация и управление аккаунтом:**\n  - POST `/auth/register` – регистрация нового пользователя.\n  - POST `/auth/login` – вход в систему (с установкой cookie-сессии).\n  - GET `/auth/profile` – получение профиля текущего пользователя.\n  - DELETE `/auth/user` – удаление аккаунта (с каскадным удалением всех связанных ссылок).\n\n## Структура проекта\n\n```\nSimpler Linker API/\n├── api/\n│   ├── Dockerfile\n│   ├── requirements.txt\n│   ├── .env\n│   ├── app.py\n│   ├── api/\n│   │   ├── __init__.py\n│   │   ├── auth.py\n│   │   └── links.py\n│   ├── core/\n│   │   ├── __init__.py\n│   │   └── database.py\n│   └── models/\n│       ├── __init__.py\n│       ├── user.py\n│       └── link.py\n├── docker-compose.yml\n└── README.md\n```\n\n## Локальный запуск\n\n1. **Настройка переменных окружения для локального запуска:**  \n   В файле `api/.env` укажите следующие значения по шаблону:\n   ```env\n   DATABASE_URL=postgresql://urlshort:urlshortpass@localhost:5432/urlshort_db\n   REDIS_URL=redis://localhost:6379/0\n   SECRET_KEY=YourProdSecretKeyHere\n   SESSION_TTL=86400\n   INACTIVITY_DAYS=90\n   ```\n\n2.\t**Запуск backend:**\nПерейдите в папку api и выполните:\n\n```bash\nuvicorn app.main:app --reload --host 0.0.0.0 --port 8000\n```\n\nAPI будет доступно по ссылке http://localhost:8000 в случае локального тестирования.\n\n4.\t**Тестирование API:**\n \nЯ использовала Postman.\n\n5. **Docker Compose**\n\nДля сборки сервиса был написан Docker Compose файл для локального тестирования.\n\nШаблон docker-compose.yml:\n\n```yaml\nservices:\n  db:\n    image: postgres:15-alpine\n    container_name: simplerlink_db\n    environment:\n      POSTGRES_USER: linker_user\n      POSTGRES_PASSWORD: linker_pass\n      POSTGRES_DB: linker_db\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n    networks:\n      - internal\n\n  redis:\n    image: redis:7-alpine\n    container_name: simplerlink_redis\n    ports:\n      - \"6379:6379\"\n    networks:\n      - internal\n\n  api:\n    build:\n      context: ./api\n      dockerfile: Dockerfile\n    container_name: simplerlink_api\n    env_file:\n      - ./api/.env\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      - db\n      - redis\n    networks:\n      - internal\n\nnetworks:\n  internal:\n    driver: bridge\n\nvolumes:\n  db_data:\n```\n\nЗапустить можно командой:\n\n```bash\ndocker-compose up --build\n```\n\n## Примеры работы API\n\n### 1. Register User - регистрация\n - **Endpoint**: `{{API_URL}}/api/auth/register`\n - **Method**: `POST`\n - **Headers**: Content-Type: `application/json`\n - **Body**:\n```json\n{\n  \"username\": \"daskwin\",\n  \"password\": \"mypassword\",\n  \"email\": \"daskwin@example.com\"\n}\n```\n\n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e  \"id\": 2,\n\u003e  \"username\": \"daskwin\",\n\u003e  \"email\": \"daswin@example.com\",\n\u003e  \"created_at\": \"2025-03-31T08:35:53.288861Z\"\n\u003e}\n\u003e```\n\n### 2. Login User - логин пользователя\n - **Endpoint**: `{{API_URL}}/api/auth/login`\n - **Method:** `POST`\n - **Headers:** Content-Type: `application/json`\n - **Body:**\n```json\n{\n  \"username\": \"daskwin\",\n  \"password\": \"mypassword\"\n}\n```\n\n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e    \"message\": \"Успешный вход\",\n\u003e    \"user\": {\n\u003e        \"id\": 2,\n\u003e        \"username\": \"daskwin\"\n\u003e    }\n\u003e}\n\u003e```\n\n\u003e Cookie с именем session_id сохраняется для дальнейших запросов.\n\n### 3. Get Profile\n - **Endpoint:** `{{API_URL}}/api/auth/profile`\n - **Method:** `GET`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n\n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e    \"id\": 2,\n\u003e    \"username\": \"daskwin\",\n\u003e    \"email\": \"daskwin@example.com\",\n\u003e    \"created_at\": \"2025-03-31T08:35:53.288861Z\"\n\u003e}\n\u003e```\n\n### 4. Create Link\n - **Endpoint:** `{{API_URL}}/api/links/shorten`\n - **Method:** `POST`\n - **Headers:**\n   - Content-Type: `application/json`\n   - Cookie: `session_id={{session_id}}`\n - **Body:**\n\n```json\n{\n  \"original_url\": \"https://example.com/very-long-and-difficult-for-me\",\n  \"custom_alias\": \"simple\",\n  \"expires_in_days\": 3\n}\n```\n\n\u003e **Response:**\n\u003e ```json\n\u003e {\n\u003e    \"id\": 2,\n\u003e    \"short_code\": \"simple\",\n\u003e    \"original_url\": \"https://example.com/very-long-and-difficult-for-me\",\n\u003e    \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e    \"expires_at\": \"2025-04-03T09:10:08.224911Z\",\n\u003e    \"access_count\": 0\n\u003e}\n\u003e ```\n\n### 5. List Links\n - **Endpoint:** `{{API_URL}}/api/links`\n - **Method:** `GET`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n\n\u003e **Response:**\n\u003e ```json\n\u003e[\n\u003e    {\n\u003e        \"id\": 2,\n\u003e        \"short_code\": \"simple\",\n\u003e        \"original_url\": \"https://example.com/very-long-and-difficult-for-me\",\n\u003e        \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e        \"expires_at\": \"2025-04-03T09:10:08.224911Z\",\n\u003e        \"access_count\": 0\n\u003e    }\n\u003e]\n\u003e ```\n\n### 6. Get Link by short_code\n - **Endpoint:** `{{API_URL}}/api/links/{{short_code}}`\n - **Method:** `GET`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n\n\u003e **Response for short_code = simple:**\n\u003e ```json\n\u003e {\n\u003e    \"id\": 2,\n\u003e    \"short_code\": \"simple\",\n\u003e    \"original_url\": \"https://example.com/very-long-and-difficult-for-me\",\n\u003e    \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e    \"expires_at\": \"2025-04-03T09:10:08.224911Z\",\n\u003e    \"access_count\": 0\n\u003e}\n\u003e ```\n\n### 7. Update Link\n - **Endpoint:** `{{API_URL}}/api/links/{{short_code}}`\n - **Method:** `PUT`\n - **Headers:**\n   - Content-Type: `application/json`\n   - Cookie: `session_id={{session_id}}`\n - **Body:**\n\n```json\n{\n  \"original_url\": \"https://example.com/updated-url-but-difficult\",\n  \"expires_in_days\": 5\n}\n```\n\n\u003e **Response:**\n\u003e ```json\n\u003e{\n\u003e    \"id\": 2,\n\u003e    \"short_code\": \"simple\",\n\u003e    \"original_url\": \"https://example.com/updated-url-but-difficult\",\n\u003e    \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e    \"expires_at\": \"2025-04-05T09:25:54.641557Z\",\n\u003e    \"access_count\": 0\n\u003e}\n\u003e ```\n\n### 8. Get Link Stats\n - **Endpoint:** `{{API_URL}}/api/links/{{short_code}}/stats`\n - **Method:** `GET`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n  \n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e    \"original_url\": \"https://example.com/updated-url-but-difficult\",\n\u003e    \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e    \"access_count\": 1\n\u003e}\n\u003e``` \n\n### 9. Search Link by Original URL\n - **Endpoint:** `{{API_URL}}/api/links/search?original_url=https://example.com/updated-url-but-difficult`\n - **Method:** `GET`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n\n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e    \"id\": 2,\n\u003e    \"short_code\": \"simple\",\n\u003e    \"original_url\": \"https://example.com/updated-url-but-difficult\",\n\u003e    \"created_at\": \"2025-03-31T09:10:08.226766Z\",\n\u003e    \"expires_at\": \"2025-04-05T09:25:54.641557Z\",\n\u003e    \"access_count\": 1\n\u003e}\n\u003e``` \n\n### 10. Public Redirect\n - **Endpoint:** `{{API_URL}}/api/links/public/{{short_code}}`\n - **Method:** GET\n - **Headers:** -\n - **Body:** -\n -\n\n\u003e **Response:** получили код страницы изначальной ссылки.\n\n### 11. Delete Link\n - **Endpoint:** `{{API_URL}}/api/links/{{short_code}}`\n - **Method:** `DELETE`\n - **Headers:** Cookie: `session_id={{session_id}}`\n - **Body:** -\n\n\u003e**Response:** произошло удаление привязки ссылок.\n\n### 12. Delete User (Account)\n- **Endpoint:** `{{API_URL}}/api/auth/user`\n- **Method:** `DELETE`\n- **Headers:**\n- **Cookie:** `session_id={{session_id}}`\n- **Body:** (нет)\n\n\u003e **Response:**\n\u003e```json\n\u003e{\n\u003e  \"message\": \"Пользователь удалён\"\n\u003e}\n\u003e```\n\n## Deploy на Render\n\nПроект был задеплойен с помощью платформы [Render](https://dashboard.render.com/). \nНа основе кода этого репозитория, в этой платформе было развернуто 3 компонента сервиса:\n\n1. FastAPI сервис:  [https://pylinkshort.onrender.com](https://applied-python-hw3.onrender.com);\n3. PostgreSQL DB;\n4. Redis (key-value store).\n\n## Описание БД\n\nВ качестве основного хранилища используется PostgreSQL (указанная в переменной окружения DATABASE_URL).\n\nСтруктура БД:\n- Таблица users: хранит информацию о пользователях.\n- Поля:\n  - id – уникальный идентификатор пользователя.\n  - username – уникальное имя пользователя.\n  - email – адрес электронной почты (необязательное).\n  - password_hash – хэш пароля.\n  - created_at – дата и время регистрации.\n- Таблица links: хранит данные о сокращённых ссылках.\n- Поля:\n  - id – уникальный идентификатор ссылки.\n  - original_url – исходный длинный URL.\n  - short_code – сокращённый код (alias) ссылки.\n  - created_at – дата и время создания ссылки.\n  - expires_at – дата и время истечения срока действия ссылки (если задан).\n  - access_count – количество переходов по ссылке.\n  - owner_id – идентификатор пользователя, создавшего ссылку (связь с таблицей users).\n\nRedis используется для хранения сессионных токенов (управление сессиями) и для кэширования (в моем случае при создании ссылки кэшированием запоминаем юрл, используем при публичном редиректе).\n\n## Дополнительные ссылки:\n- https://applied-python-hw3.onrender.com - ссылка на сервис\n- https://applied-python-hw3.onrender.com/docs - документация\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaskwin%2Fapplied_python_hw3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaskwin%2Fapplied_python_hw3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaskwin%2Fapplied_python_hw3/lists"}