]> WPIA git - gigi.git/blob - src/club/wpia/gigi/util/Notary.java
fix: ResultSet.getDate is often wrong as it fetches day-precision times
[gigi.git] / src / club / wpia / gigi / util / Notary.java
1 package club.wpia.gigi.util;
2
3 import java.io.IOException;
4 import java.text.ParseException;
5 import java.util.Calendar;
6 import java.util.Date;
7 import java.util.GregorianCalendar;
8 import java.util.HashMap;
9 import java.util.Map;
10
11 import club.wpia.gigi.GigiApiException;
12 import club.wpia.gigi.database.GigiPreparedStatement;
13 import club.wpia.gigi.database.GigiResultSet;
14 import club.wpia.gigi.dbObjects.Country;
15 import club.wpia.gigi.dbObjects.Group;
16 import club.wpia.gigi.dbObjects.Name;
17 import club.wpia.gigi.dbObjects.User;
18 import club.wpia.gigi.dbObjects.Assurance.AssuranceType;
19 import club.wpia.gigi.localisation.Language;
20 import club.wpia.gigi.output.ArrayIterable;
21 import club.wpia.gigi.output.DateSelector;
22 import club.wpia.gigi.output.template.MailTemplate;
23 import club.wpia.gigi.output.template.SprintfCommand;
24
25 public class Notary {
26
27     // minimum date range between 2 verifications of the RA-Agent to the same
28     // Applicant
29     public final static int LIMIT_DAYS_VERIFICATION = TimeConditions.getInstance().getVerificationLimitDays();
30
31     // maximum date range from date when the verification took place and the
32     // entering to the system
33     public final static int LIMIT_MAX_MONTHS_VERIFICATION = TimeConditions.getInstance().getVerificationMaxAgeMonths();
34
35     public static void writeUserAgreement(User member, String document, String method, String comment, boolean active, int secmemid) {
36         try (GigiPreparedStatement q = new GigiPreparedStatement("INSERT INTO `user_agreements` SET `memid`=?, `secmemid`=?," + " `document`=?,`date`=NOW(), `active`=?,`method`=?,`comment`=?")) {
37             q.setInt(1, member.getId());
38             q.setInt(2, secmemid);
39             q.setString(3, document);
40             q.setBoolean(4, active);
41             q.setString(5, method);
42             q.setString(6, comment);
43             q.execute();
44         }
45     }
46
47     public static boolean checkAssuranceIsPossible(User assurer, Name target) {
48         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT 1 FROM `notary` where `to`=? and `from`=? and `method` = ? ::`notaryType` AND `deleted` IS NULL AND `when` > (now() - interval '1 days' * ?)")) {
49             ps.setInt(1, target.getId());
50             ps.setInt(2, assurer.getId());
51             ps.setEnum(3, AssuranceType.FACE_TO_FACE);
52             ps.setInt(4, LIMIT_DAYS_VERIFICATION);
53             GigiResultSet rs = ps.executeQuery();
54             return !rs.next();
55         }
56     }
57
58     public static final Group ASSURER_BLOCKED = Group.BLOCKEDASSURER;
59
60     public static final Group ASSUREE_BLOCKED = Group.BLOCKEDASSUREE;
61
62     public static final Group VERIFY_NOTIFICATION = Group.VERIFY_NOTIFICATION;
63
64     /**
65      * This method assures another user.
66      * 
67      * @see User#canAssure() (for assurer)
68      * @see #checkAssuranceIsPossible(User, User) (for assurer or assuree)
69      * @param assurer
70      *            the person that wants to assure
71      * @param assuree
72      *            the person that should be assured
73      * @param assureeName
74      *            the Name that was personally verified
75      * @param dob
76      *            the Date of birth that the assurer verified
77      * @param awarded
78      *            the points that should be awarded in total
79      * @param location
80      *            the location where the assurance took place
81      * @param date
82      *            the date when the assurance took place
83      * @throws GigiApiException
84      *             if the assurance fails (for various reasons)
85      */
86     public synchronized static void assure(User assurer, User assuree, Name assureeName, DayDate dob, int awarded, String location, String date, AssuranceType type, Country country) throws GigiApiException {
87         may(assurer, assuree, AssuranceType.FACE_TO_FACE);
88         GigiApiException gae = new GigiApiException();
89         if ( !gae.isEmpty()) {
90             throw gae;
91         }
92         if (date == null || date.equals("")) {
93             gae.mergeInto(new GigiApiException("You must enter the date when you met the applicant."));
94         } else {
95             try {
96                 Date d = DateSelector.getDateFormat().parse(date);
97                 Calendar gc = GregorianCalendar.getInstance();
98                 gc.setTimeInMillis(System.currentTimeMillis());
99                 gc.add(Calendar.HOUR_OF_DAY, 12);
100                 if (d.getTime() > gc.getTimeInMillis()) {
101                     gae.mergeInto(new GigiApiException("You must not enter a date in the future."));
102                 }
103                 gc.setTimeInMillis(System.currentTimeMillis());
104                 gc.add(Calendar.MONTH, -LIMIT_MAX_MONTHS_VERIFICATION);
105                 if (d.getTime() < gc.getTimeInMillis()) {
106                     gae.mergeInto(new GigiApiException(SprintfCommand.createSimple("Verifications older than {0} months are not accepted.", LIMIT_MAX_MONTHS_VERIFICATION)));
107                 }
108             } catch (ParseException e) {
109                 gae.mergeInto(new GigiApiException("You must enter the date in this format: YYYY-MM-DD."));
110             }
111         }
112         // check location, min 3 characters
113         if (location == null || location.equals("")) {
114             gae.mergeInto(new GigiApiException("You failed to enter a location of your meeting."));
115         } else if (location.length() <= 2) {
116             gae.mergeInto(new GigiApiException("You must enter a location with at least 3 characters eg town and country."));
117         }
118
119         if (country == null) {
120             gae.mergeInto(new GigiApiException("You failed to enter the country of your meeting."));
121         }
122
123         synchronized (assuree) {
124             if (assurer.getId() == assuree.getId()) {
125                 throw new GigiApiException("You cannot verify yourself.");
126             }
127             if (assureeName.getOwner() != assuree) {
128                 throw new GigiApiException("Internal error, name does not belong to applicant.");
129             }
130             if ( !assurer.canAssure()) {
131                 throw new GigiApiException("You are not an RA-Agent.");
132             }
133
134             if ( !checkAssuranceIsPossible(assurer, assureeName)) {
135                 gae.mergeInto(new GigiApiException(SprintfCommand.createSimple("You have already verified this applicant within the last {0} days.", LIMIT_DAYS_VERIFICATION)));
136             }
137
138             if ( !assuree.getDoB().equals(dob)) {
139                 gae.mergeInto(new GigiApiException("The person you are verifying changed his personal details."));
140             }
141
142             if (awarded < 0) {
143                 gae.mergeInto(new GigiApiException("The points you are trying to award are out of range."));
144             } else {
145                 if (type == AssuranceType.NUCLEUS) {
146                     if (awarded > 50) {
147                         gae.mergeInto(new GigiApiException("The points you are trying to award are out of range."));
148                     }
149                 } else {
150                     if (awarded > assurer.getMaxAssurePoints()) {
151                         gae.mergeInto(new GigiApiException("The points you are trying to award are out of range."));
152                     }
153                 }
154             }
155
156             if ( !gae.isEmpty()) {
157                 throw gae;
158             }
159
160             if (type == AssuranceType.FACE_TO_FACE) {
161                 assureF2F(assurer, assuree, assureeName, awarded, location, date, country);
162             } else if (type == AssuranceType.NUCLEUS) {
163                 assureNucleus(assurer, assuree, assureeName, awarded, location, date, country);
164             } else if (type == AssuranceType.TTP_ASSISTED) {
165                 assureTTP(assurer, assuree, assureeName, awarded, location, date, country);
166             } else {
167                 throw new GigiApiException(SprintfCommand.createSimple("Unknown Verification type: {0}", type.toString()));
168             }
169             assurer.invalidateMadeAssurances();
170             assuree.invalidateReceivedAssurances();
171         }
172     }
173
174     private static void assureF2F(User assurer, User assuree, Name name, int awarded, String location, String date, Country country) throws GigiApiException {
175         may(assurer, assuree, AssuranceType.FACE_TO_FACE);
176         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?, `country`=?")) {
177             ps.setInt(1, assurer.getId());
178             ps.setInt(2, name.getId());
179             ps.setInt(3, awarded);
180             ps.setString(4, location);
181             ps.setString(5, date);
182             ps.setString(6, country.getCode());
183             ps.execute();
184         }
185     }
186
187     private static void assureTTP(User assurer, User assuree, Name name, int awarded, String location, String date, Country country) throws GigiApiException {
188         may(assurer, assuree, AssuranceType.TTP_ASSISTED);
189         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?, `country`=?, `method`='TTP-Assisted'")) {
190             ps.setInt(1, assurer.getId());
191             ps.setInt(2, name.getId());
192             ps.setInt(3, awarded);
193             ps.setString(4, location);
194             ps.setString(5, date);
195             ps.setString(6, country.getCode());
196             ps.execute();
197             assuree.revokeGroup(assurer, Group.TTP_APPLICANT);
198         }
199     }
200
201     public static void may(User assurer, User assuree, AssuranceType t) throws GigiApiException {
202         if (assuree.isInGroup(ASSUREE_BLOCKED)) {
203             throw new GigiApiException("The applicant is blocked.");
204         }
205         if (assurer.isInGroup(ASSURER_BLOCKED)) {
206             throw new GigiApiException("The RA Agent is blocked.");
207         }
208
209         if (t == AssuranceType.NUCLEUS) {
210             if ( !assurer.isInGroup(Group.NUCLEUS_ASSURER)) {
211                 throw new GigiApiException("RA Agent needs to be Nucleus RA Agent.");
212             }
213             return;
214         } else if (t == AssuranceType.TTP_ASSISTED) {
215             if ( !assurer.isInGroup(Group.TTP_ASSURER)) {
216                 throw new GigiApiException("RA Agent needs to be TTP RA Agent.");
217             }
218             if ( !assuree.isInGroup(Group.TTP_APPLICANT)) {
219                 throw new GigiApiException("Applicant needs to be TTP Applicant.");
220             }
221             return;
222         } else if (t == AssuranceType.FACE_TO_FACE) {
223             return;
224         }
225         throw new GigiApiException("Verification type not possible.");
226     }
227
228     private static void assureNucleus(User assurer, User assuree, Name name, int awarded, String location, String date, Country country) throws GigiApiException {
229         may(assurer, assuree, AssuranceType.NUCLEUS);
230         // Do up to 35 points as f2f
231         int f2fPoints = Math.min(assurer.getMaxAssurePoints(), awarded);
232         assureF2F(assurer, assuree, name, f2fPoints, location, date, country);
233
234         awarded -= f2fPoints;
235         if (awarded <= 0) {
236             return;
237         }
238
239         // Assure remaining points as "Nucleus Bonus"
240         // Valid for 4 Weeks = 28 days
241         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?, `country`=?, `method`='Nucleus Bonus', `expire` = CURRENT_TIMESTAMP + interval '28 days'")) {
242             ps.setInt(1, assurer.getId());
243             ps.setInt(2, name.getId());
244             ps.setInt(3, awarded);
245             ps.setString(4, location);
246             ps.setString(5, date);
247             ps.setString(6, country.getCode());
248             ps.execute();
249         }
250     }
251
252     public synchronized static void assureAll(User assurer, User assuree, DayDate dob, int awarded, String location, String date, AssuranceType type, Name[] toAssure, Country country) throws GigiApiException {
253         if (toAssure.length == 0) {
254             throw new GigiApiException("You must confirm at least one name to verify an account.");
255         }
256         boolean[] hadLessThan50Points = new boolean[toAssure.length];
257         boolean hadTotalLessThan100 = assuree.getAssurancePoints() < 100;
258         for (int i = 0; i < toAssure.length; i++) {
259             hadLessThan50Points[i] = toAssure[i].getAssurancePoints() < 50;
260
261             assure(assurer, assuree, toAssure[i], dob, awarded, location, date, type, country);
262         }
263         sendVerificationNotificationApplicant(assurer, assuree, toAssure, awarded, hadLessThan50Points, hadTotalLessThan100);
264         if (assurer.isInGroup(VERIFY_NOTIFICATION)) {
265             sendVerificationNotificationAgent(assurer, assuree, toAssure, awarded, location, date, country);
266         }
267     }
268
269     private static final MailTemplate verificationEntered = new MailTemplate(Notary.class.getResource("VerificationEntered.templ"));
270
271     private static final MailTemplate verificationAgentEntered = new MailTemplate(Notary.class.getResource("VerificationAgentEntered.templ"));
272
273     private static void sendVerificationNotificationApplicant(User assurer, User assuree, Name[] toAssure, final int awarded, final boolean[] hadLessThan50Points, boolean hadTotalLessThan100) {
274         HashMap<String, Object> mailVars = new HashMap<>();
275         mailVars.put("agent", assurer.getPreferredName().toString());
276         mailVars.put("names", new ArrayIterable<Name>(toAssure) {
277
278             @Override
279             public void apply(Name t, Language l, Map<String, Object> vars) {
280                 int totalVP = t.getAssurancePoints();
281                 vars.put("name", t.toString());
282                 vars.put("points", Integer.toString(awarded));
283                 vars.put("total", totalVP);
284                 if (totalVP < 50) {
285                     vars.put("rem", (50 - totalVP));
286                     vars.remove("gotGreater");
287                 } else if (hadLessThan50Points[i]) {
288                     vars.put("gotGreater", true);
289                     vars.remove("rem");
290                 }
291             }
292
293         });
294
295         int grandTotalVP = assuree.getAssurancePoints();
296         if (grandTotalVP >= 50 && grandTotalVP < 100) {
297             mailVars.put("remAll", (100 - grandTotalVP));
298             mailVars.remove("gotGreaterAll");
299         } else if (hadTotalLessThan100) {
300             mailVars.put("gotGreaterAll", true);
301             mailVars.remove("remAll");
302         }
303         try {
304             verificationEntered.sendMail(Language.getInstance(assuree.getPreferredLocale()), mailVars, assuree.getEmail());
305         } catch (IOException e) {
306             e.printStackTrace();
307         }
308     }
309
310     private static void sendVerificationNotificationAgent(User agent, User applicant, Name[] toVerify, final int awarded, String location, String date, Country country) {
311         HashMap<String, Object> mailVars = new HashMap<>();
312         mailVars.put("email", applicant.getEmail());
313         mailVars.put("location", location);
314         mailVars.put("date", date);
315         mailVars.put("country", country.getName());
316         mailVars.put("points", Integer.toString(awarded));
317         mailVars.put("names", new ArrayIterable<Name>(toVerify) {
318
319             @Override
320             public void apply(Name t, Language l, Map<String, Object> vars) {
321                 vars.put("name", t.toString());
322             }
323
324         });
325
326         try {
327             verificationAgentEntered.sendMail(Language.getInstance(applicant.getPreferredLocale()), mailVars, agent.getEmail());
328         } catch (IOException e) {
329             e.printStackTrace();
330         }
331     }
332 }