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

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