DeepLearning101 commited on
Commit
0ad419c
·
verified ·
1 Parent(s): b634ffe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -89
app.py CHANGED
@@ -5,7 +5,7 @@ import requests
5
  from supabase import create_client, Client
6
  from datetime import datetime, timedelta, timezone
7
 
8
- # 設定台北時區
9
  TAIPEI_TZ = timezone(timedelta(hours=8))
10
 
11
  # --- 設定 ---
@@ -13,12 +13,13 @@ SUPABASE_URL = os.getenv("SUPABASE_URL")
13
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
14
  GAS_MAIL_URL = os.getenv("GAS_MAIL_URL")
15
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
16
- # ⚠️ 請確認這是您 Space A 的正確網址 (結尾不要有斜線)
17
  PUBLIC_SPACE_URL = "https://deeplearning101-ciecietaipei.hf.space"
18
 
 
 
 
 
19
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
20
- ADMIN_USER = os.getenv("ADMIN_USER")
21
- ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")
22
 
23
  def get_bookings():
24
  res = supabase.table("bookings").select("*").order("created_at", desc=True).execute()
@@ -37,20 +38,16 @@ def send_confirmation_hybrid(booking_id):
37
  email, user_id = booking.get('email'), booking.get('user_id')
38
  log_msg = ""
39
 
40
- # 產生確認連結
41
  confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
42
  cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
43
 
44
- # ---------------------------------------------------------
45
- # 1. Email 發送 (改良版:使用 Table 排版,解決跑版問題)
46
- # ---------------------------------------------------------
47
  if email and "@" in email:
48
  try:
49
  html = f"""
50
  <div style="padding: 20px; background: #111; color: #d4af37; border-radius: 10px; max-width: 600px; margin: 0 auto; font-family: sans-serif;">
51
  <h2 style="border-bottom: 1px solid #d4af37; padding-bottom: 15px; text-align: center; letter-spacing: 2px;">Cié Cié Taipei</h2>
52
  <p style="font-size: 16px; margin-top: 20px; color: #eee;">{booking['name']} 您好,已為您保留座位:</p>
53
-
54
  <div style="background: #222; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #d4af37;">
55
  <ul style="color: #eee; list-style: none; padding: 0; margin: 0; line-height: 2;">
56
  <li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
@@ -59,7 +56,6 @@ def send_confirmation_hybrid(booking_id):
59
  <li>📝 備註:{booking.get('remarks') or '無'}</li>
60
  </ul>
61
  </div>
62
-
63
  <table width="100%" border="0" cellspacing="0" cellpadding="0">
64
  <tr>
65
  <td align="center">
@@ -68,7 +64,6 @@ def send_confirmation_hybrid(booking_id):
68
  </td>
69
  </tr>
70
  </table>
71
-
72
  <hr style="border: 0; border-top: 1px solid #333; margin-top: 30px;">
73
  <p style="color: #666; font-size: 12px; text-align: center;">如需更改,請直接回覆此信件。</p>
74
  </div>
@@ -78,115 +73,98 @@ def send_confirmation_hybrid(booking_id):
78
  except Exception as e:
79
  log_msg += f"⚠️ Email 失敗: {e} "
80
 
81
- # ---------------------------------------------------------
82
- # 2. LINE 發送 (升級版:Flex Message 卡片按鈕)
83
- # ---------------------------------------------------------
84
  if not LINE_ACCESS_TOKEN:
85
  log_msg += "| ⚠️ 未設定 LINE_ACCESS_TOKEN"
86
  elif not user_id or len(str(user_id)) < 10:
87
  log_msg += "| ℹ️ 無 LINE ID"
88
  else:
89
  try:
90
- # 定義 Flex Message 內容
91
  flex_payload = {
92
  "type": "flex",
93
  "altText": "您有一筆訂位確認通知",
94
  "contents": {
95
  "type": "bubble",
96
  "styles": { "header": {"backgroundColor": "#222222"}, "body": {"backgroundColor": "#2c2c2c"}, "footer": {"backgroundColor": "#2c2c2c"} },
97
- "header": {
98
- "type": "box",
99
- "layout": "vertical",
100
- "contents": [
101
- {"type": "text", "text": "Cié Cié Taipei", "color": "#d4af37", "weight": "bold", "size": "xl", "align": "center"}
102
- ]
103
- },
104
  "body": {
105
- "type": "box",
106
- "layout": "vertical",
107
  "contents": [
108
  {"type": "text", "text": "訂位確認", "weight": "bold", "size": "lg", "color": "#ffffff", "align": "center", "margin": "md"},
109
  {"type": "separator", "margin": "lg", "color": "#444444"},
110
  {"type": "box", "layout": "vertical", "margin": "lg", "spacing": "sm", "contents": [
111
- {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [
112
- {"type": "text", "text": "姓名", "color": "#aaaaaa", "size": "sm", "flex": 2},
113
- {"type": "text", "text": f"{booking['name']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4}
114
- ]},
115
- {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [
116
- {"type": "text", "text": "日期", "color": "#aaaaaa", "size": "sm", "flex": 2},
117
- {"type": "text", "text": f"{booking['date']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4}
118
- ]},
119
- {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [
120
- {"type": "text", "text": "時間", "color": "#aaaaaa", "size": "sm", "flex": 2},
121
- {"type": "text", "text": f"{booking['time']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4}
122
- ]},
123
- {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [
124
- {"type": "text", "text": "人數", "color": "#aaaaaa", "size": "sm", "flex": 2},
125
- {"type": "text", "text": f"{booking['pax']} 位", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4}
126
- ]}
127
  ]}
128
  ]
129
  },
130
  "footer": {
131
- "type": "box",
132
- "layout": "vertical",
133
- "spacing": "sm",
134
  "contents": [
135
- {
136
- "type": "button",
137
- "style": "primary",
138
- "color": "#d4af37",
139
- "height": "sm",
140
- "action": {
141
- "type": "uri",
142
- "label": "✅ 確認出席",
143
- "uri": confirm_link
144
- }
145
- },
146
- {
147
- "type": "button",
148
- "style": "secondary",
149
- "height": "sm",
150
- "color": "#aaaaaa",
151
- "action": {
152
- "type": "uri",
153
- "label": "🚫 取消訂位",
154
- "uri": cancel_link
155
- }
156
- }
157
  ]
158
  }
159
  }
160
  }
161
-
162
- # 發送請求
163
- r = requests.post(
164
- "https://api.line.me/v2/bot/message/push",
165
- headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"},
166
- json={"to": user_id, "messages": [flex_payload]}
167
- )
168
-
169
- if r.status_code == 200:
170
- log_msg += "| ✅ LINE Flex ok"
171
- else:
172
- log_msg += f"| ❌ LINE 錯誤: {r.text}"
173
- except Exception as e:
174
- log_msg += f"| ❌ LINE 例外: {e}"
175
 
176
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
177
  return log_msg
178
  except Exception as e: return f"❌ Error: {str(e)}"
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  with gr.Blocks(title="Admin") as demo:
181
- gr.Markdown("# 🍷 訂位管理後台")
182
- refresh_btn = gr.Button("🔄 重新整理")
183
- booking_table = gr.Dataframe(interactive=False)
184
- with gr.Row():
185
- id_input = gr.Number(label="訂單 ID", precision=0)
186
- action_btn = gr.Button("📧 發送確認信 (Hybrid)", variant="primary")
187
- log_output = gr.Textbox(label="結果")
188
- refresh_btn.click(get_bookings, outputs=booking_table)
189
- action_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  if __name__ == "__main__":
192
- demo.launch(auth=(ADMIN_USER or "admin", ADMIN_PASSWORD or "123456"))
 
 
5
  from supabase import create_client, Client
6
  from datetime import datetime, timedelta, timezone
7
 
8
+ # ✅ 補回:設定台北時區
9
  TAIPEI_TZ = timezone(timedelta(hours=8))
10
 
11
  # --- 設定 ---
 
13
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
14
  GAS_MAIL_URL = os.getenv("GAS_MAIL_URL")
15
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
 
16
  PUBLIC_SPACE_URL = "https://deeplearning101-ciecietaipei.hf.space"
17
 
18
+ # 取得帳密 (若沒設定則使用預設值)
19
+ REAL_ADMIN_USER = os.getenv("ADMIN_USER") or "Deep Learning 101"
20
+ REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11"
21
+
22
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
 
 
23
 
24
  def get_bookings():
25
  res = supabase.table("bookings").select("*").order("created_at", desc=True).execute()
 
38
  email, user_id = booking.get('email'), booking.get('user_id')
39
  log_msg = ""
40
 
 
41
  confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
42
  cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
43
 
44
+ # 1. Email 發送
 
 
45
  if email and "@" in email:
46
  try:
47
  html = f"""
48
  <div style="padding: 20px; background: #111; color: #d4af37; border-radius: 10px; max-width: 600px; margin: 0 auto; font-family: sans-serif;">
49
  <h2 style="border-bottom: 1px solid #d4af37; padding-bottom: 15px; text-align: center; letter-spacing: 2px;">Cié Cié Taipei</h2>
50
  <p style="font-size: 16px; margin-top: 20px; color: #eee;">{booking['name']} 您好,已為您保留座位:</p>
 
51
  <div style="background: #222; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #d4af37;">
52
  <ul style="color: #eee; list-style: none; padding: 0; margin: 0; line-height: 2;">
53
  <li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
 
56
  <li>📝 備註:{booking.get('remarks') or '無'}</li>
57
  </ul>
58
  </div>
 
59
  <table width="100%" border="0" cellspacing="0" cellpadding="0">
60
  <tr>
61
  <td align="center">
 
64
  </td>
65
  </tr>
66
  </table>
 
67
  <hr style="border: 0; border-top: 1px solid #333; margin-top: 30px;">
68
  <p style="color: #666; font-size: 12px; text-align: center;">如需更改,請直接回覆此信件。</p>
69
  </div>
 
73
  except Exception as e:
74
  log_msg += f"⚠️ Email 失敗: {e} "
75
 
76
+ # 2. LINE 發送
 
 
77
  if not LINE_ACCESS_TOKEN:
78
  log_msg += "| ⚠️ 未設定 LINE_ACCESS_TOKEN"
79
  elif not user_id or len(str(user_id)) < 10:
80
  log_msg += "| ℹ️ 無 LINE ID"
81
  else:
82
  try:
 
83
  flex_payload = {
84
  "type": "flex",
85
  "altText": "您有一筆訂位確認通知",
86
  "contents": {
87
  "type": "bubble",
88
  "styles": { "header": {"backgroundColor": "#222222"}, "body": {"backgroundColor": "#2c2c2c"}, "footer": {"backgroundColor": "#2c2c2c"} },
89
+ "header": { "type": "box", "layout": "vertical", "contents": [ {"type": "text", "text": "Cié Cié Taipei", "color": "#d4af37", "weight": "bold", "size": "xl", "align": "center"} ] },
 
 
 
 
 
 
90
  "body": {
91
+ "type": "box", "layout": "vertical",
 
92
  "contents": [
93
  {"type": "text", "text": "訂位確認", "weight": "bold", "size": "lg", "color": "#ffffff", "align": "center", "margin": "md"},
94
  {"type": "separator", "margin": "lg", "color": "#444444"},
95
  {"type": "box", "layout": "vertical", "margin": "lg", "spacing": "sm", "contents": [
96
+ {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [ {"type": "text", "text": "姓名", "color": "#aaaaaa", "size": "sm", "flex": 2}, {"type": "text", "text": f"{booking['name']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4} ]},
97
+ {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [ {"type": "text", "text": "日期", "color": "#aaaaaa", "size": "sm", "flex": 2}, {"type": "text", "text": f"{booking['date']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4} ]},
98
+ {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [ {"type": "text", "text": "時間", "color": "#aaaaaa", "size": "sm", "flex": 2}, {"type": "text", "text": f"{booking['time']}", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4} ]},
99
+ {"type": "box", "layout": "baseline", "spacing": "sm", "contents": [ {"type": "text", "text": "人數", "color": "#aaaaaa", "size": "sm", "flex": 2}, {"type": "text", "text": f"{booking['pax']} 位", "wrap": True, "color": "#ffffff", "size": "sm", "flex": 4} ]}
 
 
 
 
 
 
 
 
 
 
 
 
100
  ]}
101
  ]
102
  },
103
  "footer": {
104
+ "type": "box", "layout": "vertical", "spacing": "sm",
 
 
105
  "contents": [
106
+ { "type": "button", "style": "primary", "color": "#d4af37", "height": "sm", "action": { "type": "uri", "label": "✅ 確認出席", "uri": confirm_link } },
107
+ { "type": "button", "style": "secondary", "height": "sm", "color": "#aaaaaa", "action": { "type": "uri", "label": "🚫 取消訂位", "uri": cancel_link } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  ]
109
  }
110
  }
111
  }
112
+ r = requests.post("https://api.line.me/v2/bot/message/push", headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"}, json={"to": user_id, "messages": [flex_payload]})
113
+ if r.status_code == 200: log_msg += "| ✅ LINE Flex ok"
114
+ else: log_msg += f"| ❌ LINE 錯誤: {r.text}"
115
+ except Exception as e: log_msg += f"| ❌ LINE 例外: {e}"
 
 
 
 
 
 
 
 
 
 
116
 
117
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
118
  return log_msg
119
  except Exception as e: return f"❌ Error: {str(e)}"
120
 
121
+ # --- [修正點] 登入邏輯 ---
122
+ def check_login(user, password):
123
+ if user == REAL_ADMIN_USER and password == REAL_ADMIN_PASSWORD:
124
+ # 登入成功:隱藏登入頁,顯示後台,並清空錯誤訊息
125
+ return {
126
+ login_row: gr.update(visible=False),
127
+ admin_row: gr.update(visible=True),
128
+ error_msg: ""
129
+ }
130
+ else:
131
+ return {
132
+ error_msg: "❌ 帳號或密碼錯誤"
133
+ }
134
+
135
+ # --- 介面開始 ---
136
  with gr.Blocks(title="Admin") as demo:
137
+
138
+ # 1. 登入介面 (預設顯示)
139
+ with gr.Group(visible=True) as login_row:
140
+ gr.Markdown("# 🔒 請登入後台")
141
+ with gr.Row():
142
+ username_input = gr.Textbox(label="帳號 Username", placeholder="Enter username")
143
+ password_input = gr.Textbox(label="密碼 Password", type="password", placeholder="Enter password")
144
+ login_btn = gr.Button("登入 Login", variant="primary")
145
+ error_msg = gr.Markdown("", style="color: red;")
146
+
147
+ # 2. 後台介面 (預設隱藏)
148
+ with gr.Group(visible=False) as admin_row:
149
+ gr.Markdown("# 🍷 訂位管理後台 (Dashboard)")
150
+ refresh_btn = gr.Button("🔄 重新整理")
151
+ booking_table = gr.Dataframe(interactive=False)
152
+ with gr.Row():
153
+ id_input = gr.Number(label="訂單 ID", precision=0)
154
+ action_btn = gr.Button("📧 發送確認信 (Hybrid)", variant="primary")
155
+ log_output = gr.Textbox(label="結果")
156
+
157
+ # 綁定後台按鈕功能
158
+ refresh_btn.click(get_bookings, outputs=booking_table)
159
+ action_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output)
160
+
161
+ # 3. 綁定登入按鈕事件
162
+ login_btn.click(
163
+ check_login,
164
+ inputs=[username_input, password_input],
165
+ outputs=[login_row, admin_row, error_msg]
166
+ )
167
 
168
  if __name__ == "__main__":
169
+ # ⚠️ 注意:這裡不再使用 auth=(...),改用上面寫的 Python 邏輯來切換顯示
170
+ demo.launch()