add wiki-user-search tool

This commit is contained in:
Jörg Thalheim 2025-06-19 15:33:33 +02:00
parent f48e28fd44
commit ecdbd13bc8
6 changed files with 198 additions and 0 deletions

View File

@ -36,6 +36,30 @@ $ ls -la /tmp/restore/var/lib/mediawiki-uploads/
$ umount /tmp/restore/ $ umount /tmp/restore/
``` ```
## Searching for wiki users (wiki admins only)
The `wiki-user-search` tool allows administrators to search for MediaWiki users
by username, email, or real name. Since it connects to PostgreSQL, it must be
run as the `postgres` user:
```bash
# Search by username
$ sudo -u postgres wiki-user-search "admin"
# Search by email domain
$ sudo -u postgres wiki-user-search "@example.com"
# Search by real name
$ sudo -u postgres wiki-user-search "John Smith"
# Limit results (default is 10)
$ sudo -u postgres wiki-user-search "test" -l 20
```
The tool displays results in a table format showing Username, Email, and Real
Name. It uses fuzzy matching with case-insensitive search and prioritizes exact
matches over partial matches.
## Applying terraform ## Applying terraform
Updating hetzner ssh keys: Updating hetzner ssh keys:

View File

@ -46,6 +46,7 @@
... ...
}: }:
{ {
packages = import ./pkgs { inherit pkgs; };
checks = checks =
let let

View File

@ -50,6 +50,10 @@ in
}; };
config = { config = {
environment.systemPackages = [
(pkgs.python3.pkgs.callPackage ../../pkgs/wiki-user-search { })
];
services.mediawiki = { services.mediawiki = {
name = "NixOS Wiki"; name = "NixOS Wiki";
enable = true; enable = true;

5
pkgs/default.nix Normal file
View File

@ -0,0 +1,5 @@
{ pkgs }:
{
wiki-user-search = pkgs.python3.pkgs.callPackage ./wiki-user-search { };
}

View File

@ -0,0 +1,32 @@
{
lib,
psycopg2,
buildPythonApplication,
}:
buildPythonApplication {
pname = "wiki-user-search";
version = "1.0.0";
src = ./.;
format = "other";
propagatedBuildInputs = [ psycopg2 ];
installPhase = ''
runHook preInstall
install -Dm755 wiki-user-search.py $out/bin/wiki-user-search
runHook postInstall
'';
meta = with lib; {
description = "MediaWiki user search tool with fuzzy matching";
homepage = "https://github.com/NixOS/nixos-wiki-infra";
license = licenses.mit;
maintainers = [ ];
mainProgram = "wiki-user-search";
};
}

View File

@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
MediaWiki User Search Tool
Fuzzy search for users by username, real name, or email
"""
import argparse
import psycopg2
import sys
from typing import List, Tuple
def search_users(search_term: str, limit: int = 10) -> List[Tuple[str, str, str]]:
"""
Search for users using fuzzy matching on username, real name, or email.
Returns list of (username, email, real_name) tuples.
"""
# Connect via Unix socket
try:
conn = psycopg2.connect(
host="/run/postgresql", dbname="mediawiki", user="postgres"
)
except psycopg2.Error as e:
print(f"Error connecting to database: {e}", file=sys.stderr)
sys.exit(1)
cur = conn.cursor()
# Use PostgreSQL's ILIKE for case-insensitive pattern matching
query = """
SELECT DISTINCT
user_name,
user_email,
user_real_name,
CASE
WHEN LOWER(user_name) = LOWER(%s) THEN 1
WHEN LOWER(user_email) = LOWER(%s) THEN 1
WHEN LOWER(user_real_name) = LOWER(%s) THEN 1
WHEN LOWER(user_name) LIKE LOWER(%s) THEN 2
WHEN LOWER(user_email) LIKE LOWER(%s) THEN 2
WHEN LOWER(user_real_name) LIKE LOWER(%s) THEN 2
ELSE 3
END as match_score
FROM mediawiki."user"
WHERE
user_name ILIKE %s
OR user_email ILIKE %s
OR user_real_name ILIKE %s
ORDER BY match_score, user_name
LIMIT %s
"""
# Create search pattern with wildcards
search_pattern = f"%{search_term}%"
# Execute query with all parameters
cur.execute(
query,
(
search_term,
search_term,
search_term, # Exact match checks
search_pattern,
search_pattern,
search_pattern, # Pattern match checks
search_pattern,
search_pattern,
search_pattern, # WHERE clause
limit,
),
)
results = []
for row in cur.fetchall():
username, email, real_name, _ = row
results.append((username or "", email or "", real_name or ""))
cur.close()
conn.close()
return results
def print_table(results: List[Tuple[str, str, str]]) -> None:
"""Print search results as a formatted table."""
if not results:
print("No users found matching the search term.")
return
# Calculate column widths
username_width = max(len("Username"), max(len(r[0]) for r in results))
email_width = max(len("Email"), max(len(r[1]) for r in results))
real_name_width = max(len("Real Name"), max(len(r[2]) for r in results))
# Print header
print(
f"{'Username':<{username_width}} | {'Email':<{email_width}} | {'Real Name':<{real_name_width}}"
)
print(f"{'-' * username_width}-+-{'-' * email_width}-+-{'-' * real_name_width}")
# Print rows
for username, email, real_name in results:
print(
f"{username:<{username_width}} | {email:<{email_width}} | {real_name:<{real_name_width}}"
)
def main():
parser = argparse.ArgumentParser(
description="Search MediaWiki users by username, email, or real name"
)
parser.add_argument(
"search_term", help="Search term to match against username, email, or real name"
)
parser.add_argument(
"--limit",
"-l",
type=int,
default=10,
help="Maximum number of results (default: 10)",
)
args = parser.parse_args()
# Search for users
results = search_users(args.search_term, args.limit)
# Print results as table
print_table(results)
if __name__ == "__main__":
main()