File size: 3,056 Bytes
89a70e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""
Niches people couldn't find in data/sectors.ts and described in their own words.

One Firestore doc per distinct niche (keyed by its normalised text), so repeat
requests add up instead of piling up as duplicates. Admins read the list in the
console to decide which niches to add. firestore.rules' default deny keeps the
collection backend-only.
"""

import re

from firebase_admin import firestore

COLLECTION = "niche_requests"
STATUSES = {"open", "added", "dismissed"}
MAX_USERS_TRACKED = 1000  # beyond this the count stops rising; plenty to rank by


def niche_key(text: str) -> str:
    """'School  Stationery!' and 'school stationery' are the same request."""
    return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:100]


def record(uid: str, text: str, sector_id: str = "", subsector_id: str = "") -> str:
    key = niche_key(text)
    if not key:
        raise ValueError("Niche text has no letters or numbers.")
    db = firestore.client()
    ref = db.collection(COLLECTION).document(key)

    @firestore.transactional
    def _upsert(tx):
        snap = ref.get(transaction=tx)
        d = snap.to_dict() if snap.exists else {}
        uids = list(d.get("uids", []))
        if uid not in uids and len(uids) < MAX_USERS_TRACKED:
            uids.append(uid)
        sectors = list(d.get("sectors", []))
        where = "/".join(p for p in (sector_id, subsector_id) if p)
        if where and where not in sectors:
            sectors.append(where)
        update = {
            "text": " ".join(text.split()),
            "uids": uids,
            "userCount": len(uids),
            "sectors": sectors[-20:],
            "status": d.get("status", "open"),
            "lastAt": firestore.SERVER_TIMESTAMP,
        }
        if not snap.exists:
            update["firstAt"] = firestore.SERVER_TIMESTAMP
        tx.set(ref, update, merge=True)

    _upsert(db.transaction())
    return key


def list_requests(status: str | None = None) -> list[dict]:
    """Most-requested first. Never returns who asked, only how many."""
    out = []
    for doc in firestore.client().collection(COLLECTION).stream():
        d = doc.to_dict()
        if status and d.get("status", "open") != status:
            continue
        out.append({
            "id": doc.id,
            "text": d.get("text", ""),
            "userCount": d.get("userCount", 0),
            "sectors": d.get("sectors", []),
            "status": d.get("status", "open"),
            "firstAt": str(d.get("firstAt", "")),
            "lastAt": str(d.get("lastAt", "")),
        })
    return sorted(out, key=lambda r: (-r["userCount"], r["text"]))


def set_status(key: str, status: str) -> str:
    """Returns the niche text, for the audit trail."""
    if status not in STATUSES:
        raise ValueError(f"status must be one of {sorted(STATUSES)}")
    ref = firestore.client().collection(COLLECTION).document(key)
    snap = ref.get()
    if not snap.exists:
        raise KeyError(key)
    ref.update({"status": status})
    return snap.to_dict().get("text", key)