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

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