Merge remote-tracking branch 'origin/master'
All checks were successful
Build and Publish / build_and_publish (push) Successful in 1m30s

# Conflicts:
#	src/static/index.html
This commit is contained in:
Nexus 2024-07-11 17:09:30 +01:00
commit 7af49cbb84
Signed by: nex
GPG key ID: 0FA334385D0B689F
4 changed files with 72 additions and 20 deletions

View file

@ -5,21 +5,33 @@ on: [push]
jobs: jobs:
build_and_publish: build_and_publish:
runs-on: [ubuntu-latest] runs-on: [ubuntu-latest]
permissions:
contents: read
packages: write
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: git.i-am.nexus/nex/link-elongater
- name: Log into forgejo CR - name: Log into forgejo CR
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: git.i-am.nexus registry: git.i-am.nexus
username: nex username: nex
password: ${{ secrets.CR_TOKEN }} password: ${{ secrets.CR_PASSWORD }}
- name: Build Docker image
run: | - name: Build and push
docker build -t git.i-am.nexus/nex/link-elongater:latest . uses: docker/build-push-action@v5
- name: Push to forgejo CR with:
run: | context: .
docker push -a git.i-am.nexus/nex/link-elongater push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=git.i-am.nexus/nex/link-elongater:master
cache-to: type=inline

View file

@ -7,7 +7,9 @@ from tortoise.contrib.pydantic import pydantic_model_creator
class Redirect(Model): class Redirect(Model):
uuid = fields.UUIDField(primary_key=True, default=uuid.uuid4) uuid = fields.UUIDField(primary_key=True, default=uuid.uuid4)
slug = fields.CharField(max_length=32779, index=False, default=lambda: secrets.token_urlsafe(1024)) slug = fields.CharField(
max_length=32779, index=False, default=lambda: secrets.token_urlsafe(1024)
)
destination = fields.CharField(max_length=8192) destination = fields.CharField(max_length=8192)
created_at = fields.DatetimeField(auto_now_add=True) created_at = fields.DatetimeField(auto_now_add=True)
expires = fields.DatetimeField(null=True) expires = fields.DatetimeField(null=True)

View file

@ -8,7 +8,7 @@ import db
import requests import requests
from fastapi import FastAPI, Request, HTTPException, status, Form from fastapi import FastAPI, Request, HTTPException, status, Form
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from tortoise.contrib.fastapi import RegisterTortoise from tortoise.contrib.fastapi import RegisterTortoise
@ -94,11 +94,12 @@ async def list_redirects():
@app.post("/api/create", response_model=db.RedirectPydantic) @app.post("/api/create", response_model=db.RedirectPydantic)
async def create_redirect( async def create_redirect(
destination: str = Form(...), response: JSONResponse,
expires: typing.Optional[datetime.datetime] = Form(None), destination: str = Form(...),
max_visits: typing.Optional[int] = Form(None), expires: typing.Optional[datetime.datetime] = Form(None),
slug_length: int = Form(2048, gt=0, lte=16389), max_visits: typing.Optional[int] = Form(None),
slug_type: str = Form("urlsafe", regex="^(urlsafe|hex|words|uuid)$"), slug_length: int = Form(2048, gt=0, lte=16389),
slug_type: str = Form("urlsafe", regex="^(urlsafe|hex|words|uuid)$"),
): ):
parsed = urlparse(destination) parsed = urlparse(destination)
if not parsed.scheme or not parsed.netloc: if not parsed.scheme or not parsed.netloc:
@ -106,6 +107,11 @@ async def create_redirect(
elif parsed.scheme not in ["http", "https"]: elif parsed.scheme not in ["http", "https"]:
raise HTTPException(status_code=400, detail="Invalid URL - must be HTTP/HTTPS") raise HTTPException(status_code=400, detail="Invalid URL - must be HTTP/HTTPS")
existing = await db.Redirect.get_or_none(destination=destination)
if existing is not None:
response.status_code = 200
return await db.RedirectPydantic.from_tortoise_orm(existing)
match slug_type: match slug_type:
case "urlsafe": case "urlsafe":
slug = secrets.token_urlsafe(slug_length // 2) slug = secrets.token_urlsafe(slug_length // 2)
@ -130,7 +136,10 @@ async def create_redirect(
while await db.Redirect.get_or_none(slug=slug) is not None: while await db.Redirect.get_or_none(slug=slug) is not None:
slug = secrets.token_urlsafe(slug_length // 2) slug = secrets.token_urlsafe(slug_length // 2)
redirect = await db.Redirect.create(destination=destination, expires=expires, max_visits=max_visits, slug=slug) redirect = await db.Redirect.create(
destination=destination, expires=expires, max_visits=max_visits, slug=slug
)
response.status_code = 201
return await db.RedirectPydantic.from_tortoise_orm(redirect) return await db.RedirectPydantic.from_tortoise_orm(redirect)

View file

@ -5,6 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Link Elongater - Create Redirect</title> <title>Link Elongater - Create Redirect</title>
<script> <script>
function onChange(e) {
let slugLength = parseInt(document.getElementById("slug_length").value);
let netloc = window.location.origin;
const totalLength = netloc.length + 3 + slugLength;
document.getElementById("urllen").textContent = totalLength;
}
function onSubmit(e) { function onSubmit(e) {
e.preventDefault(); e.preventDefault();
let formData = new FormData(e.target); let formData = new FormData(e.target);
@ -44,9 +50,31 @@
"DOMContentLoaded", "DOMContentLoaded",
() => { () => {
document.querySelector("form").addEventListener("submit", onSubmit); document.querySelector("form").addEventListener("submit", onSubmit);
document.getElementById("slug_length").addEventListener("input", onChange);
} }
) )
</script> </script>
<style>
body {
font-family: sans-serif;
}
form {
display: grid;
gap: 0.5em;
}
label {
font-weight: bold;
}
input, select {
width: 100%;
}
button {
width: 100%;
}
#result {
margin-top: 1em;
}
</style>
</head> </head>
<body> <body>
<h1>Create redirect</h1> <h1>Create redirect</h1>
@ -60,10 +88,11 @@
<input type="url" id="destination" name="destination" required/><br/> <input type="url" id="destination" name="destination" required/><br/>
<label for="expires">Expires:</label> <label for="expires">Expires:</label>
<input type="date" id="expires" name="expires"/><br/> <input type="date" id="expires" name="expires"/><br/>
<label for="max_visits">Max visits:</label> <label for="max_visits">Max visits (default: unlimited):</label>
<input type="number" id="max_visits" name="max_visits"/><br/> <input type="number" id="max_visits" name="max_visits"/><br/>
<label for="slug_length">Slug length:</label> <label for="slug_length">Slug length:</label>
<input type="number" id="slug_length" name="slug_length" value="1000" min="2" max="16389"/><br/> <input type="number" id="slug_length" name="slug_length" value="1000" min="2" max="16389"/>
<p>URL length will total to <span id="urllen">a lot of</span> characters.</p><br/>
<label for="slug_type">Slug type:</label> <label for="slug_type">Slug type:</label>
<select id="slug_type" name="slug_type" value="urlsafe"> <select id="slug_type" name="slug_type" value="urlsafe">
<option value="urlsafe">Base64 (urlsafe)</option> <option value="urlsafe">Base64 (urlsafe)</option>