e962b9d94a9652c06c9ca00f89a4031d2336915d
[tpg/opendispense2.git] / src / cokebank_sqlite / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * SQLite Coke Bank (Accounts Database)
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file
8  * COPYING for full details.
9  */
10 #include <stdlib.h>
11 #include <limits.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include "../cokebank.h"
15 #include <sqlite3.h>
16
17 const char * const csBank_CreateAccountQry = "CREATE TABLE IF NOT EXISTS accounts ("
18 "       acct_id INTEGER PRIMARY KEY NOT NULL,"
19 "       acct_balance INTEGER NOT NULL DEFAULT 0,"
20 "       acct_name STRING UNIQUE,"
21 "       acct_uid INTEGER UNIQUE DEFAULT NULL,"
22 "       acct_pin INTEGER CHECK (acct_pin > 0 AND acct_pin < 10000) DEFAULT NULL,"
23 "       acct_is_disabled BOOLEAN NOT NULL DEFAULT false,"
24 "       acct_is_coke BOOLEAN NOT NULL DEFAULT false,"
25 "       acct_is_wheel BOOLEAN NOT NULL DEFAULT false,"
26 "       acct_is_door BOOLEAN NOT NULL DEFAULT false,"
27 "       acct_is_internal BOOLEAN NOT NULL DEFAULT false"
28 ")";
29 const char * const csBank_CreateCardsQry = "CREATE TABLE IF NOT EXISTS cards ("
30 "       acct_id INTEGER NOT NULL,"
31 "       card_name STRING NOT NULL UNIQUE,"
32 "       FOREIGN KEY (acct_id) REFERENCES accounts (acct_id) ON DELETE CASCADE"
33 //                       Deletion of the account frees the card  ^ ^ ^
34 ")";
35
36 // === TYPES ===
37 struct sAcctIterator    // Unused really, just used as a void type
38 {
39 };
40
41 // === PROTOYPES ===
42  int    Bank_Initialise(const char *Argument);
43  int    Bank_Transfer(int SourceAcct, int DestAcct, int Ammount, const char *Reason);
44  int    Bank_GetUserFlags(int AcctID);
45  int    Bank_SetUserFlags(int AcctID, int Mask, int Value);
46  int    Bank_GetBalance(int AcctID);
47 char    *Bank_GetAcctName(int AcctID);
48 sqlite3_stmt    *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query);
49 sqlite3_stmt    *Bank_int_QuerySingle(sqlite3 *Database, const char *Query);
50  int    Bank_int_IsValidName(const char *Name);
51
52 // === GLOBALS ===
53 sqlite3 *gBank_Database;
54
55 // === CODE ===
56 int Bank_Initialise(const char *Argument)
57 {
58          int    rv;
59         char    *errmsg;
60         // Open database
61         rv = sqlite3_open(Argument, &gBank_Database);
62         if(rv != 0)
63         {
64                 fprintf(stderr, "CokeBank: Unable to open database '%s'\n", Argument);
65                 fprintf(stderr, "Reason: %s\n", sqlite3_errmsg(gBank_Database));
66                 sqlite3_close(gBank_Database);
67                 return 1;
68         }
69
70         // Check structure
71         rv = sqlite3_exec(gBank_Database, "SELECT acct_id FROM accounts LIMIT 1", NULL, NULL, &errmsg);
72         if( rv == SQLITE_OK )
73         {
74                 // NOP
75         }
76         else if( rv == SQLITE_NOTFOUND )
77         {
78                 sqlite3_free(errmsg);
79                 // Create tables
80                 // - Accounts
81                 rv = sqlite3_exec(gBank_Database, csBank_CreateAccountQry, NULL, NULL, &errmsg);
82                 if( rv != SQLITE_OK ) {
83                         fprintf(stderr, "SQLite Error: %s\n", errmsg);
84                         sqlite3_free(errmsg);
85                         return 1;
86                 }
87                 // - Mifare relation
88                 rv = sqlite3_exec(gBank_Database, csBank_CreateCardsQry, NULL, NULL, &errmsg);
89                 if( rv != SQLITE_OK ) {
90                         fprintf(stderr, "SQLite Error: %s\n", errmsg);
91                         sqlite3_free(errmsg);
92                         return 1;
93                 }
94         }
95         else
96         {
97                 // Unknown error
98                 fprintf(stderr, "SQLite Error: %s\n", errmsg);
99                 sqlite3_free(errmsg);
100                 return 1;
101         }
102
103         return 0;
104 }
105
106 /*
107  * Move Money
108  */
109 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
110 {
111         char    *query;
112          int    rv;
113         char    *errmsg;
114         
115         Reason = "";    // Shut GCC up
116         
117         // Begin SQL Transaction
118         sqlite3_exec(gBank_Database, "BEGIN TRANSACTION", NULL, NULL, NULL);
119
120         // Take from the source
121         query = mkstr("UPDATE accounts SET acct_balance=acct_balance-%i WHERE acct_id=%i", Ammount, SourceUser);
122         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
123         free(query);
124         if( rv != SQLITE_OK )
125         {
126                 fprintf(stderr, "SQLite Error: %s\n", errmsg);
127                 sqlite3_free(errmsg);
128                 sqlite3_exec(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
129                 return 1;
130         }
131
132         // Give to the destination
133         query = mkstr("UPDATE accounts SET acct_balance=acct_balance+%i WHERE acct_id=%i", Ammount, DestUser);
134         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
135         free(query);
136         if( rv != SQLITE_OK )
137         {
138                 fprintf(stderr, "SQLite Error: %s\n", errmsg);
139                 sqlite3_free(errmsg);
140                 sqlite3_exec(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
141                 return 1;
142         }
143
144         // Commit transaction
145         sqlite3_exec(gBank_Database, "COMMIT", NULL, NULL, NULL);
146
147         return 0;
148 }
149
150 /*
151  * Get user flags
152  */
153 int Bank_GetUserFlags(int UserID)
154 {
155         sqlite3_stmt    *statement;
156         char    *query;
157          int    ret;
158
159         // Build Query
160         query = mkstr(
161                 "SELECT acct_is_disabled,acct_is_coke,acct_is_wheel,acct_is_door,acct_is_internal"
162                 " FROM accounts WHERE acct_id=%i LIMIT 1",
163                 UserID
164                 );
165         statement = Bank_int_QuerySingle(gBank_Database, query);
166         free(query);
167         if( !statement )        return -1;
168
169         // Get Flags
170         ret = 0;
171         // - Disabled
172         if( sqlite3_column_int(statement, 0) )  ret |= USER_FLAG_DISABLED;
173         // - Coke
174         if( sqlite3_column_int(statement, 1) )  ret |= USER_FLAG_COKE;
175         // - Wheel
176         if( sqlite3_column_int(statement, 2) )  ret |= USER_FLAG_WHEEL;
177         // - Door
178         if( sqlite3_column_int(statement, 3) )  ret |= USER_FLAG_DOORGROUP;
179         // - Internal
180         if( sqlite3_column_int(statement, 3) )  ret |= USER_FLAG_INTERNAL;
181         
182         // Destroy and return
183         sqlite3_finalize(statement);
184         
185         return ret;
186 }
187
188 /*
189  * Set user flags
190  */
191 int Bank_SetUserFlags(int UserID, int Mask, int Value)
192 {
193         char    *query;
194          int    rv;
195         char    *errmsg;
196
197         #define MAP_FLAG(name, flag)    (Mask&(flag)?(Value&(flag)?","name"=1":","name"=0"):"")
198         query = mkstr(
199                 "UDPATE accounts WHERE acct_id=%i SET acct_id=acct_id%s%s%s%s%s",
200                 UserID,
201                 MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
202                 MAP_FLAG("acct_is_wheel", USER_FLAG_WHEEL),
203                 MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
204                 MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
205                 MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED)
206                 );
207         #undef MAP_FLAG
208
209         // Execute Query
210         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
211         free(query);
212         if( rv != SQLITE_OK )
213         {
214                 fprintf(stderr, "SQLite Error: %s\n", errmsg);
215                 sqlite3_free(errmsg);
216                 return -1;
217         }
218         
219         return 0;
220 }
221
222 /*
223  * Get user balance
224  */
225 int Bank_GetBalance(int AcctID)
226 {
227         sqlite3_stmt    *statement;
228         char    *query;
229          int    ret;
230         
231         query = mkstr("SELECT acct_balance FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
232         statement = Bank_int_QuerySingle(gBank_Database, query);
233         free(query);
234         if( !statement )        return INT_MIN;
235         
236         // Read return value
237         ret = sqlite3_column_int(statement, 0);
238         
239         // Clean up and return
240         sqlite3_finalize(statement);
241         return ret;
242 }
243
244 /*
245  * Get the name of an account
246  */
247 char *Bank_GetAcctName(int AcctID)
248 {
249         sqlite3_stmt    *statement;
250         char    *query;
251         char    *ret;
252         
253         query = mkstr("SELECT acct_name FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
254         statement = Bank_int_QuerySingle(gBank_Database, query);
255         free(query);
256         if( !statement )        return NULL;
257         
258         // Read return value
259         ret = strdup( (const char*)sqlite3_column_text(statement, 0) );
260         
261         // Clean up and return
262         sqlite3_finalize(statement);
263         return ret;
264 }
265
266 /*
267  * Get an account ID from a name
268  */
269 int Bank_GetAcctByName(const char *Name)
270 {
271         char    *query;
272         sqlite3_stmt    *statement;
273          int    ret;
274         
275         if( !Bank_int_IsValidName(Name) )       return -1;
276         
277         query = mkstr("SELECT acct_id FROM accounts WHERE acct_name='%s' LIMIT 1", Name);
278         statement = Bank_int_QuerySingle(gBank_Database, query);
279         free(query);
280         if( !statement )        return -1;
281         
282         ret = sqlite3_column_int(statement, 0);
283         
284         sqlite3_finalize(statement);
285         return ret;
286 }
287
288 /*
289  * Create a new named account
290  */
291 int Bank_CreateAcct(const char *Name)
292 {
293         char    *query;
294         char    *errmsg;
295          int    rv;
296         
297         if( Name )
298         {
299                 if( !Bank_int_IsValidName(Name) )       return -1;
300                 query = mkstr("INSERT INTO accounts (acct_name) VALUES ('%s')", Name);
301         }
302         else
303         {
304                 query = strdup("INSERT INTO accounts (acct_name) VALUES (NULL)");
305         }
306                 
307         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
308         if( rv != SQLITE_OK )
309         {
310                 fprintf(stderr, "SQLite Error: '%s'\n", errmsg);
311                 fprintf(stderr, "Query = '%s'\n", query);
312                 sqlite3_free(errmsg);
313                 free(query);
314                 return -1;
315         }
316         
317         free(query);
318         
319         return sqlite3_last_insert_rowid(gBank_Database);
320 }
321
322 /*
323  * Create an iterator for user accounts
324  */
325 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
326 {
327         char    *query;
328         const char      *balanceClause;
329         const char      *lastSeenClause;
330         const char      *orderClause;
331         const char      *revSort;
332         sqlite3_stmt    *ret;
333         
334         if( Flags & BANK_ITFLAG_MINBALANCE )
335                 balanceClause = "acct_balance>=";
336         else if( Flags & BANK_ITFLAG_MAXBALANCE )
337                 balanceClause = "acct_balance<=";
338         else {
339                 balanceClause = "1!=";
340                 MinMaxBalance = 0;
341         }
342         
343         if( Flags & BANK_ITFLAG_SEENAFTER )
344                 lastSeenClause = "acct_last_seen>=";
345         else if( Flags & BANK_ITFLAG_SEENBEFORE )
346                 lastSeenClause = "acct_last_seen<=";
347         else {
348                 lastSeenClause = "datetime(0,'unixepoch')!=";
349         }
350         
351         switch( Flags & BANK_ITFLAG_SORTMASK )
352         {
353         case BANK_ITFLAG_SORT_NONE:
354                 orderClause = "";
355                 revSort = "";
356                 break;
357         case BANK_ITFLAG_SORT_NAME:
358                 orderClause = "ORDER BY acct_name";
359                 revSort = " DESC";
360                 break;
361         case BANK_ITFLAG_SORT_BAL:
362                 orderClause = "ORDER BY acct_balance";
363                 revSort = " DESC";
364                 break;
365         case BANK_ITFLAG_SORT_LASTSEEN:
366                 orderClause = "ORDER BY acct_balance";
367                 revSort = " DESC";
368                 break;
369         default:
370                 fprintf(stderr, "BUG: Unknown sort (%x) in SQLite CokeBank\n", Flags & BANK_ITFLAG_SORTMASK);
371                 return NULL;
372         }
373         if( !(Flags & BANK_ITFLAG_REVSORT) )
374                 revSort = "";
375         
376         #define MAP_FLAG(name, flag)    (FlagMask&(flag)?(FlagValues&(flag)?" AND "name"=1":" AND "name"=0"):"")
377         query = mkstr("SELECT acct_id FROM accounts WHERE 1=1"
378                 "%s%s%s%s%s"    // Flags
379                 "%s%i"  // Balance
380                 "%sdatetime(%lli,'unixepoch')"  // Last seen
381                 "%s%s"  // Sort and direction
382                 ,
383                 MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
384                 MAP_FLAG("acct_is_wheel", USER_FLAG_WHEEL),
385                 MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
386                 MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
387                 MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED),
388                 balanceClause, MinMaxBalance,
389                 lastSeenClause, LastSeen,
390                 orderClause, revSort
391                 );
392         #undef MAP_FLAG
393         
394         ret = Bank_int_MakeStatemnt(gBank_Database, query);
395         if( !ret )      return NULL;
396         
397         free(query);
398         
399         return (void*)ret;
400 }
401
402 /*
403  * Get the next account in an iterator
404  */
405 int Bank_IteratorNext(tAcctIterator *It)
406 {
407          int    rv;
408         rv = sqlite3_step( (sqlite3_stmt*)It );
409         
410         if( rv == SQLITE_DONE ) return -1;
411         if( rv != SQLITE_ROW ) {
412                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
413                 return -1;
414         }
415         
416         return sqlite3_column_int( (sqlite3_stmt*)It, 0 );
417 }
418
419 /*
420  * Free an interator
421  */
422 void Bank_DelIterator(tAcctIterator *It)
423 {
424         sqlite3_finalize( (sqlite3_stmt*)It );
425 }
426
427 /*
428  * Check user authentication token
429  */
430 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
431 {
432         Salt = Password = Username;     // Shut up GCC
433         // DEBUG HACKS!
434         #if 1
435         return Bank_GetAcctByName(Username);
436         #else
437         return -1;
438         #endif
439 }
440
441 /*
442  * Get an account number given a card ID
443  * NOTE: Actually ends up just being an alternate authentication token,
444  *       as no checking is done on the ID's validity, save for SQL sanity.
445  */
446 int Bank_GetAcctByCard(const char *CardID)
447 {
448         char    *query;
449         sqlite3_stmt    *statement;
450          int    ret;
451         
452         if( !Bank_int_IsValidName(CardID) )
453                 return -1;
454         
455         query = mkstr("SELECT acct_id FROM cards WHERE card_name='%s' LIMIT 1", CardID);
456         statement = Bank_int_QuerySingle(gBank_Database, query);
457         free(query);
458         if( !statement )        return -1;
459         
460         ret = sqlite3_column_int(statement, 0);
461         
462         sqlite3_finalize(statement);
463         
464         return ret;
465 }
466
467 /*
468  * Add a card to an account
469  */
470 int Bank_AddAcctCard(int AcctID, const char *CardID)
471 {
472         char    *query;
473          int    rv;
474         char    *errmsg;
475         
476         if( !Bank_int_IsValidName(CardID) )
477                 return -1;
478         
479         // TODO: Check the AcctID too
480         
481         // Insert card
482         query = mkstr("INSERT INTO cards (acct_id,card_name) VALUES (%i,'%s')",
483                 AcctID, CardID);
484         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
485         if( rv == SQLITE_CONSTRAINT )
486         {
487                 sqlite3_free(errmsg);
488                 free(query);
489                 return 2;       // Card in use
490         }
491         if( rv != SQLITE_OK )
492         {
493                 fprintf(stderr, "SQLite Error: '%s'\n", errmsg);
494                 fprintf(stderr, "Query = '%s'\n", query);
495                 sqlite3_free(errmsg);
496                 free(query);
497                 return -1;
498         }
499         free(query);
500         
501         return 0;
502 }
503
504 /*
505  * Create a SQLite Statement
506  */
507 sqlite3_stmt *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query)
508 {
509          int    rv;
510         sqlite3_stmt    *ret;
511         rv = sqlite3_prepare_v2(Database, Query, strlen(Query)+1, &ret, NULL);
512         if( rv != SQLITE_OK ) {
513                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(Database));
514                 fprintf(stderr, "query = \"%s\"\n", Query);
515                 return NULL;
516         }
517         
518         return ret;
519 }
520
521 /*
522  * Create a SQLite statement and query it for the first row
523  * Returns NULL if the the set is empty
524  */
525 sqlite3_stmt *Bank_int_QuerySingle(sqlite3 *Database, const char *Query)
526 {
527         sqlite3_stmt    *ret;
528          int    rv;
529         
530         // Prepare query
531         ret = Bank_int_MakeStatemnt(Database, Query);
532         if( !ret )      return NULL;
533         
534         // Get row
535         rv = sqlite3_step(ret);
536         // - Empty result set
537         if( rv == SQLITE_DONE ) return NULL;
538         // - Other error
539         if( rv != SQLITE_ROW ) {
540                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
541                 fprintf(stderr, "query = \"%s\"\n", Query);
542                 return NULL;
543         }
544         
545         return ret;
546 }
547
548 /**
549  * \brief Checks if the passed account name is valid
550  */
551 int Bank_int_IsValidName(const char *Name)
552 {
553         while(*Name)
554         {
555                 if( *Name == '\'' )     return 0;
556                 Name ++;
557         }
558         return 1;
559 }

UCC git Repository :: git.ucc.asn.au