-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
VoteController.java
190 lines (173 loc) · 7.04 KB
/
VoteController.java
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
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.cloudrun;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ResponseStatusException;
@Controller
public final class VoteController {
private static final Logger logger = LoggerFactory.getLogger(VoteController.class);
private final String table = System.getenv().getOrDefault("TABLE", "pet_votes");
// [START cloudrun_user_auth_sql_connect]
private final JdbcTemplate jdbcTemplate;
public VoteController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// [END cloudrun_user_auth_sql_connect]
@GetMapping("/")
public String index(Model model) {
try {
// Query the total count of "CATS" from the database.
int catVotes = getVoteCount("CATS");
// Query the total count of "DOGS" from the database.
int dogVotes = getVoteCount("DOGS");
// Calculate and set leader values.
String leadTeam;
String leaderMessage;
int voteDiff = 0;
if (catVotes != dogVotes) {
if (catVotes > dogVotes) {
leadTeam = "CATS";
voteDiff = catVotes - dogVotes;
} else {
leadTeam = "DOGS";
voteDiff = dogVotes - catVotes;
}
String append = (voteDiff > 1) ? "s" : "";
leaderMessage = leadTeam + " are winning by " + voteDiff + " vote" + append + ".";
} else {
leaderMessage = "CATS and DOGS are evenly matched!";
leadTeam = null;
}
// Query the last 5 votes from the database.
List<Vote> votes = getVotes();
// Add values to template
model.addAttribute("leaderMessage", leaderMessage);
model.addAttribute("leadTeam", leadTeam);
model.addAttribute("catVotes", catVotes);
model.addAttribute("dogVotes", dogVotes);
model.addAttribute("votes", votes);
} catch (DataAccessException e) {
String message =
"Error while connecting to the Cloud SQL database. "
+ "Check that your username and password are correct and that the "
+ "PostgreSQL instance, database, and table exists and are ready for use: "
+ e.toString();
logger.error(message);
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "Unable to load page; see logs for more details.", e);
}
return "index";
}
@PostMapping("/")
@ResponseBody
public String vote(
@RequestHeader Map<String, String> headers, @RequestParam Map<String, String> body) {
// Get decoded Id Platform user id
String uid = authenticateJwt(headers);
// Get the team from the request and record the time of the vote.
String team = body.get("team");
Date date = new Date();
Timestamp timestamp = new Timestamp(date.getTime());
// Validate team selection
if (team == null || (!team.equals("CATS") && !team.equals("DOGS"))) {
return "error: '" + team + "' is not a valid candidate.";
}
// Create a vote record to be stored in the database.
Vote vote = new Vote(uid, team, timestamp);
// Save the data to the database.
try {
insertVote(vote);
MDC.put("uid", uid);
MDC.put("team", team);
logger.info("vote_inserted");
} catch (DataAccessException e) {
logger.error("Error while attempting to submit vote: " + e.toString());
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "Unable to cast vote; see logs for more details.", e);
}
return "Successfully voted for " + team + " at " + timestamp.toLocalDateTime();
}
// [START cloudrun_user_auth_jwt]
/** Extract and verify Id Token from header */
private String authenticateJwt(Map<String, String> headers) {
String authHeader =
(headers.get("authorization") != null)
? headers.get("authorization")
: headers.get("Authorization");
if (authHeader != null) {
String idToken = authHeader.split(" ")[1];
// If the provided ID token has the correct format, is not expired, and is
// properly signed, the method returns the decoded ID token
try {
FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
String uid = decodedToken.getUid();
return uid;
} catch (FirebaseAuthException e) {
logger.error("Error with authentication: " + e.toString());
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "", e);
}
} else {
logger.error("Error no authorization header");
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
}
// [END cloudrun_user_auth_jwt]
/** Insert a vote record into the database. */
public void insertVote(Vote vote) throws DataAccessException {
this.jdbcTemplate.update(
"INSERT INTO " + table + "(candidate, time_cast, uid) VALUES(?,?,?)",
vote.getCandidate(),
vote.getTimeCast(),
vote.getUid());
}
/** Retrieve the latest 5 vote records from the database. */
public List<Vote> getVotes() throws DataAccessException {
return this.jdbcTemplate.query(
"SELECT candidate, time_cast, uid FROM " + table + " ORDER BY time_cast DESC LIMIT 5",
(rs, rowNum) -> {
String candidate = rs.getString("candidate");
String uid = rs.getString("uid");
Timestamp timeCast = rs.getTimestamp("time_cast");
return new Vote(uid, candidate, timeCast);
});
}
/** Retrieve the total count of records for a given candidate from the database. */
public int getVoteCount(String candidate) throws DataAccessException {
return this.jdbcTemplate.queryForObject(
"SELECT COUNT(vote_id) FROM " + table + " WHERE candidate = ?",
Integer.class,
new Object[] {candidate});
}
}