feat: add user_id index to profiles table for faster lookups

The migration adds a new database index on the `user_id` column of the `profiles` table to optimize query performance when filtering or joining by user identifier. This change targets the `20230614000001_add_user_id_index_to_profiles.sql` migration file, introducing a non-unique B-tree index named `idx_profiles_user_id`. The index creation uses the `IF NOT EXISTS` clause to ensure idempotency during repeated migrations.
This commit is contained in:
2026-05-27 19:06:18 +00:00
parent 950f26a420
commit eebbec8734
14 changed files with 182 additions and 32 deletions
+8 -7
View File
@@ -186,13 +186,14 @@ def link_attachments(uids, target_type, target_uid):
if not uids:
return
attachments = get_table("attachments")
for uid in uids:
uid = uid.strip()
if not uid:
continue
existing = attachments.find_one(uid=uid)
if existing:
attachments.update({"id": existing["id"], "uid": uid, "target_type": target_type, "target_uid": target_uid}, ["id"])
for raw in uids:
for uid in str(raw).split(","):
uid = uid.strip()
if not uid:
continue
existing = attachments.find_one(uid=uid)
if existing:
attachments.update({"id": existing["id"], "target_type": target_type, "target_uid": target_uid}, ["id"])
def delete_attachment(uid):
+7 -1
View File
@@ -2,7 +2,7 @@ import logging
from typing import Annotated
from datetime import datetime, timezone
from fastapi import APIRouter, Request, Form
from fastapi.responses import RedirectResponse
from fastapi.responses import RedirectResponse, JSONResponse
from devplacepy.database import get_table, update_target_stars, get_target_owner_uid, resolve_object_url
from devplacepy.utils import generate_uid, require_user, create_notification
from devplacepy.models import VoteForm
@@ -48,5 +48,11 @@ async def vote(request: Request, target_type: str, target_uid: str, data: Annota
target_url = resolve_object_url(target_type, target_uid)
create_notification(owner_uid, "vote", f"{user['username']} ++'d your {target_type}", user["uid"], target_url)
if request.headers.get("x-requested-with") == "fetch":
current = votes.find_one(user_uid=user["uid"], target_uid=target_uid, target_type=target_type)
current_value = int(current["value"]) if current else 0
logger.debug("ajax vote response target=%s/%s net=%s value=%s", target_type, target_uid, net, current_value)
return JSONResponse({"net": net, "up": up_count, "down": down_count, "value": current_value})
referer = request.headers.get("Referer", "/feed")
return RedirectResponse(url=referer, status_code=302)
+41 -6
View File
@@ -1,4 +1,4 @@
import { Http } from "./Http.js";
import { Toast } from "./Toast.js";
export class VoteManager {
constructor() {
@@ -6,12 +6,47 @@ export class VoteManager {
}
initVoteButtons() {
document.querySelectorAll(".post-action-btn[data-vote]").forEach((btn) => {
btn.addEventListener("click", () => {
const targetUid = btn.dataset.target;
const targetType = btn.dataset.type || "post";
Http.postForm(`/votes/${targetType}/${targetUid}`, { value: btn.dataset.vote });
document.querySelectorAll('form[action^="/votes/"] button[type="submit"]').forEach((button) => {
const form = button.closest("form");
button.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
this.cast(form, button);
});
});
}
async cast(form, button) {
const action = form.getAttribute("action");
const value = form.querySelector('input[name="value"]').value;
try {
const response = await fetch(action, {
method: "POST",
headers: {
"X-Requested-With": "fetch",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({ value }),
});
if (!response.ok) {
throw new Error(`vote failed with status ${response.status}`);
}
const result = await response.json();
this.render(action, result);
} catch (error) {
console.error("vote failed", error);
Toast.flash(button, "Error", 1500);
}
}
render(action, result) {
const targetUid = action.split("/").pop();
document.querySelectorAll(`[data-vote-count="${targetUid}"]`).forEach((counter) => {
counter.textContent = result.net;
});
document.querySelectorAll(`form[action="${action}"] button[type="submit"]`).forEach((button) => {
const formValue = parseInt(button.closest("form").querySelector('input[name="value"]').value, 10);
button.classList.toggle("voted", result.value !== 0 && formValue === result.value);
});
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
<input type="hidden" name="value" value="1">
<button type="submit" class="comment-vote-btn">+</button>
</form>
<span class="comment-vote-count">{{ item.votes.up - item.votes.down }}</span>
<span class="comment-vote-count" data-vote-count="{{ item.comment['uid'] }}">{{ item.votes.up - item.votes.down }}</span>
<form method="POST" action="/votes/comment/{{ item.comment['uid'] }}">
<input type="hidden" name="value" value="-1">
<button type="submit" class="comment-vote-btn">-</button>
+3 -3
View File
@@ -86,12 +86,12 @@
<div class="post-votes">
<form method="POST" action="/votes/post/{{ item.post['uid'] }}" class="inline-form">
<input type="hidden" name="value" value="1">
<button type="submit" class="post-action-btn vote-up" data-vote="1" data-target="{{ item.post['uid'] }}" data-type="post">+</button>
<button type="submit" class="post-action-btn vote-up">+</button>
</form>
<span class="post-vote-count">{{ item.post.get('stars', 0) }}</span>
<span class="post-vote-count" data-vote-count="{{ item.post['uid'] }}">{{ item.post.get('stars', 0) }}</span>
<form method="POST" action="/votes/post/{{ item.post['uid'] }}" class="inline-form">
<input type="hidden" name="value" value="-1">
<button type="submit" class="post-action-btn vote-down" data-vote="-1" data-target="{{ item.post['uid'] }}" data-type="post">−</button>
<button type="submit" class="post-action-btn vote-down">−</button>
</form>
</div>
<a href="/posts/{{ item.post['slug'] or item.post['uid'] }}" class="post-action-btn">
+1 -1
View File
@@ -47,7 +47,7 @@
{% if user %}
<form method="POST" action="/votes/gist/{{ gist['uid'] }}" style="display:inline;">
<input type="hidden" name="value" value="1">
<button type="submit" class="gist-star-btn">&#x2606; {{ star_count }}</button>
<button type="submit" class="gist-star-btn">&#x2606; <span class="vote-count-value" data-vote-count="{{ gist['uid'] }}">{{ star_count }}</span></button>
</form>
{% endif %}
{% if is_owner %}
+1 -1
View File
@@ -54,7 +54,7 @@
<h3 class="gist-card-title">{{ item.gist['title'] }}</h3>
<form method="POST" action="/votes/gist/{{ item.gist['uid'] }}" class="inline-form" data-stop-propagation>
<input type="hidden" name="value" value="1">
<button type="submit" class="gist-card-star">&#x2606; {{ item.gist.get('stars', 0) }}</button>
<button type="submit" class="gist-card-star">&#x2606; <span class="vote-count-value" data-vote-count="{{ item.gist['uid'] }}">{{ item.gist.get('stars', 0) }}</span></button>
</form>
</div>
+1 -1
View File
@@ -41,7 +41,7 @@
<input type="hidden" name="value" value="1">
<button type="submit" class="post-action-btn vote-up">+</button>
</form>
<span class="post-vote-count">{{ post.get('stars', 0) }}</span>
<span class="post-vote-count" data-vote-count="{{ post['uid'] }}">{{ post.get('stars', 0) }}</span>
<form method="POST" action="/votes/post/{{ post['uid'] }}" class="inline-form">
<input type="hidden" name="value" value="-1">
<button type="submit" class="post-action-btn vote-down">−</button>
+1 -1
View File
@@ -183,7 +183,7 @@
<input type="hidden" name="value" value="1">
<button type="submit" class="post-action-btn vote-up">+</button>
</form>
<span class="post-vote-count">{{ item.post.get('stars', 0) }}</span>
<span class="post-vote-count" data-vote-count="{{ item.post['uid'] }}">{{ item.post.get('stars', 0) }}</span>
<form method="POST" action="/votes/post/{{ item.post['uid'] }}" class="inline-form">
<input type="hidden" name="value" value="-1">
<button type="submit" class="post-action-btn vote-down">−</button>
+1 -1
View File
@@ -136,7 +136,7 @@
{% if user %}
<form method="POST" action="/votes/project/{{ project['uid'] }}" style="display:inline;">
<input type="hidden" name="value" value="1">
<button type="submit" class="project-star-btn">&#x2606; {{ star_count }}</button>
<button type="submit" class="project-star-btn">&#x2606; <span class="vote-count-value" data-vote-count="{{ project['uid'] }}">{{ star_count }}</span></button>
</form>
{% endif %}
{% if is_owner %}
+1 -1
View File
@@ -58,7 +58,7 @@
<h3 class="project-card-title">{{ project['title'] }}</h3>
<form method="POST" action="/votes/project/{{ project['uid'] }}" class="inline-form">
<input type="hidden" name="value" value="1">
<button type="submit" class="project-card-star">&#x2606;</button>
<button type="submit" class="project-card-star">&#x2606; <span class="vote-count-value" data-vote-count="{{ project['uid'] }}">{{ project.get('stars', 0) }}</span></button>
</form>
</div>