{"id":18438684,"url":"https://github.com/seapagan/url-shortener2","last_synced_at":"2026-05-19T06:07:06.336Z","repository":{"id":88709187,"uuid":"593188314","full_name":"seapagan/url-shortener2","owner":"seapagan","description":"URL Shortener Version 2. Rebuilt using my Fastapi-template project - original version was based on a realpython.com tutorial, with additional functionality","archived":false,"fork":false,"pushed_at":"2024-01-11T20:28:58.000Z","size":764,"stargazers_count":1,"open_issues_count":12,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-16T10:44:22.190Z","etag":null,"topics":["fastapi","python"],"latest_commit_sha":null,"homepage":"","language":"Python","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/seapagan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","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},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":["https://www.buymeacoffee.com/seapagan"]}},"created_at":"2023-01-25T12:53:54.000Z","updated_at":"2024-05-24T12:13:31.000Z","dependencies_parsed_at":"2023-06-12T19:30:59.676Z","dependency_job_id":"ad36a839-933f-4fb5-b7e4-c814d510dfad","html_url":"https://github.com/seapagan/url-shortener2","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":"seapagan/fastapi-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seapagan%2Furl-shortener2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seapagan%2Furl-shortener2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seapagan%2Furl-shortener2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seapagan%2Furl-shortener2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seapagan","download_url":"https://codeload.github.com/seapagan/url-shortener2/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248886927,"owners_count":21177777,"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":["fastapi","python"],"created_at":"2024-11-06T06:21:07.422Z","updated_at":"2026-05-19T06:07:01.314Z","avatar_url":"https://github.com/seapagan.png","language":"Python","funding_links":["https://www.buymeacoffee.com/seapagan"],"categories":[],"sub_categories":[],"readme":"# URL Shortener / Redirector \u003c!-- omit in toc --\u003e\n\nThis a rewrite of my [URL Shortener](https://github.com/seapagan/url-shortener)\nwhich was in turn based on and extended from an original tutorial from [Real\nPython](https://realpython.com/courses/url-shortener-fastapi/).\n\nThe API uses the [FastAPI framework](https://fastapi.tiangolo.com/)\n\n- [Functionality](#functionality)\n- [Configuration](#configuration)\n- [Development](#development)\n  - [Set up a Virtual Environment](#set-up-a-virtual-environment)\n  - [Install required Dependencies](#install-required-dependencies)\n  - [Migrate the Database](#migrate-the-database)\n  - [Add a user](#add-a-user)\n  - [Run a development Server](#run-a-development-server)\n- [Deploying to Production](#deploying-to-production)\n- [Planned Functionality](#planned-functionality)\n- [Contributing](#contributing)\n- [Project Organization](#project-organization)\n- [Provided Routes](#provided-routes)\n  - [**`GET`** _/list_](#get-list)\n  - [**`POST`** _/create_](#post-create)\n  - [**`PATCH`** _/{url\\_key}/edit_](#patch-url_keyedit)\n  - [**`GET`** _/{url\\_key}/peek_](#get-url_keypeek)\n  - [**`DELETE`** _/{url\\_key}_](#delete-url_key)\n  - [**`POST`** _/{url\\_key}/deactivate_](#post-url_keydeactivate)\n  - [**`POST`** _/{url\\_key}/activate_](#post-url_keyactivate)\n  - [**`GET`** _/users_](#get-users)\n  - [**`GET`** _/users/me_](#get-usersme)\n  - [**`POST`** _/users/{user\\_id}/make-admin_](#post-usersuser_idmake-admin)\n  - [**`POST`** _/users/{user\\_id}/password_](#post-usersuser_idpassword)\n  - [**`POST`** _/users/{user\\_id}/ban_](#post-usersuser_idban)\n  - [**`POST`** _/users/{user\\_id}/unban_](#post-usersuser_idunban)\n  - [**`PUT`** _/users/{user\\_id}_](#put-usersuser_id)\n  - [**`DELETE`** _/users/{user\\_id}_](#delete-usersuser_id)\n  - [**`POST`** _/register_](#post-register)\n  - [**`POST`** _/login_](#post-login)\n  - [**`POST`** _/refresh_](#post-refresh)\n  - [**`GET`** _/verify_](#get-verify)\n\n## Functionality\n\nThis application currently has the same functionality as Version 1, with the\naddition of User Authentication and Authorization. Anonymous users can use the\nredirect functionality, but cannot add new redirects nor edit existing.\n\nFull API documentation is available from the `/docs` route, which also allows to\ntest out the API.\n\nFuture plans are to add a user-friendly front end to this.\n\n## Configuration\n\nDatabase (and other) settings can be read from environment variables or from a\n`.env` file in the project root. By default, these are only used for the\nDatabase setup, Email settings and JWT Secret Key. See the\n[.env.example](.env.example) file for how to use.\n\n```ini\n# The Base API Url. This is where your API wil be served from, and can be read\n# in the application code. It has no effect on the running of the applciation\n# but is an easy way to build a path for API responses. Defaults to\n# http://localhost:8000\nBASE_URL=http://localhost:8000\n\n# Database Settings These must be changed to match your setup.\nDB_USER=dbuser\nDB_PASSWORD=my_secret_passw0rd\nDB_ADDRESS=localhost\nDB_PORT=5432\nDB_NAME=my_database_name\n\n# generate your own super secret key here, used by the JWT functions.\n# 32 characters or longer, definately change the below!!\nSECRET_KEY=123456\n\n# List of origins that can access this API, separated by a comma, eg:\n# CORS_ORIGINS=http://localhost,https://www.gnramsay.com\n# If you want all origins to access (the default), use * or leave commented:\nCORS_ORIGINS=*\n\n# Email Settings\nMAIL_USERNAME=test_username\nMAIL_PASSWORD=s3cr3tma1lp@ssw0rd\nMAIL_FROM=test@email.com\nMAIL_PORT=587\nMAIL_SERVER=mail.server.com\nMAIL_FROM_NAME=\"Seapagan @ URL Redirector\"\nMAIL_STARTTLS=True\nMAIL_SSL_TLS=False\nMAIL_USE_CREDENTIALS=True\nMAIL_VALIDATE_CERTS=True\n```\n\nFor a **PUBLIC API** (unless its going through an API gateway!), set\n`CORS_ORIGINS=*`, otherwise list the domains (**and ports**) required. If you\nuse an API gateway of some nature, that will probably need to be listed.\n\nTo generate a good secret key you can use the below command on Linux or Mac:\n\n```console\n$ openssl rand -base64 32\nxtFhsNhbGOJG//TAtDNtoTxV/hVDvssC79ApNm0gs7w=\n\n```\n\nIf the database is not configured or cannot be reached, the Application will\ndisable all routes, print an error to the console, and return a a 500 status\ncode with a clear JSON message for all routes. This saves the ugly default\n\"Internal Server Error\" from being displayed.\n\n## Development\n\n### Set up a Virtual Environment\n\nIt is always a good idea to set up dedicated Virtual Environment when you are\ndeveloping a Python application. If you use Poetry, this will be done\nautomatically for you when you run `poetry install`.\n\nOtherwise, [Pyenv](https://github.com/pyenv/pyenv) has a\n[virtualenv](https://github.com/pyenv/pyenv-virtualenv) plugin which is very\neasy to use.\n\nAlso, check out this\n[freeCodeCamp](https://www.freecodecamp.org/news/how-to-setup-virtual-environments-in-python/)\ntutorial or a similar\n[RealPython](https://realpython.com/python-virtual-environments-a-primer/) one\nfor some great info. If you are going this (oldschool!) way, I'd recommend using\n[Virtualenv](https://virtualenv.pypa.io/en/latest/) instead of the built in\n`venv` tool (which is a subset of this).\n\n### Install required Dependencies\n\nThe project has been set up using [Poetry](https://python-poetry.org/) to\norganize and install dependencies. If you have Poetry installed, simply run the\nfollowing to install all that is needed.\n\n```console\npoetry install\n```\n\nIf you do not (or cannot) have Poetry installed, I have provided an\nauto-generated `requirements.txt` in the project root which you can use as\nnormal:\n\n```console\npip install -r requirements.txt\n```\n\nI definately recommend using Poetry if you can though, it makes dealing with\nupdates and conflicts very easy.\n\nIf using poetry you now need to activate the VirtualEnv:\n\n```console\npoetry shell\n```\n\n### Migrate the Database\n\nMake sure you have [configured](#configuration) the database. Then run the\nfollowing command to setup the database:\n\n```console\nalembic upgrade head\n```\n\nEverytime you add or edit a model, create a new migration then run the upgrade\nas shown below:\n\n```console\nalembic revision -m \"\u003cMy commit message\u003e\"\nalembic upgrade head\n```\n\nCheck out the [Alembic](https://github.com/sqlalchemy/alembic) repository for\nmore information on how to use (for example how to revert migrations).\n\n### Add a user\n\nIt is possible to add Users to the database using the API itself, but you cannot\ncreate an Admin user this way, unless you already have an existing Admin user in\nthe database.\n\nThis template includes a command-line utility to create a new user and\noptionally make them Admin at the same time:\n\n```console\n./api-admin user create\n```\n\nYou will be asked for the new user's email etc, and if this should be an\nAdmin user (default is to be a standard non-admin User). These values can be\nadded from the command line too, for automated use. See the built in help for\ndetails :\n\n```console\n$ ./api-admin user create --help\nUsage: api-admin user create [OPTIONS]\n\n  Create a new user.\n\n  Values are either taken from the command line options, or interactively for\n  any that are missing.\n\nOptions:\n  -e, --email TEXT       [required]\n  -f, --first_name TEXT  [required]\n  -l, --last_name TEXT   [required]\n  -p, --password TEXT    [required]\n  -a, --admin TEXT       [required]\n  --help                 Show this message and exit.\n```\n\nNote that any user added manually this way will automatically be verified (no\nneed for the confirmation email which will not be sent anyway.)\n\n### Run a development Server\n\nThe [uvicorn](https://www.uvicorn.org/) ASGI server is automatically installed\nwhen you install the project dependencies. This can be used for testing the API\nduring development. There is a built-in command to run this easily :\n\n```console\n./api-admin dev\n```\n\nThis will by default run the server on \u003chttp://localhost:8000\u003e, and reload after\nany change to the source code. You can add options to change this\n\n```console\n$ ./api-admin dev --help\n\nUsage: api-admin dev [OPTIONS]\n\n  Run a development server from the command line.\n\n  This will auto-refresh on any changes to the source in real-time.\n\nOptions:\n  -h, --host TEXT       Define the interface to run the server on.  [default:\n                        localhost]\n  -p, --port INTEGER    Define the port to run the server on  [default: 8000]\n  -r, --reload BOOLEAN  [default: True]\n  --help                Show this message and exit.\n```\n\nIf you need more control, you can run `uvicorn` directly :\n\n```console\nuvicorn main:app --reload\n```\n\nThe above command starts the server running on \u003chttp://localhost:8000\u003e, and it\nwill automatically reload when it detects any changes as you develop.\n\n**Note: Neither of these are suitable to host a project in production, see the\nnext section for information.**\n\n## Deploying to Production\n\nThere are quite a few ways to deploy a FastAPI app to production. There is a\nvery good discussion about this on the FastAPI [Deployment\nGuide](https://fastapi.tiangolo.com/deployment/) which covers using Uvicorn,\nGunicorn and Containers.\n\nMy Personal preference is to serve with Gunicorn, using uvicorn workers behind\nan Nginx proxy, though this does require you having your own server. There is a\npretty decent tutorial on this at\n[Vultr](https://www.vultr.com/docs/how-to-deploy-fastapi-applications-with-gunicorn-and-nginx-on-ubuntu-20-04/).\nFor deploying to AWS Lambda with API Gateway, there is a really excellent Medium\npost (and it's followup)\n[Here](https://medium.com/towards-data-science/fastapi-aws-robust-api-part-1-f67ae47390f9),\nor for AWS Elastic Beanstalk there is a very comprehensive tutorial at\n[testdriven.io](https://testdriven.io/blog/fastapi-elastic-beanstalk/)\n\n\u003e Remember:  you still need to set up a virtual environment, install all the\n\u003e dependencies, setup your `.env` file (or use Environment variables if your\n\u003e hosting provider uses these - for example Vercel or Heroku) and set up and\n\u003e migrate your Database, exactly the same as for Develpment as desctribed above.\n\n## Planned Functionality\n\nSee the [TODO.md](TODO.md) file for plans.\n\n## Contributing\n\nPlease **do** feel free to open an Issue for any bugs or issues you find, or\neven a Pull Request with solutions 😎\n\nLikewise, I am very open to new feature Pull Requests!\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Project Organization\n\nThis project has been deliberately laid out in a specific way. To avoid long\ncomplicated files which are difficult to debug, functionality is separated out\nin files and modules depending on the specific functionality.\n\n[main.py](main.py) - The main controlling file, this should be as clean and\nshort as possible with all functionality moved out to modules.\n\n[database/](/database) - This module controls database setup and configuration,\nand should generally not need to be touched.\n\n[config/](/config) - Handles the API settings and defaults, also the Metadata\ncustomization. If you add more settings (for example in the `.env` file) you\nshould also add them to the [settings.py](config/settings.py) or\n[metadata.py](config/metadata.py) with suitable defaults. Non-secret (or\ndepoloyment independent) settings should go in the `metadata` file, while\nsecrets (or deployment specific) should go in the `settings` and `.env` files\n\n[commands/](/commands) - This directory can hold any commands you need to write,\nfor example populating a database, create a superuser or other housekeeping\ntasks.\n\n[managers/](/managers) - This directory contains individual files for each\n'group' of functionality. They contain a Class that should take care of the\nactual work needed for the routes. Check out the [auth.py](managers/auth.py) and\n[user.py](managers/user.py)\n\n[migrations/](/migrations) - We use\n[Alembic](https://github.com/sqlalchemy/alembic) to handle the database\nmigrations. Check out their pages for more info. See instructions under\n[Development](#development) for more info.\n\n[models/](/models) - Any database models used should be defined here along with\nsupporting files (eq the [enums.py](models/enums.py)) used here. Models are\nspecified using the SQLAlchemy format, see [user.py](models/user.py) for an\nexample.\n\n[resources/](/resources) - Contains the actual Route resources used by your API.\nBasically, each grouped set of routes should have its own file, which then\nshould be imported into the [routes.py](resources/routes.py) file. That file is\nautomatically imported into the main application, so there are no more changes\nneeded. Check out the routes in [user.py](resources/user.py) for a good example.\nNote that the routes contain minimal actual logic, instead they call the\nrequired functionality from the Manager ([UserManager](managers/user.py) in this\ncase).\n\n[schemas/](/schemas) - Contains all `request` and `response` schemas used in the\napplication, as usual with a separate file for each group. The Schemas are\ndefined as [Pydantic](https://pydantic-docs.helpmanual.io/) Classes.\n\n[helpers/](/helpers) - Contains some helper functions that can be used across the\ncode base.\n\n[static/](/static) - Any static files used by HTML templates for example CSS or\nJS files.\n\n[templates/](/templates) - Any HTML templates. We have one by default - used\nonly when the root of the API is accessed using a Web Browser (otherwise a\nsimple informational JSON response is returned). You can edit the template in\n[index.html](templates/index.html) for your own API.\n\n## Provided Routes\n\nSee below for a full list on implemented routes in this API.\n\nFor full info and to test the routes, you can go to the `/docs` path on a\nrunning API for interactive Swagger (OpenAPI) Documentation.\n\n\u003c!-- openapi-schema --\u003e\n\n### **`GET`** _/list_\n\n\u003e List Redirects : _List all URL's for the logged in user._\n\u003e\n\u003e Admin users can see all, anon users see nothing.\n\n### **`POST`** _/create_\n\n\u003e Create A Redirect : _Create a new URL redirection belonging to the current User._\n\n### **`PATCH`** _/{url_key}/edit_\n\n\u003e Edit A Redirect : _Edit an existing URL entry destination._\n\n### **`GET`** _/{url_key}/peek_\n\n\u003e Peek A Redirect : _Return the target of the URL redirect only._\n\u003e\n\u003e Anon users can access this.\n\n### **`DELETE`** _/{url_key}_\n\n\u003e Remove Redirect : _Delete the specified URL redirect._\n\n### **`POST`** _/{url_key}/deactivate_\n\n\u003e Deactivate Redirect : _Deactivate the specified URL redirect._\n\n### **`POST`** _/{url_key}/activate_\n\n\u003e Activate Redirect : _Activate the specified URL redirect._\n\n### **`GET`** _/users_\n\n\u003e Get Users : _Get all users or a specific user by their ID._\n\u003e\n\u003e To get a specific User data, the requesting user must match the user_id, or\n\u003e be an Admin.\n\u003e\n\u003e user_id is optional, and if omitted then all Users are returned. This is\n\u003e only allowed for Admins.\n\n### **`GET`** _/users/me_\n\n\u003e Get My User Data : _Get the current user's data only._\n\n### **`POST`** _/users/{user_id}/make-admin_\n\n\u003e Make Admin : _Make the User with this ID an Admin._\n\n### **`POST`** _/users/{user_id}/password_\n\n\u003e Change Password : _Change the password for the specified user._\n\u003e\n\u003e Can only be done by an Admin, or the specific user that matches the user_id.\n\n### **`POST`** _/users/{user_id}/ban_\n\n\u003e Ban User : _Ban the specific user Id._\n\u003e\n\u003e Admins only. The Admin cannot ban their own ID!\n\n### **`POST`** _/users/{user_id}/unban_\n\n\u003e Unban User : _Ban the specific user Id._\n\u003e\n\u003e Admins only.\n\n### **`PUT`** _/users/{user_id}_\n\n\u003e Edit User : _Update the specified User's data._\n\u003e\n\u003e Available for the specific requesting User, or an Admin.\n\u003e\n### **`DELETE`** _/users/{user_id}_\n\n\u003e Delete User : _Delete the specified User by user_id._\n\u003e\n\u003e Admin only.\n\n### **`POST`** _/register_\n\n\u003e Register A New User : _Register a new User and return a JWT token plus a Refresh Token._\n\u003e\n\u003e The JWT token should be sent as a Bearer token for each access to a\n\u003e protected route. It will expire after 120 minutes.\n\u003e\n\u003e When the JWT expires, the Refresh Token can be sent using the '/refresh'\n\u003e endpoint to return a new JWT Token. The Refresh token will last 30 days, and\n\u003e cannot be refreshed.\n\n### **`POST`** _/login_\n\n\u003e Login An Existing User : _Login an existing User and return a JWT token plus a Refresh Token._\n\u003e\n\u003e The JWT token should be sent as a Bearer token for each access to a\n\u003e protected route. It will expire after 120 minutes.\n\u003e\n\u003e When the JWT expires, the Refresh Token can be sent using the '/refresh'\n\u003e endpoint to return a new JWT Token. The Refresh token will last 30 days, and\n\u003e cannot be refreshed.\n\n### **`POST`** _/refresh_\n\n\u003e Refresh An Expired Token : _Return a new JWT, given a valid Refresh token._\n\u003e\n\u003e The Refresh token will not be updated at this time, it will still expire 30\n\u003e days after original issue. At that time the User will need to login again.\n\n### **`GET`** _/verify_\n\n\u003e Verify : _Verify a new user._\n\u003e\n\u003e The code is sent to  new user by email, which must then be validated here.\n\u003c!-- openapi-schema-end --\u003e\n\nThe route table above was automatically generated from an `openapi.json` file by\nmy [openapi-readme](https://pypi.org/project/openapi-readme/) project. Check it\nout for your own API documentation! 😊\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseapagan%2Furl-shortener2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseapagan%2Furl-shortener2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseapagan%2Furl-shortener2/lists"}