Door config settings (and some fixes to doorgroup reporting)
[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         #if DEBUG
224         printf("Bank_SetFlags: query=\"%s\"\n", query);
225         #endif
226
227         // Execute Query
228         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
229         if( rv != SQLITE_OK )
230         {
231                 fprintf(stderr, "Bank_SetUserFlags - SQLite Error: %s\n", errmsg);
232                 fprintf(stderr, "query = '%s'\n", query);
233                 free(query);
234                 sqlite3_free(errmsg);
235                 return -1;
236         }
237         free(query);
238         
239         return 0;
240 }
241
242 /*
243  * Get user balance
244  */
245 int Bank_GetBalance(int AcctID)
246 {
247         sqlite3_stmt    *statement;
248         char    *query;
249          int    ret;
250         
251         query = mkstr("SELECT acct_balance FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
252         statement = Bank_int_QuerySingle(gBank_Database, query);
253         free(query);
254         if( !statement )        return INT_MIN;
255         
256         // Read return value
257         ret = sqlite3_column_int(statement, 0);
258         
259         // Clean up and return
260         sqlite3_finalize(statement);
261         return ret;
262 }
263
264 /*
265  * Get the name of an account
266  */
267 char *Bank_GetAcctName(int AcctID)
268 {
269         sqlite3_stmt    *statement;
270         char    *query;
271         char    *ret;
272         
273         query = mkstr("SELECT acct_name FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
274         statement = Bank_int_QuerySingle(gBank_Database, query);
275         free(query);
276         if( !statement )        return NULL;
277         
278         // Read return value
279         ret = strdup( (const char*)sqlite3_column_text(statement, 0) );
280         
281         // Clean up and return
282         sqlite3_finalize(statement);
283         return ret;
284 }
285
286 /*
287  * Get an account ID from a name
288  */
289 int Bank_GetAcctByName(const char *Name)
290 {
291         char    *query;
292         sqlite3_stmt    *statement;
293          int    ret;
294         
295         if( !Bank_int_IsValidName(Name) )       return -1;
296         
297         query = mkstr("SELECT acct_id FROM accounts WHERE acct_name='%s' LIMIT 1", Name);
298         statement = Bank_int_QuerySingle(gBank_Database, query);
299         free(query);
300         if( !statement )        return -1;
301         
302         ret = sqlite3_column_int(statement, 0);
303         sqlite3_finalize(statement);
304         
305         if( ret == 0 )  return -1;
306         return ret;
307 }
308
309 /*
310  * Create a new named account
311  */
312 int Bank_CreateAcct(const char *Name)
313 {
314         char    *query;
315         char    *errmsg;
316          int    rv;
317         
318         if( Name )
319         {
320                 if( !Bank_int_IsValidName(Name) )       return -1;
321                 query = mkstr("INSERT INTO accounts (acct_name) VALUES ('%s')", Name);
322         }
323         else
324         {
325                 query = strdup("INSERT INTO accounts (acct_name) VALUES (NULL)");
326         }
327                 
328         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
329         if( rv != SQLITE_OK )
330         {
331                 fprintf(stderr, "Bank_CreateAcct - SQLite Error: '%s'\n", errmsg);
332                 fprintf(stderr, "Query = '%s'\n", query);
333                 sqlite3_free(errmsg);
334                 free(query);
335                 return -1;
336         }
337         
338         free(query);
339         
340         return sqlite3_last_insert_rowid(gBank_Database);
341 }
342
343 /*
344  * Create an iterator for user accounts
345  */
346 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
347 {
348         char    *query;
349         const char      *balanceClause;
350         const char      *lastSeenClause;
351         const char      *orderClause;
352         const char      *revSort;
353         sqlite3_stmt    *ret;
354         
355         // Balance condtion
356         if( Flags & BANK_ITFLAG_MINBALANCE )
357                 balanceClause = " AND acct_balance>=";
358         else if( Flags & BANK_ITFLAG_MAXBALANCE )
359                 balanceClause = " AND acct_balance<=";
360         else {
361                 balanceClause = " AND 1!=";
362                 MinMaxBalance = 0;
363         }
364         
365         // Last seen condition
366         if( Flags & BANK_ITFLAG_SEENAFTER )
367                 lastSeenClause = " AND acct_last_seen>=";
368         else if( Flags & BANK_ITFLAG_SEENBEFORE )
369                 lastSeenClause = " AND acct_last_seen<=";
370         else {
371                 lastSeenClause = " AND datetime(-1,'unixepoch')!=";
372         }
373         
374         // Sorting clause
375         switch( Flags & BANK_ITFLAG_SORTMASK )
376         {
377         case BANK_ITFLAG_SORT_NONE:
378                 orderClause = "";
379                 revSort = "";
380                 break;
381         case BANK_ITFLAG_SORT_NAME:
382                 orderClause = "ORDER BY acct_name";
383                 revSort = " DESC";
384                 break;
385         case BANK_ITFLAG_SORT_BAL:
386                 orderClause = "ORDER BY acct_balance";
387                 revSort = " DESC";
388                 break;
389         case BANK_ITFLAG_SORT_LASTSEEN:
390                 orderClause = "ORDER BY acct_balance";
391                 revSort = " DESC";
392                 break;
393         default:
394                 fprintf(stderr, "BUG: Unknown sort (%x) in SQLite CokeBank\n", Flags & BANK_ITFLAG_SORTMASK);
395                 return NULL;
396         }
397         if( !(Flags & BANK_ITFLAG_REVSORT) )
398                 revSort = "";
399         
400         #define MAP_FLAG(name, flag)    (FlagMask&(flag)?(FlagValues&(flag)?" AND "name"=1":" AND "name"=0"):"")
401         query = mkstr("SELECT acct_id FROM accounts WHERE 1=1"
402                 "%s%s%s%s%s"    // Flags
403                 "%s%i"  // Balance
404                 "%sdatetime(%"PRIu64",'unixepoch')"     // Last seen
405                 "%s%s"  // Sort and direction
406                 ,
407                 MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
408                 MAP_FLAG("acct_is_admin", USER_FLAG_ADMIN),
409                 MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
410                 MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
411                 MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED),
412                 balanceClause, MinMaxBalance,
413                 lastSeenClause, (uint64_t)LastSeen,
414                 orderClause, revSort
415                 );
416         //printf("query = \"%s\"\n", query);
417         #undef MAP_FLAG
418         
419         ret = Bank_int_MakeStatemnt(gBank_Database, query);
420         if( !ret )      return NULL;
421         
422         free(query);
423         
424         return (void*)ret;
425 }
426
427 /*
428  * Get the next account in an iterator
429  */
430 int Bank_IteratorNext(tAcctIterator *It)
431 {
432          int    rv;
433         rv = sqlite3_step( (sqlite3_stmt*)It );
434         
435         if( rv == SQLITE_DONE ) return -1;
436         if( rv != SQLITE_ROW ) {
437                 fprintf(stderr, "Bank_IteratorNext - SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
438                 return -1;
439         }
440         
441         return sqlite3_column_int( (sqlite3_stmt*)It, 0 );
442 }
443
444 /*
445  * Free an interator
446  */
447 void Bank_DelIterator(tAcctIterator *It)
448 {
449         sqlite3_finalize( (sqlite3_stmt*)It );
450 }
451
452 /*
453  * Check user authentication token
454  */
455 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
456 {
457         Salt = Password = Username;     // Shut up GCC
458         // DEBUG HACKS!
459         #if 1
460         return Bank_GetAcctByName(Username);
461         #else
462         return -1;
463         #endif
464 }
465
466 /*
467  * Get an account number given a card ID
468  * NOTE: Actually ends up just being an alternate authentication token,
469  *       as no checking is done on the ID's validity, save for SQL sanity.
470  */
471 int Bank_GetAcctByCard(const char *CardID)
472 {
473         char    *query;
474         sqlite3_stmt    *statement;
475          int    ret;
476         
477         if( !Bank_int_IsValidName(CardID) )
478                 return -1;
479         
480         query = mkstr("SELECT acct_id FROM cards WHERE card_name='%s' LIMIT 1", CardID);
481         statement = Bank_int_QuerySingle(gBank_Database, query);
482         free(query);
483         if( !statement )        return -1;
484         
485         ret = sqlite3_column_int(statement, 0);
486         
487         sqlite3_finalize(statement);
488         
489         return ret;
490 }
491
492 /*
493  * Add a card to an account
494  */
495 int Bank_AddAcctCard(int AcctID, const char *CardID)
496 {
497         char    *query;
498          int    rv;
499         char    *errmsg;
500         
501         if( !Bank_int_IsValidName(CardID) )
502                 return -1;
503         
504         // TODO: Check the AcctID too
505         
506         // Insert card
507         query = mkstr("INSERT INTO cards (acct_id,card_name) VALUES (%i,'%s')",
508                 AcctID, CardID);
509         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
510         if( rv == SQLITE_CONSTRAINT )
511         {
512                 sqlite3_free(errmsg);
513                 free(query);
514                 return 2;       // Card in use
515         }
516         if( rv != SQLITE_OK )
517         {
518                 fprintf(stderr, "Bank_AddAcctCard - SQLite Error: '%s'\n", errmsg);
519                 fprintf(stderr, "Query = '%s'\n", query);
520                 sqlite3_free(errmsg);
521                 free(query);
522                 return -1;
523         }
524         free(query);
525         
526         return 0;
527 }
528
529 /*
530  * Create a SQLite Statement
531  */
532 sqlite3_stmt *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query)
533 {
534          int    rv;
535         sqlite3_stmt    *ret;
536         rv = sqlite3_prepare_v2(Database, Query, strlen(Query)+1, &ret, NULL);
537         if( rv != SQLITE_OK ) {
538                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(Database));
539                 fprintf(stderr, "query = \"%s\"\n", Query);
540                 return NULL;
541         }
542         
543         return ret;
544 }
545
546 /*
547  * Create a SQLite statement and query it for the first row
548  * Returns NULL if the the set is empty
549  */
550 sqlite3_stmt *Bank_int_QuerySingle(sqlite3 *Database, const char *Query)
551 {
552         sqlite3_stmt    *ret;
553          int    rv;
554         
555         #if DEBUG
556         printf("Bank_int_QuerySingle: (Query='%s')\n", Query);
557         #endif
558         
559         // Prepare query
560         ret = Bank_int_MakeStatemnt(Database, Query);
561         if( !ret ) {
562                 #if DEBUG
563                 printf("Bank_int_QuerySingle: RETURN NULL ret=NULL\n");
564                 #endif
565                 return NULL;
566         }
567         
568         // Get row
569         rv = sqlite3_step(ret);
570         // - Empty result set
571         if( rv == SQLITE_DONE ) {
572                 #if DEBUG
573                 printf("Bank_int_QuerySingle: RETURN NULL (rv == SQLITE_DONE)\n");
574                 #endif
575                 return NULL;
576         }
577         // - Other error
578         if( rv != SQLITE_ROW ) {
579                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
580                 fprintf(stderr, "query = \"%s\"\n", Query);
581                 return NULL;
582         }
583         
584         #if DEBUG
585         printf("Bank_int_QuerySingle: RETURN %p\n", ret);
586         #endif
587         return ret;
588 }
589
590 /**
591  * \brief Checks if the passed account name is valid
592  */
593 int Bank_int_IsValidName(const char *Name)
594 {
595         while(*Name)
596         {
597                 if( *Name == '\'' )     return 0;
598                 Name ++;
599         }
600         return 1;
601 }

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