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

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