Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions python/url-shortener/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# URL Shortener in Python

A Python function to create and manage short URLs.

## 📝 Environment Variables

- `APPWRITE_ENDPOINT`: Your Appwrite endpoint.
- `APPWRITE_API_KEY`: Your Appwrite API key.
- `APPWRITE_PROJECT`: Your Appwrite project ID.
- `DATABASE_ID`: The ID of the database to store URLs.
- `COLLECTION_ID`: The ID of the collection to store URLs.

## 🚀 Building and Deployment

1. **Create an Appwrite Database and Collection:**
* Create a database with a unique ID.
* Create a collection with the following attribute:
* `original_url` (string, required, size 2048)

2. **Deploy the Function:**
* Package the function: `tar -czvf code.tar.gz .`
* In the Appwrite Console, go to **Functions** and click **Create Function**.
* Select the **Python 3.9** runtime.
* Upload the `code.tar.gz` file.
* In the **Settings** tab, set the **Entrypoint** to `src/main.py`.
* Add the required environment variables.
* Activate the function.

## 🛠️ Usage

### Create a Short URL

Execute the function with a `POST` request and a JSON body:

```json
{
"url": "[https://www.google.com](https://www.google.com)"
}
```
2 changes: 2 additions & 0 deletions python/url-shortener/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
appwrite==3.1.0
nanoid==2.0.0
93 changes: 93 additions & 0 deletions python/url-shortener/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import os
import json
import nanoid
from appwrite.exceptions import AppwriteException
import utils

# This is your Appwrite function
# It's executed each time we get a request
def main(context):
database = utils.get_database()

database_id = os.environ.get("DATABASE_ID")
collection_id = os.environ.get("COLLECTION_ID")

if not database_id or not collection_id:
return context.res.json(
{'error': 'Missing required environment variables: DATABASE_ID or COLLECTION_ID'},
status_code=500
)

short_id = None
if context.req.method in ('GET', 'HEAD'):
path_parts = context.req.path.split('/')
if path_parts and len(path_parts[-1]) == 7:
short_id = path_parts[-1]

query = getattr(context.req, 'query', {})
if not short_id and query and 'id' in query:
short_id = query['id']

if short_id:
try:
doc = database.get_document(database_id, collection_id, short_id)
return context.res.redirect(doc['original_url'], 301)
except AppwriteException as e:
if e.code == 404:
return context.res.json({'error': 'URL not found'}, status_code=404)
return context.res.json({'error': str(e)}, status_code=500)

try:
payload = json.loads(context.req.body) if context.req.body else {}
except json.JSONDecodeError:
return context.res.json({'error': 'Invalid JSON payload'}, status_code=400)

if 'short_id' in payload:
short_id = payload['short_id']
if not isinstance(short_id, str) or len(short_id) != 7:
return context.res.json({'error': 'Invalid short_id format'}, status_code=400)
try:
doc = database.get_document(database_id, collection_id, short_id)
return context.res.redirect(doc['original_url'], 301)
except AppwriteException as e:
if e.code == 404:
return context.res.json({'error': 'URL not found'}, status_code=404)
return context.res.json({'error': str(e)}, status_code=500)

if 'url' in payload:
original_url = payload['url'].strip() if isinstance(payload.get('url'), str) else None

if not original_url or not original_url.startswith(('http://', 'https://')):
return context.res.json({'error': 'Invalid URL format. Must start with http:// or https://'}, status_code=400)
if len(original_url) > 2048:
return context.res.json({'error': 'URL too long (max 2048 characters)'}, status_code=400)

short_id = None
# --- (FINAL FIX) Wrap the loop in a try...except block ---
try:
for _ in range(5):
candidate_id = nanoid.generate(size=7)
try:
database.create_document(
database_id,
collection_id,
candidate_id,
{'original_url': original_url}
)
short_id = candidate_id
break
except AppwriteException as e:
if e.code == 409:
continue
raise
except AppwriteException as e:
return context.res.json({'error': f'Database error: {str(e)}'}, status_code=500)

if not short_id:
return context.res.json({'error': 'Failed to generate a unique short ID after multiple attempts.'}, status_code=500)

execution_path = context.req.path
short_url = f"{context.req.scheme}://{context.req.host}{execution_path}/{short_id}"
return context.res.json({'short_url': short_url})

return context.res.json({'error': 'Invalid request.'}, status_code=400)
23 changes: 23 additions & 0 deletions python/url-shortener/src/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
from appwrite.client import Client
from appwrite.services.databases import Databases
from functools import lru_cache

# This is your Appwrite function
# It's executed each time we get a request
@lru_cache(maxsize=1)
def get_database():
# Validate environment variables
required_vars = ["APPWRITE_ENDPOINT", "APPWRITE_PROJECT", "APPWRITE_API_KEY"]
missing_vars = [var for var in required_vars if var not in os.environ]
if missing_vars:
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")

# Initialize the Appwrite client
client = Client()
client.set_endpoint(os.environ["APPWRITE_ENDPOINT"])
client.set_project(os.environ["APPWRITE_PROJECT"])
client.set_key(os.environ["APPWRITE_API_KEY"])

# Initialize the database service
return Databases(client)