Merge pull request 'Rewrite functions, add delete function, etc' (#21) from dev into main

Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
2025-06-04 15:50:46 +01:00
10 changed files with 354 additions and 101 deletions

View File

@@ -32,6 +32,9 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -53,7 +56,7 @@ jobs:
python-version-file: "pyproject.toml"
- name: Install the project
run: uv sync --locked --all-extras --dev
run: uv sync --all-extras --dev
- name: Create requirements.txt
run: |
@@ -66,25 +69,15 @@ jobs:
git config user.name "Gitea CI"
git config user.email "ci@git.theprivateserver.de"
- name: Create release notes
run: |
mkdir release_notes
echo -e "${{ inputs.release_notes }}" >> release_notes/release_notes.md
echo "Release notes:"
cat release_notes/release_notes.md
echo ""
- name: Build and store Docker image
if: ${{ github.event.inputs.docker_release == 'true' }}
- name: Build Changelog
id: build_changelog
uses: https://github.com/mikepenz/release-changelog-builder-action@v5
with:
platform: "gitea"
baseURL: "http://gitea:3000"
configuration: ".gitea/changelog-config.json"
env:
TAG: ${{ github.sha }}
run: |
REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
docker buildx build \
--platform linux/amd64 \
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:latest \
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:${TAG} \
--push .
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Bump version
@@ -98,6 +91,15 @@ jobs:
echo "EOF" >> $GITHUB_ENV
- name: Check version
run: echo ${{ env.VERSION }}
- name: Build and store Docker image
if: ${{ github.event.inputs.docker_release == 'true' }}
run: |
REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
docker buildx build \
--platform linux/amd64 \
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:latest \
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:${{ env.VERSION }} \
--push .
- name: Push changes
uses: ad-m/github-push-action@master
with:
@@ -108,7 +110,7 @@ jobs:
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@master
with:
tag_name: ${{ env.VERSION }}
tag_name: v${{ env.VERSION }}
release_name: Release ${{ env.VERSION }}
body_path: release_notes/release_notes.md
draft: false

View File

@@ -1,5 +1,5 @@
# Use the official Python image as the base image
FROM python:3.11-slim
FROM python:3.13-slim
# Set the working directory in the container
WORKDIR /app
@@ -7,11 +7,14 @@ WORKDIR /app
# Copy the application files into the container
COPY . /app
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Install dependencies with an external pip index
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt \
--extra-index-url https://git.theprivateserver.de/api/packages/KomSuite/pypi/simple/
# Expose the port the app runs on
EXPOSE 5001
# Set the default command to run the application
CMD ["python", "src/app.py"]
# Set the default command to run the application using hypercorn
# run main.py using uv run python
CMD ["python", "main.py"]

View File

@@ -1,5 +1,6 @@
from anilistapi import AnilistAPI
from src.app import app
api = AnilistAPI()
if __name__ == "__main__":
import uvicorn
print(api.get_tags())
uvicorn.run(app, host="0.0.0.0", port=5001)

View File

@@ -9,6 +9,7 @@ dependencies = [
"flask>=3.1.1",
"httpx>=0.28.1",
"httpx-retries>=0.3.2",
"hypercorn>=0.17.3",
"jinja2>=3.1.6",
"komcache>=0.1.0",
"komconfig>=0.2.0",
@@ -16,6 +17,7 @@ dependencies = [
"komsuite-nyaapy>=0.1.0",
"limit>=0.2.3",
"quart>=0.20.0",
"uvicorn>=0.34.2",
]
@@ -23,3 +25,26 @@ dependencies = [
[[tool.uv.index]]
name = "gitea"
url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/simple/"
[tool.bumpversion]
current_version = "0.1.0"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = true
commit = true
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []

View File

@@ -54,6 +54,7 @@ click==8.2.1 \
# via
# flask
# quart
# uvicorn
colorama==0.4.6 ; sys_platform == 'win32' \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
@@ -66,12 +67,33 @@ flask==3.1.1 \
# via
# kompage
# quart
greenlet==3.2.2 \
--hash=sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207 \
--hash=sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4 \
--hash=sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825 \
--hash=sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708 \
--hash=sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763 \
--hash=sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf \
--hash=sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d \
--hash=sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59 \
--hash=sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51 \
--hash=sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5 \
--hash=sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf \
--hash=sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b \
--hash=sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240 \
--hash=sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485 \
--hash=sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8 \
--hash=sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418 \
--hash=sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325 \
--hash=sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421
# via sqlalchemy
h11==0.16.0 \
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
# via
# httpcore
# hypercorn
# uvicorn
# wsproto
h2==4.2.0 \
--hash=sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0 \
@@ -99,7 +121,9 @@ httpx-retries==0.4.0 \
hypercorn==0.17.3 \
--hash=sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547 \
--hash=sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165
# via quart
# via
# kompage
# quart
hyperframe==6.1.0 \
--hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \
--hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08
@@ -124,13 +148,13 @@ jinja2==3.1.6 \
# flask
# kompage
# quart
komcache==0.1.0 \
--hash=sha256:67df7d393d27385d605cb4c92af8a20331a0582d988ac1fcaccd2c6f819b4ff6 \
--hash=sha256:cfb87e7800e9e9ace4454446e001298a4763a2e39f271d16343f6ba6d8596676
komcache==0.1.2 \
--hash=sha256:b474c0f00e72b9e6d9f1fd765b69fbd4bdf0fd37e094692d69a3565b49c7c017 \
--hash=sha256:d56d01a81751113433325a9e25afbb985eff2530fc9b0b7cc3dac960308b4c35
# via kompage
komconfig==0.2.0 \
--hash=sha256:31b1e06e821cfaba8bbc8c077dbb596989a277c33c99096870df4cba39bc0f15 \
--hash=sha256:913bedd6eb38dad81460afed96352f3af7518e9ea76d575b6cf2d190adb4a162
komconfig==0.2.2 \
--hash=sha256:6bfda853ac3ee513380db2df896830099eba8a6d6e0ed1484049839e9dcb5845 \
--hash=sha256:a2b15158aedb2fc8f55c9d8fc15d6bda9791f2b9690e2e066d3010b1f681061e
# via
# komcache
# komgapi
@@ -207,6 +231,10 @@ priority==2.0.0 \
--hash=sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa \
--hash=sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0
# via hypercorn
pymysql==1.1.1 \
--hash=sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c \
--hash=sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0
# via komcache
pyyaml==6.0.2 \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
@@ -249,14 +277,32 @@ sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
typing-extensions==4.13.2 \
--hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
--hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
# via komgapi
sqlalchemy==2.0.41 \
--hash=sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443 \
--hash=sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23 \
--hash=sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576 \
--hash=sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df \
--hash=sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1 \
--hash=sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f \
--hash=sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d \
--hash=sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc \
--hash=sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a \
--hash=sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9
# via komcache
typing-extensions==4.14.0 \
--hash=sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4 \
--hash=sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af
# via
# komgapi
# sqlalchemy
urllib3==2.4.0 \
--hash=sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466 \
--hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813
# via requests
uvicorn==0.34.3 \
--hash=sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885 \
--hash=sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a
# via kompage
werkzeug==3.1.3 \
--hash=sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e \
--hash=sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746

View File

@@ -22,22 +22,23 @@ log.add("application.log", rotation="1 week", retention="1 month")
log.add(sys.stdout)
app = Quart(__name__)
cache = KomCache()
cache.create_table(
"CREATE TABLE IF NOT EXISTS manga_requests (id INTEGER PRIMARY KEY, manga_id INTEGER, grabbed BOOLEAN DEFAULT 0)"
)
settings = KomConfig()
# else:
# cache.create_table(
# "CREATE TABLE IF NOT EXISTS manga_requests (id INT AUTO_INCREMENT PRIMARY KEY, anilist_id INT NOT NULL, grabbed BOOLEAN DEFAULT 0)"
komga = KOMGAPI(
url=settings.komga.url,
username=settings.komga.user,
password=settings.komga.password,
)
komga_series = komga.seriesList()
# komga_series = komga.seriesList()
@limit(90, 60)
@limit(limit=1, every=2)
async def fetch_data(
inputdata: Dict[str, Any], check_downloads: bool = False
) -> List[Dict[str, Any]]:
@@ -67,6 +68,11 @@ async def fetch_data(
"query": REQUESTS_QUERY,
"variables": variables,
},
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "KomPage Searcher API/1.0 (contact: discord-id: 908987973264089139)",
},
)
response.raise_for_status()
@@ -74,14 +80,16 @@ async def fetch_data(
log.debug(f"GraphQL response: {data}")
results: List[Dict[str, Any]] = []
cache = KomCache()
for item in data.get("data", {}).get("Page", {}).get("media", []):
manga = Manga(**item)
in_komga = komga.getSeries(
manga.title.english if manga.title.english else manga.title.romaji
)
requested = cache.fetch_one(
query="SELECT manga_id, grabbed FROM manga_requests WHERE manga_id = ?",
args=(manga.id,),
query="SELECT manga_id, grabbed FROM manga_requests WHERE manga_id = :id",
args={"id": manga.id},
)
komga_request = bool(requested)
results.append(
@@ -126,9 +134,12 @@ async def fetch_data(
log.error(f"Unexpected error in fetch_data: {e}")
return []
@limit(90, 60)
@limit(limit=1, every=2)
async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]:
requested_data: list[dict[str, Any]] = []
cache = KomCache()
for manga_id in data:
async with httpx.AsyncClient() as client:
try:
@@ -158,8 +169,8 @@ async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]:
else manga.title.romaji
)
requested = cache.fetch_one(
query="SELECT grabbed FROM manga_requests WHERE manga_id = ?",
args=(manga.id,),
query="SELECT grabbed FROM manga_requests WHERE manga_id = :id",
args={"id": manga.id},
)
komga_request = bool(requested)
@@ -281,18 +292,28 @@ async def api_search():
@app.route("/", methods=["GET"])
async def index():
return await render_template("index.html", komga_series=komga_series)
return await render_template("index.html") # , komga_series=komga_series)
@app.route("/request", methods=["POST"])
async def log_request():
data = await request.get_json()
log.debug(f"Received request data: {data}")
item = data.get("item")
if item:
data = await fetch_requested_data([item])
if not data:
return jsonify({"status": "failed", "message": "Item not found"}), 404
data = data[0]
title = data.get("title")
image_url = data.get("image")
log.debug(
f"Logging request for item: {item}, title: {title}, image_url: {image_url}"
)
asynccache = KomCache()
asynccache.insert(
"INSERT INTO manga_requests (manga_id) VALUES (?)",
(item,),
f"INSERT INTO manga_requests (manga_id, title, image) VALUES ({item}, '{title}', :image)",
args={"image": image_url},
)
return jsonify({"status": "success"})
return jsonify({"status": "failed"}), 400
@@ -300,21 +321,42 @@ async def log_request():
@app.route("/requests", methods=["GET"])
async def requests_page():
cache = KomCache()
requests = (
cache.fetch_all(query="SELECT manga_id, grabbed FROM manga_requests") or []
cache.fetch_all(
query="SELECT manga_id, title, image grabbed FROM manga_requests"
)
or []
)
entries: List[Dict[str, Any]] = []
req_ids = [req[0] for req in requests]
if req_ids:
entries = await fetch_requested_data(req_ids)
entries = [
{"manga_id": req[0], "title": req[1], "image": req[2]} for req in requests
]
else:
entries = []
data = []
for entry in entries:
data.append(dict(entry))
return await render_template("requests.html", requests=data)
return await render_template("requests.html", requests=entries)
@app.route("/delete", methods=["POST"])
async def delete_request():
# Delete a request from the database. ID is sent after the /delete endpoint, so: /delete/<id>
data = await request.get_json()
item_id = data.get("item")
if item_id:
asynccache = KomCache()
asynccache.query(
"DELETE FROM manga_requests WHERE manga_id = :id", args={"id": item_id}
)
return jsonify({"status": "success"})
return jsonify({"status": "failed"}), 400
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5001)
log.info("Starting Komga Manga Grabber API")
# use hypercorn to run the app
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5001)
# run in test mode

View File

@@ -55,6 +55,7 @@
margin-right: 0.5em;
}
.actions .info:hover {
background-color: #0056b3;
}
@@ -230,4 +231,21 @@ body.nsfw-disabled .image-container.nsfw:hover img {
transform: none !important;
box-shadow: none !important;
z-index: auto !important;
}
.card .delete-button {
position: absolute;
top: 5px;
right: 5px;
background-color: #dc3545;
color: white;
border: none;
padding: 0.5em;
cursor: pointer;
border-radius: 50%;
z-index: 3;
opacity: 1;
}
.card:hover .delete-button {
display: block;
}

View File

@@ -77,7 +77,8 @@
<button class="info" onclick="showInfo({{ result | tojson | safe }})">Info</button>
<!-- if entry has in_komga == true, do not show the request button, else show it -->
{% if not result.in_komga %}
<button class="request" onclick="sendRequest('{{ result.id }}')">Request</button>
<button class="request" onclick="sendRequest({{ result | tojson | safe }})">Request</button>
{% endif %}
</div>
</div>
@@ -113,7 +114,9 @@
fetch("/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ item: item })
body: JSON.stringify({
item: item
})
})
.then(res => res.json())
.then(data => alert(data.status === "success" ? "Request logged!" : "Failed"));
@@ -304,7 +307,7 @@
const card = document.createElement('div');
card.className = `card ${result.in_komga ? 'komga' : ''}`;
card.className += ` ${result.type.toLowerCase()}`;
// card.className += ` ${result.requested ? 'requested' : ''}`;
const imageContainer = document.createElement('div');
imageContainer.className = `image-container ${result.isAdult ? 'nsfw' : ''}`;
@@ -335,14 +338,16 @@
const requestButton = document.createElement('button');
requestButton.textContent = 'Request';
requestButton.className = 'request';
requestButton.onclick = () => sendRequest(result.id);
requestButton.onclick = () => sendRequest(result);
// Disable request button if the result is in Komga
if (result.in_komga) {
requestButton.disabled = true;
}
if (result.requested) {
requestButton.textContent = 'Requested';
requestButton.disabled = true;
card.style.border = '3px solid orange';
}
actions.appendChild(infoButton);

View File

@@ -4,6 +4,7 @@
<head>
<title>Requested Manga</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
@@ -14,12 +15,38 @@
<button class="index" onclick="window.location.href='/'">Back to Index</button>
</div>
<div class="results">
{% if requests %}
{% if results %}
{% for result in results %}
<div class="card {{ result.type | lower }} {% if result.in_komga %}komga{% else %}requested{% endif %}">
<div class="image-container {{ 'nsfw' if result.isAdult else '' }}">
<img src="{{ result.image }}" alt="Cover">
{% if result.isAdult %}
<div class="adult-badge">18+</div>
{% endif %}
</div>
<p>{{ result.title }}</p>
<div class="actions">
<button onclick="showInfo({{ result | tojson | safe }})" class="info">Info</button>
<!-- if entry has in_komga == true, do not show the request button, else show it -->
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% if requests %}
<div class="results">
{% for request in requests %}
<div class="card {{ request.type | lower }} {% if request.in_komga %}komga{% else %}requested{% endif %}"
data-info="{{ request | tojson }}">
<div class="card {{ request.type | lower }} {% if request.in_komga %}komga{% else %}requested{% endif %}">
<div class="image-container {{ 'nsfw' if request.isAdult else '' }}">
<img src="{{ request.image }}" alt="Cover">
{% if request.isAdult %}
<div class="adult-badge">18+</div>
@@ -28,17 +55,19 @@
<p>{{ request.title }}</p>
<div class="actions">
<button class="info">Info</button>
<button onclick="showInfo({{ request | tojson | safe }})" class="info">Info</button>
<button onclick="deleteEntry({{ request.id }})" class="delete-button">Delete</button>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% if not requests %}
<p>No requests found.</p>
{% else %}
<p>No requests found. This may be because the database disconnected. Please refresh using the button, if the message
persists, there are no requests</p>
{% endif %}
<!-- Info Modal -->
<div id="infoModal" class="modal" style="display:none;">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
@@ -54,25 +83,6 @@
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Add click event listeners to all info buttons
document.querySelectorAll('.card .info').forEach(button => {
button.addEventListener('click', (e) => {
// Get the parent card element
const card = e.target.closest('.card');
// Get the data from the data-info attribute
try {
const data = JSON.parse(card.dataset.info);
console.log("Info button clicked, data:", data);
showInfo(data);
} catch (error) {
console.error("Error parsing data:", error);
console.error("Raw data:", card.dataset.info);
}
});
});
});
function showInfo(data) {
console.log("showInfo data:", data);
try {
@@ -101,6 +111,28 @@
closeModal();
}
}
function deleteEntry(entryId) {
fetch(`/delete`, {
method: 'POST',
body: JSON.stringify({ item: entryId }),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.ok) {
alert('Entry deleted successfully!');
window.location.reload(); // Refresh the page to update the list
} else {
alert('Failed to delete the entry.');
}
})
.catch(error => {
console.error('Error deleting entry:', error);
alert('An error occurred while deleting the entry.');
});
}
</script>
</body>

99
uv.lock generated
View File

@@ -127,6 +127,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" },
]
[[package]]
name = "greenlet"
version = "3.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/34/c1/a82edae11d46c0d83481aacaa1e578fea21d94a1ef400afd734d47ad95ad/greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485", size = 185797, upload-time = "2025-05-09T19:47:35.066Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/30/97b49779fff8601af20972a62cc4af0c497c1504dfbb3e93be218e093f21/greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59", size = 269150, upload-time = "2025-05-09T14:50:30.784Z" },
{ url = "https://files.pythonhosted.org/packages/21/30/877245def4220f684bc2e01df1c2e782c164e84b32e07373992f14a2d107/greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf", size = 637381, upload-time = "2025-05-09T15:24:12.893Z" },
{ url = "https://files.pythonhosted.org/packages/8e/16/adf937908e1f913856b5371c1d8bdaef5f58f251d714085abeea73ecc471/greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325", size = 651427, upload-time = "2025-05-09T15:24:51.074Z" },
{ url = "https://files.pythonhosted.org/packages/ad/49/6d79f58fa695b618654adac64e56aff2eeb13344dc28259af8f505662bb1/greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5", size = 645795, upload-time = "2025-05-09T15:29:26.673Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e6/28ed5cb929c6b2f001e96b1d0698c622976cd8f1e41fe7ebc047fa7c6dd4/greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825", size = 648398, upload-time = "2025-05-09T14:53:36.61Z" },
{ url = "https://files.pythonhosted.org/packages/9d/70/b200194e25ae86bc57077f695b6cc47ee3118becf54130c5514456cf8dac/greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d", size = 606795, upload-time = "2025-05-09T14:53:47.039Z" },
{ url = "https://files.pythonhosted.org/packages/f8/c8/ba1def67513a941154ed8f9477ae6e5a03f645be6b507d3930f72ed508d3/greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf", size = 1117976, upload-time = "2025-05-09T15:27:06.542Z" },
{ url = "https://files.pythonhosted.org/packages/c3/30/d0e88c1cfcc1b3331d63c2b54a0a3a4a950ef202fb8b92e772ca714a9221/greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708", size = 1145509, upload-time = "2025-05-09T14:54:02.223Z" },
{ url = "https://files.pythonhosted.org/packages/90/2e/59d6491834b6e289051b252cf4776d16da51c7c6ca6a87ff97e3a50aa0cd/greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421", size = 296023, upload-time = "2025-05-09T14:53:24.157Z" },
{ url = "https://files.pythonhosted.org/packages/65/66/8a73aace5a5335a1cba56d0da71b7bd93e450f17d372c5b7c5fa547557e9/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418", size = 629911, upload-time = "2025-05-09T15:24:22.376Z" },
{ url = "https://files.pythonhosted.org/packages/48/08/c8b8ebac4e0c95dcc68ec99198842e7db53eda4ab3fb0a4e785690883991/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4", size = 635251, upload-time = "2025-05-09T15:24:52.205Z" },
{ url = "https://files.pythonhosted.org/packages/37/26/7db30868f73e86b9125264d2959acabea132b444b88185ba5c462cb8e571/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763", size = 632620, upload-time = "2025-05-09T15:29:28.051Z" },
{ url = "https://files.pythonhosted.org/packages/10/ec/718a3bd56249e729016b0b69bee4adea0dfccf6ca43d147ef3b21edbca16/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b", size = 628851, upload-time = "2025-05-09T14:53:38.472Z" },
{ url = "https://files.pythonhosted.org/packages/9b/9d/d1c79286a76bc62ccdc1387291464af16a4204ea717f24e77b0acd623b99/greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207", size = 593718, upload-time = "2025-05-09T14:53:48.313Z" },
{ url = "https://files.pythonhosted.org/packages/cd/41/96ba2bf948f67b245784cd294b84e3d17933597dffd3acdb367a210d1949/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8", size = 1105752, upload-time = "2025-05-09T15:27:08.217Z" },
{ url = "https://files.pythonhosted.org/packages/68/3b/3b97f9d33c1f2eb081759da62bd6162159db260f602f048bc2f36b4c453e/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51", size = 1125170, upload-time = "2025-05-09T14:54:04.082Z" },
{ url = "https://files.pythonhosted.org/packages/31/df/b7d17d66c8d0f578d2885a3d8f565e9e4725eacc9d3fdc946d0031c055c4/greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240", size = 269899, upload-time = "2025-05-09T14:54:01.581Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -254,26 +279,28 @@ wheels = [
[[package]]
name = "komcache"
version = "0.1.0"
version = "0.1.2"
source = { registry = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/simple/" }
dependencies = [
{ name = "komconfig" },
{ name = "pymysql" },
{ name = "sqlalchemy", extra = ["asyncio"] },
]
sdist = { url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komcache/0.1.0/komcache-0.1.0.tar.gz", hash = "sha256:cfb87e7800e9e9ace4454446e001298a4763a2e39f271d16343f6ba6d8596676" }
sdist = { url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komcache/0.1.2/komcache-0.1.2.tar.gz", hash = "sha256:d56d01a81751113433325a9e25afbb985eff2530fc9b0b7cc3dac960308b4c35" }
wheels = [
{ url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komcache/0.1.0/komcache-0.1.0-py3-none-any.whl", hash = "sha256:67df7d393d27385d605cb4c92af8a20331a0582d988ac1fcaccd2c6f819b4ff6" },
{ url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komcache/0.1.2/komcache-0.1.2-py3-none-any.whl", hash = "sha256:b474c0f00e72b9e6d9f1fd765b69fbd4bdf0fd37e094692d69a3565b49c7c017" },
]
[[package]]
name = "komconfig"
version = "0.2.0"
version = "0.2.2"
source = { registry = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/simple/" }
dependencies = [
{ name = "omegaconf" },
]
sdist = { url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komconfig/0.2.0/komconfig-0.2.0.tar.gz", hash = "sha256:913bedd6eb38dad81460afed96352f3af7518e9ea76d575b6cf2d190adb4a162" }
sdist = { url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komconfig/0.2.2/komconfig-0.2.2.tar.gz", hash = "sha256:a2b15158aedb2fc8f55c9d8fc15d6bda9791f2b9690e2e066d3010b1f681061e" }
wheels = [
{ url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komconfig/0.2.0/komconfig-0.2.0-py3-none-any.whl", hash = "sha256:31b1e06e821cfaba8bbc8c077dbb596989a277c33c99096870df4cba39bc0f15" },
{ url = "https://git.theprivateserver.de/api/packages/KomSuite/pypi/files/komconfig/0.2.2/komconfig-0.2.2-py3-none-any.whl", hash = "sha256:6bfda853ac3ee513380db2df896830099eba8a6d6e0ed1484049839e9dcb5845" },
]
[[package]]
@@ -300,6 +327,7 @@ dependencies = [
{ name = "flask" },
{ name = "httpx" },
{ name = "httpx-retries" },
{ name = "hypercorn" },
{ name = "jinja2" },
{ name = "komcache" },
{ name = "komconfig" },
@@ -307,6 +335,7 @@ dependencies = [
{ name = "komsuite-nyaapy" },
{ name = "limit" },
{ name = "quart" },
{ name = "uvicorn" },
]
[package.metadata]
@@ -315,6 +344,7 @@ requires-dist = [
{ name = "flask", specifier = ">=3.1.1" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "httpx-retries", specifier = ">=0.3.2" },
{ name = "hypercorn", specifier = ">=0.17.3" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "komcache", specifier = ">=0.1.0" },
{ name = "komconfig", specifier = ">=0.2.0" },
@@ -322,6 +352,7 @@ requires-dist = [
{ name = "komsuite-nyaapy", specifier = ">=0.1.0" },
{ name = "limit", specifier = ">=0.2.3" },
{ name = "quart", specifier = ">=0.20.0" },
{ name = "uvicorn", specifier = ">=0.34.2" },
]
[[package]]
@@ -432,6 +463,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/5f/82c8074f7e84978129347c2c6ec8b6c59f3584ff1a20bc3c940a3e061790/priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa", size = 8946, upload-time = "2021-06-27T10:15:03.856Z" },
]
[[package]]
name = "pymysql"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678, upload-time = "2024-05-21T11:03:43.722Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
@@ -517,12 +557,38 @@ wheels = [
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
name = "sqlalchemy"
version = "2.0.41"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
dependencies = [
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
{ url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" },
{ url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" },
{ url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" },
{ url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" },
{ url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" },
{ url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" },
{ url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" },
{ url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" },
]
[package.optional-dependencies]
asyncio = [
{ name = "greenlet" },
]
[[package]]
name = "typing-extensions"
version = "4.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
]
[[package]]
@@ -534,6 +600,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
]
[[package]]
name = "uvicorn"
version = "0.34.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
]
[[package]]
name = "werkzeug"
version = "3.1.3"