euler314 commited on
Commit
f05f818
·
verified ·
1 Parent(s): 7af6ed1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -136
app.py CHANGED
@@ -1,5 +1,6 @@
1
- from flask import Flask, request, jsonify, render_template_string
2
- from google.oauth2.service_account import Credentials
 
3
  from googleapiclient.discovery import build
4
  from googleapiclient.http import MediaIoBaseUpload
5
  import os
@@ -7,155 +8,205 @@ import io
7
  import datetime
8
 
9
  app = Flask(__name__)
 
 
 
 
10
 
11
- # Google API settings
12
- SCOPES = ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive.file']
13
  SPREADSHEET_ID = '1rcIJflbC1VIc70F6dnASLFvGQ90TXHhCx0iyxsXR7Ww'
14
  FOLDER_ID = '1brmLNqOMCvRS0TDY6ECHvIBmlRx8pcPz'
15
 
16
- # Create credentials from environment variables
17
- creds_dict = {
18
- "type": "service_account",
19
- "project_id": os.environ.get('PROJECT_ID'),
20
- "private_key_id": os.environ.get('PRIVATE_KEY_ID'),
21
- "private_key": os.environ.get('PRIVATE_KEY').replace('\\n', '\n'),
22
- "client_email": os.environ.get('CLIENT_EMAIL'),
23
- "client_id": os.environ.get('CLIENT_ID'),
24
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
25
- "token_uri": "https://oauth2.googleapis.com/token",
26
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
27
- "client_x509_cert_url": os.environ.get('CLIENT_X509_CERT_URL')
28
  }
29
 
30
- credentials = Credentials.from_service_account_info(creds_dict, scopes=SCOPES)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  @app.route('/')
33
- def home():
34
- return render_template_string('''
35
- <!DOCTYPE html>
36
- <html>
37
- <head>
38
- <title>Purchase Record Form</title>
39
- <meta charset="UTF-8">
40
- <style>
41
- body {
42
- font-family: Arial, sans-serif;
43
- max-width: 600px;
44
- margin: 20px auto;
45
- padding: 20px;
46
- }
47
- .form-group {
48
- margin-bottom: 20px;
49
- }
50
- label {
51
- display: block;
52
- margin-bottom: 8px;
53
- font-weight: bold;
54
- }
55
- input {
56
- width: 100%;
57
- padding: 10px;
58
- border: 1px solid #ddd;
59
- border-radius: 4px;
60
- font-size: 16px;
61
- }
62
- button {
63
- background-color: #4CAF50;
64
- color: white;
65
- padding: 12px 24px;
66
- border: none;
67
- border-radius: 4px;
68
- cursor: pointer;
69
- font-size: 16px;
70
- width: 100%;
71
- }
72
- button:hover {
73
- background-color: #45a049;
74
- }
75
- #status {
76
- margin-top: 20px;
77
- padding: 15px;
78
- border-radius: 4px;
79
- display: none;
80
- }
81
- .success {
82
- background-color: #dff0d8;
83
- color: #3c763d;
84
- }
85
- .error {
86
- background-color: #f2dede;
87
- color: #a94442;
88
- }
89
- </style>
90
- </head>
91
- <body>
92
- <h2>購買記錄表單 Purchase Record Form</h2>
93
- <form id="purchaseForm">
94
- <div class="form-group">
95
- <label for="name">姓名 Name:</label>
96
- <input type="text" id="name" name="name" required>
97
- </div>
98
-
99
- <div class="form-group">
100
- <label for="studentId">學號 Student ID:</label>
101
- <input type="text" id="studentId" name="studentId" required>
102
- </div>
103
-
104
- <div class="form-group">
105
- <label for="product">購買商品 Product:</label>
106
- <input type="text" id="product" name="product" required>
107
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- <div class="form-group">
110
- <label for="cost">金額 Cost:</label>
111
- <input type="number" id="cost" name="cost" required>
112
- </div>
113
 
114
- <div class="form-group">
115
- <label for="receipt">發票 Receipt:</label>
116
- <input type="file" id="receipt" name="receipt" accept="image/*,.pdf" required>
117
- </div>
118
 
119
- <button type="submit">提交 Submit</button>
120
- </form>
121
- <div id="status"></div>
122
-
123
- <script>
124
- document.getElementById('purchaseForm').addEventListener('submit', async (e) => {
125
- e.preventDefault();
126
-
127
- const status = document.getElementById('status');
128
- status.style.display = 'block';
129
- status.textContent = '提交中... Submitting...';
130
- status.className = '';
131
 
132
- const formData = new FormData(e.target);
133
 
134
- try {
135
- const response = await fetch('/submit', {
136
- method: 'POST',
137
- body: formData
138
- });
139
-
140
- const result = await response.json();
141
-
142
- if (result.success) {
143
- status.className = 'success';
144
- status.textContent = '提交成功! ' + result.message;
145
- e.target.reset();
146
- } else {
147
- status.className = 'error';
148
- status.textContent = '錯誤:' + result.message;
149
- }
150
- } catch (error) {
151
  status.className = 'error';
152
- status.textContent = '提交錯誤 Error submitting form';
153
  }
154
- });
155
- </script>
156
- </body>
157
- </html>
158
- ''')
 
 
 
 
 
 
 
 
159
 
160
  @app.route('/submit', methods=['POST'])
161
  def submit():
@@ -166,6 +217,8 @@ def submit():
166
  cost = request.form['cost']
167
  file = request.files['receipt']
168
 
 
 
169
  # Upload file to Drive
170
  drive_service = build('drive', 'v3', credentials=credentials)
171
 
 
1
+ from flask import Flask, request, jsonify, render_template_string, redirect, session
2
+ from google_auth_oauthlib.flow import Flow
3
+ from google.oauth2.credentials import Credentials
4
  from googleapiclient.discovery import build
5
  from googleapiclient.http import MediaIoBaseUpload
6
  import os
 
8
  import datetime
9
 
10
  app = Flask(__name__)
11
+ app.secret_key = os.environ.get('FLASK_SECRET_KEY')
12
+
13
+ SCOPES = ['https://www.googleapis.com/auth/spreadsheets',
14
+ 'https://www.googleapis.com/auth/drive.file']
15
 
 
 
16
  SPREADSHEET_ID = '1rcIJflbC1VIc70F6dnASLFvGQ90TXHhCx0iyxsXR7Ww'
17
  FOLDER_ID = '1brmLNqOMCvRS0TDY6ECHvIBmlRx8pcPz'
18
 
19
+ # OAuth configuration
20
+ client_config = {
21
+ "web": {
22
+ "client_id": os.environ.get('GOOGLE_CLIENT_ID'),
23
+ "client_secret": os.environ.get('GOOGLE_CLIENT_SECRET'),
24
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
25
+ "token_uri": "https://oauth2.googleapis.com/token",
26
+ "redirect_uris": ["https://euler314-purchase-record.hf.space/oauth2callback"]
27
+ }
 
 
 
28
  }
29
 
30
+ def credentials_to_dict(credentials):
31
+ return {
32
+ 'token': credentials.token,
33
+ 'refresh_token': credentials.refresh_token,
34
+ 'token_uri': credentials.token_uri,
35
+ 'client_id': credentials.client_id,
36
+ 'client_secret': credentials.client_secret,
37
+ 'scopes': credentials.scopes
38
+ }
39
+
40
+ def get_credentials():
41
+ if 'credentials' not in session:
42
+ return None
43
+ return Credentials(**session['credentials'])
44
+
45
+ @app.route('/authorize')
46
+ def authorize():
47
+ flow = Flow.from_client_config(
48
+ client_config,
49
+ scopes=SCOPES,
50
+ redirect_uri=client_config['web']['redirect_uris'][0]
51
+ )
52
+ authorization_url, state = flow.authorization_url(
53
+ access_type='offline',
54
+ include_granted_scopes='true'
55
+ )
56
+ session['state'] = state
57
+ return redirect(authorization_url)
58
+
59
+ @app.route('/oauth2callback')
60
+ def oauth2callback():
61
+ state = session['state']
62
+ flow = Flow.from_client_config(
63
+ client_config,
64
+ scopes=SCOPES,
65
+ state=state,
66
+ redirect_uri=client_config['web']['redirect_uris'][0]
67
+ )
68
+ authorization_response = request.url
69
+ flow.fetch_token(authorization_response=authorization_response)
70
+ credentials = flow.credentials
71
+ session['credentials'] = credentials_to_dict(credentials)
72
+ return redirect('/')
73
 
74
  @app.route('/')
75
+ def index():
76
+ if not get_credentials():
77
+ return redirect('/authorize')
78
+ return render_template_string(HTML_TEMPLATE)
79
+
80
+
81
+ HTML_TEMPLATE = '''
82
+ <!DOCTYPE html>
83
+ <html>
84
+ <head>
85
+ <title>Purchase Record Form</title>
86
+ <meta charset="UTF-8">
87
+ <style>
88
+ body {
89
+ font-family: Arial, sans-serif;
90
+ max-width: 600px;
91
+ margin: 20px auto;
92
+ padding: 20px;
93
+ }
94
+ .form-group {
95
+ margin-bottom: 20px;
96
+ }
97
+ label {
98
+ display: block;
99
+ margin-bottom: 8px;
100
+ font-weight: bold;
101
+ }
102
+ input {
103
+ width: 100%;
104
+ padding: 10px;
105
+ border: 1px solid #ddd;
106
+ border-radius: 4px;
107
+ font-size: 16px;
108
+ }
109
+ button {
110
+ background-color: #4CAF50;
111
+ color: white;
112
+ padding: 12px 24px;
113
+ border: none;
114
+ border-radius: 4px;
115
+ cursor: pointer;
116
+ font-size: 16px;
117
+ width: 100%;
118
+ }
119
+ button:hover {
120
+ background-color: #45a049;
121
+ }
122
+ #status {
123
+ margin-top: 20px;
124
+ padding: 15px;
125
+ border-radius: 4px;
126
+ display: none;
127
+ }
128
+ .success {
129
+ background-color: #dff0d8;
130
+ color: #3c763d;
131
+ }
132
+ .error {
133
+ background-color: #f2dede;
134
+ color: #a94442;
135
+ }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <h2>購買記錄表單 Purchase Record Form</h2>
140
+ <form id="purchaseForm">
141
+ <div class="form-group">
142
+ <label for="name">姓名 Name:</label>
143
+ <input type="text" id="name" name="name" required>
144
+ </div>
145
+
146
+ <div class="form-group">
147
+ <label for="studentId">學號 Student ID:</label>
148
+ <input type="text" id="studentId" name="studentId" required>
149
+ </div>
150
+
151
+ <div class="form-group">
152
+ <label for="product">購買商品 Product:</label>
153
+ <input type="text" id="product" name="product" required>
154
+ </div>
155
+
156
+ <div class="form-group">
157
+ <label for="cost">金額 Cost:</label>
158
+ <input type="number" id="cost" name="cost" required>
159
+ </div>
160
+
161
+ <div class="form-group">
162
+ <label for="receipt">發票 Receipt:</label>
163
+ <input type="file" id="receipt" name="receipt" accept="image/*,.pdf" required>
164
+ </div>
165
+
166
+ <button type="submit">提交 Submit</button>
167
+ </form>
168
+ <div id="status"></div>
169
+
170
+ <script>
171
+ document.getElementById('purchaseForm').addEventListener('submit', async (e) => {
172
+ e.preventDefault();
173
 
174
+ const status = document.getElementById('status');
175
+ status.style.display = 'block';
176
+ status.textContent = '提交中... Submitting...';
177
+ status.className = '';
178
 
179
+ const formData = new FormData(e.target);
 
 
 
180
 
181
+ try {
182
+ const response = await fetch('/submit', {
183
+ method: 'POST',
184
+ body: formData
185
+ });
 
 
 
 
 
 
 
186
 
187
+ const result = await response.json();
188
 
189
+ if (result.success) {
190
+ status.className = 'success';
191
+ status.textContent = '提交成功! ' + result.message;
192
+ e.target.reset();
193
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
194
  status.className = 'error';
195
+ status.textContent = '錯誤:' + result.message;
196
  }
197
+ } catch (error) {
198
+ status.className = 'error';
199
+ status.textContent = '提交錯誤 Error submitting form';
200
+ }
201
+ });
202
+ </script>
203
+ </body>
204
+ </html>
205
+ '''
206
+
207
+ @app.route('/')
208
+ def index():
209
+ return render_template_string(HTML_TEMPLATE)
210
 
211
  @app.route('/submit', methods=['POST'])
212
  def submit():
 
217
  cost = request.form['cost']
218
  file = request.files['receipt']
219
 
220
+ credentials = Credentials.from_authorized_user_info(CREDENTIALS)
221
+
222
  # Upload file to Drive
223
  drive_service = build('drive', 'v3', credentials=credentials)
224