File size: 9,719 Bytes
1ef4e10
17ebb8a
0fb23b8
ae8975a
bc1cd44
e1df50c
bc1cd44
c6fc32c
0fb23b8
 
1ef4e10
 
 
bc1cd44
 
 
1ef4e10
e1df50c
1ef4e10
 
e1df50c
c6fc32c
 
 
 
 
 
 
e1df50c
 
 
0fb23b8
bc1cd44
ae8975a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc1cd44
ae8975a
f7ef7d3
c6fc32c
 
 
f7ef7d3
c6fc32c
e1df50c
 
ae8975a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1df50c
1ef4e10
 
bc1cd44
1ef4e10
0fb23b8
1ef4e10
c8c252f
c6fc32c
 
 
 
1ef4e10
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import gradio as gr
import time
from utils.logger import Logger
from utils.database import get_db
from data.repository.annotator_repo import AnnotatorRepo
from data.repository.annotator_workload_repo import AnnotatorWorkloadRepo
from utils.security import verify_password
from config import conf

log = Logger()


class AuthService:
    """
    Authenticate users against DB and drive Gradio UI states.
    """

    # ───────────── LOGIN ───────────── #
    @staticmethod
    def login(username: str, password: str, session: dict):
        """
        Returns different UI states based on user role:
        - Phase 1 users: Normal annotation interface
        - Phase 2 users: Review interface
        
        Outputs depend on which interface is being used:
        For Phase 1: (message, login_container, dashboard_container, header_welcome, items_state, idx_state, tts_id, filename, sentence, ann_sentence)
        For Phase 2: (message, login_container, dashboard_container, review_dashboard_container, header_welcome, items_state, idx_state, tts_id, filename, sentence, ann_sentence)
        """

        # ---------- اعتبارسنجی ---------- #
        log.info(f"Login attempt: username={username}")

        with get_db() as db:
            repo = AnnotatorRepo(db)
            annotator = repo.get_annotator_by_name(username)

            # ⬇️ توابع کمکی برای تولید خروجی خالی (درصورت خطا)
            def empty_dashboard_outputs_for_ui():  # Renamed and adjusted for UI outputs
                return (
                    [],  # items_state
                    0,  # idx_state
                    "",  # tts_id
                    "",  # filename
                    "",  # sentence
                    "",  # ann_sentence
                )

            # --- کاربر موجود نیست / غیر فعال
            if annotator is None or not annotator.is_active:
                log.warning("Failed login (not found / inactive)")
                return (
                    "❌ Wrong username or password!",  # message
                    gr.update(),  # login_container (no change)
                    gr.update(visible=False),  # dashboard_container (Phase 1)
                    gr.update(visible=False),  # review_dashboard_container (Phase 2)
                    gr.update(), # review_dashboard_page.load_trigger
                    gr.update(value=""),  # header_welcome
                    *empty_dashboard_outputs_for_ui(),
                )

            # --- رمز عبور اشتباه
            if not verify_password(password, annotator.password):
                log.warning("Failed login (bad password)")
                return (
                    "❌ Wrong username or password!",  # message
                    gr.update(),  # login_container (no change)
                    gr.update(visible=False),  # dashboard_container (Phase 1)
                    gr.update(visible=False),  # review_dashboard_container (Phase 2)
                    gr.update(), # review_dashboard_page.load_trigger
                    gr.update(value=""),  # header_welcome
                    *empty_dashboard_outputs_for_ui(),
                )

            # ---------- ورود موفق ---------- #
            session["user_id"] = annotator.id
            session["username"] = annotator.name
            session["is_reviewer"] = annotator.name in conf.REVIEW_MAPPING.values()


            if session["is_reviewer"]:
                log.info(f"User '{username}' is a Phase 2 reviewer")
                return (
                    None,  # 0: message
                    gr.update(visible=False),  # 1: login_container
                    gr.update(visible=False),  # 2: dashboard_container (Phase 1)
                    gr.update(visible=True),   # 3: review_dashboard_container (Phase 2)
                    gr.update(value=time.time()), # 4: review_dashboard_page.load_trigger
                    gr.update(value=f"👋 Welcome, {annotator.name}! (Phase 2 Review Mode)"),  # 5: header_welcome
                    # Dummy updates for dashboard_page components
                    gr.update(value=[]),  # 6: dashboard_page.items_state
                    gr.update(value=0),   # 7: dashboard_page.idx_state
                    gr.update(value=""),  # 8: dashboard_page.tts_id
                    gr.update(value=""),  # 9: dashboard_page.filename
                    gr.update(value=""),  # 10: dashboard_page.sentence
                    gr.update(value=""),  # 11: dashboard_page.ann_sentence
                )
            else:
                # Phase 1 users - existing logic
                log.info(f"User '{username}' is a Phase 1 annotator")

                # بارگذاری داده‌های داشبورد
                workload_repo = AnnotatorWorkloadRepo(db)
                raw_items = workload_repo.get_tts_data_with_annotations(username)

                dashboard_items = [
                    {
                        "id": row["tts_data"].id,
                        "filename": row["tts_data"].filename,
                        "sentence": row["tts_data"].sentence,
                        "annotated_sentence": (
                            row["annotation"].annotated_sentence
                            if row["annotation"] and row["annotation"].annotated_sentence is not None
                            else ""
                        ),
                        "annotated_at": (
                            row["annotation"].annotated_at.isoformat()
                            if row["annotation"] and row["annotation"].annotated_at
                            else ""
                        ),
                        "validated": (
                            row["annotation"].validated
                            if row["annotation"] is not None
                            else False
                        ),
                    }
                    for row in raw_items
                ]

                session["dashboard_items"] = dashboard_items

                # --- Resume Logic: Find first unannotated or default to first/last item ---
                initial_idx = 0
                if dashboard_items:
                    first_unannotated_idx = -1
                    for i, item_data in enumerate(dashboard_items):
                        if not item_data["annotated_sentence"]:  # Check if annotated_sentence is empty
                            first_unannotated_idx = i
                            break
                    
                    if first_unannotated_idx != -1:
                        initial_idx = first_unannotated_idx
                        log.info(f"User '{username}' resuming at first unannotated item, index: {initial_idx} (ID: {dashboard_items[initial_idx]['id']})")
                    else:  # All items are annotated or list is empty
                        initial_idx = len(dashboard_items) - 1 if dashboard_items else 0 # Go to the last item if all annotated, else 0
                        if dashboard_items:
                            log.info(f"User '{username}' has all items annotated or list is empty, starting at item index: {initial_idx} (ID: {dashboard_items[initial_idx]['id']})")
                else: # No items assigned
                    log.info(f"User '{username}' has no items assigned, starting at index 0.")

                # مقداردهی فیلدهای رکورد بر اساس initial_idx
                if dashboard_items: # Check if list is not empty and initial_idx is valid
                    current_item_for_ui = dashboard_items[initial_idx]
                    first_vals_for_ui = (
                        current_item_for_ui["id"],
                        current_item_for_ui["filename"],
                        current_item_for_ui["sentence"],
                        current_item_for_ui["annotated_sentence"],
                    )
                else:
                    first_vals_for_ui = ("", "", "", "") # id, filename, sentence, ann_sentence

                log.info(f"User '{username}' logged in successfully. Initial index set to: {initial_idx}")

                # ---------- خروجی نهایی برای Gradio ---------- #
                return (
                    None,  # 0: پیام خطا وجود ندارد
                    gr.update(visible=False),  # 1: فرم لاگین را مخفی کن
                    gr.update(visible=True),  # 2: داشبورد را نشان بده (Phase 1)
                    gr.update(visible=False), # 3: review dashboard را مخفی کن (Phase 2)
                    gr.update(), # 4: review_dashboard_page.load_trigger (no change)
                    gr.update(value=f"👋 Welcome, {annotator.name}!"),  # 5
                    dashboard_items,  # 6: items_state
                    initial_idx,  # 7: idx_state
                    *first_vals_for_ui,  # 8-11: چهار فیلد نخست برای UI
                )

    # ───────────── LOGOUT ───────────── #
    @staticmethod
    def logout(session: dict):
        username = session.get("username", "unknown")
        session.clear()
        log.info(f"User '{username}' logged out.")
        return (
            gr.update(visible=True),   # 1  → login_page.container
            gr.update(visible=False),  # 2  → dashboard_page.container (Phase 1)
            gr.update(visible=False),  # 3  → review_dashboard_page.container (Phase 2)
            gr.update(value=""),       # 4  → self.welcome
            gr.update(value=""),       # 5  → login_page.message
        )