github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/cmd/query_gen/tables.go (about)

     1  package main
     2  
     3  import qgen "github.com/Azareal/Gosora/query_gen"
     4  
     5  var mysqlPre = "utf8mb4"
     6  var mysqlCol = "utf8mb4_general_ci"
     7  
     8  var tables []string
     9  
    10  type tblColumn = qgen.DBTableColumn
    11  type tC = tblColumn
    12  type tblKey = qgen.DBTableKey
    13  
    14  func createTables(a qgen.Adapter) error {
    15  	tables = nil
    16  	f := func(table, charset, collation string, cols []tC, keys []tblKey) error {
    17  		tables = append(tables, table)
    18  		return qgen.Install.CreateTable(table, charset, collation, cols, keys)
    19  	}
    20  	return createTables2(a, f)
    21  }
    22  
    23  func createTables2(a qgen.Adapter, f func(table, charset, collation string, columns []tC, keys []tblKey) error) (err error) {
    24  	createTable := func(table, charset, collation string, cols []tC, keys []tblKey) {
    25  		if err != nil {
    26  			return
    27  		}
    28  		err = f(table, charset, collation, cols, keys)
    29  	}
    30  	bcol := func(col string, val bool) qgen.DBTableColumn {
    31  		if val {
    32  			return tC{col, "boolean", 0, false, false, "1"}
    33  		}
    34  		return tC{col, "boolean", 0, false, false, "0"}
    35  	}
    36  	ccol := func(col string, size int, sdefault string) qgen.DBTableColumn {
    37  		return tC{col, "varchar", size, false, false, sdefault}
    38  	}
    39  	text := func(params ...string) qgen.DBTableColumn {
    40  		if len(params) == 0 {
    41  			return tC{"", "text", 0, false, false, ""}
    42  		}
    43  		col, sdefault := params[0], ""
    44  		if len(params) > 1 {
    45  			sdefault = params[1]
    46  			if sdefault == "" {
    47  				sdefault = "''"
    48  			}
    49  		}
    50  		return tC{col, "text", 0, false, false, sdefault}
    51  	}
    52  	createdAt := func(coll ...string) qgen.DBTableColumn {
    53  		var col string
    54  		if len(coll) > 0 {
    55  			col = coll[0]
    56  		}
    57  		if col == "" {
    58  			col = "createdAt"
    59  		}
    60  		return tC{col, "createdAt", 0, false, false, ""}
    61  	}
    62  
    63  	createTable("users", mysqlPre, mysqlCol,
    64  		[]tC{
    65  			{"uid", "int", 0, false, true, ""},
    66  			ccol("name", 100, ""),
    67  			ccol("password", 100, ""),
    68  
    69  			ccol("salt", 80, "''"),
    70  			{"group", "int", 0, false, false, ""}, // TODO: Make this a foreign key
    71  			bcol("active", false),
    72  			bcol("is_super_admin", false),
    73  			createdAt(),
    74  			{"lastActiveAt", "datetime", 0, false, false, ""},
    75  			ccol("session", 200, "''"),
    76  			//ccol("authToken", 200, "''"),
    77  			ccol("last_ip", 200, "''"),
    78  			{"profile_comments", "int", 0, false, false, "0"},
    79  			{"who_can_convo", "int", 0, false, false, "0"},
    80  			{"enable_embeds", "int", 0, false, false, "-1"},
    81  			ccol("email", 200, "''"),
    82  			ccol("avatar", 100, "''"),
    83  			text("message"),
    84  
    85  			// TODO: Drop these columns?
    86  			ccol("url_prefix", 20, "''"),
    87  			ccol("url_name", 100, "''"),
    88  			//text("pub_key"),
    89  
    90  			{"level", "smallint", 0, false, false, "0"},
    91  			{"score", "int", 0, false, false, "0"},
    92  			{"posts", "int", 0, false, false, "0"},
    93  			{"bigposts", "int", 0, false, false, "0"},
    94  			{"megaposts", "int", 0, false, false, "0"},
    95  			{"topics", "int", 0, false, false, "0"},
    96  			{"liked", "int", 0, false, false, "0"},
    97  
    98  			// These two are to bound liked queries with little bits of information we know about the user to reduce the server load
    99  			{"oldestItemLikedCreatedAt", "datetime", 0, false, false, ""}, // For internal use only, semantics may change
   100  			{"lastLiked", "datetime", 0, false, false, ""},                // For internal use only, semantics may change
   101  
   102  			//{"penalty_count","int",0,false,false,"0"},
   103  			{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
   104  		},
   105  		[]tK{
   106  			{"uid", "primary", "", false},
   107  			{"name", "unique", "", false},
   108  		},
   109  	)
   110  
   111  	createTable("users_groups", mysqlPre, mysqlCol,
   112  		[]tC{
   113  			{"gid", "int", 0, false, true, ""},
   114  			ccol("name", 100, ""),
   115  			text("permissions"),
   116  			text("plugin_perms"),
   117  			bcol("is_mod", false),
   118  			bcol("is_admin", false),
   119  			bcol("is_banned", false),
   120  			{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this
   121  
   122  			ccol("tag", 50, "''"),
   123  		},
   124  		[]tK{
   125  			{"gid", "primary", "", false},
   126  		},
   127  	)
   128  
   129  	createTable("users_groups_promotions", mysqlPre, mysqlCol,
   130  		[]tC{
   131  			{"pid", "int", 0, false, true, ""},
   132  			{"from_gid", "int", 0, false, false, ""},
   133  			{"to_gid", "int", 0, false, false, ""},
   134  			bcol("two_way", false), // If a user no longer meets the requirements for this promotion then they will be demoted if this flag is set
   135  
   136  			// Requirements
   137  			{"level", "int", 0, false, false, ""},
   138  			{"posts", "int", 0, false, false, "0"},
   139  			{"minTime", "int", 0, false, false, ""},        // How long someone needs to have been in their current group before being promoted
   140  			{"registeredFor", "int", 0, false, false, "0"}, // minutes
   141  		},
   142  		[]tK{
   143  			{"pid", "primary", "", false},
   144  		},
   145  	)
   146  
   147  	/*
   148  		createTable("users_groups_promotions_scheduled","","",
   149  			[]tC{
   150  				{"prid","int",0,false,false,""},
   151  				{"uid","int",0,false,false,""},
   152  				{"runAt","datetime",0,false,false,""},
   153  			},
   154  			[]tK{
   155  				// TODO: Test to see that the compound primary key works
   156  				{"prid,uid", "primary", "", false},
   157  			},
   158  		)
   159  	*/
   160  
   161  	createTable("users_2fa_keys", mysqlPre, mysqlCol,
   162  		[]tC{
   163  			{"uid", "int", 0, false, false, ""},
   164  			ccol("secret", 100, ""),
   165  			ccol("scratch1", 50, ""),
   166  			ccol("scratch2", 50, ""),
   167  			ccol("scratch3", 50, ""),
   168  			ccol("scratch4", 50, ""),
   169  			ccol("scratch5", 50, ""),
   170  			ccol("scratch6", 50, ""),
   171  			ccol("scratch7", 50, ""),
   172  			ccol("scratch8", 50, ""),
   173  			{"createdAt", "createdAt", 0, false, false, ""},
   174  		},
   175  		[]tK{
   176  			{"uid", "primary", "", false},
   177  		},
   178  	)
   179  
   180  	// What should we do about global penalties? Put them on the users table for speed? Or keep them here?
   181  	// Should we add IP Penalties? No, that's a stupid idea, just implement IP Bans properly. What about shadowbans?
   182  	// TODO: Perm overrides
   183  	// TODO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag
   184  	// TODO: Add a penalty type where a user is stopped from creating plugin_guilds social groups
   185  	// TODO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly.
   186  	/*createTable("users_penalties","","",
   187  		[]tC{
   188  			{"uid","int",0,false,false,""},
   189  			{"element_id","int",0,false,false,""},
   190  			ccol("element_type",50,""), //forum, profile?, and social_group. Leave blank for global.
   191  			text("overrides","{}"),
   192  
   193  			bcol("mod_queue",false),
   194  			bcol("shadow_ban",false),
   195  			bcol("no_avatar",false), // Coming Soon. Should this be a perm override instead?
   196  
   197  			// Do we *really* need rate-limit penalty types? Are we going to be allowing bots or something?
   198  			//{"posts_per_hour","int",0,false,false,"0"},
   199  			//{"topics_per_hour","int",0,false,false,"0"},
   200  			//{"posts_count","int",0,false,false,"0"},
   201  			//{"topic_count","int",0,false,false,"0"},
   202  			//{"last_hour","int",0,false,false,"0"}, // UNIX Time, as we don't need to do anything too fancy here. When an hour has elapsed since that time, reset the hourly penalty counters.
   203  
   204  			{"issued_by","int",0,false,false,""},
   205  			createdAt("issued_at"),
   206  			{"expires_at","datetime",0,false,false,""},
   207  		}, nil,
   208  	)*/
   209  
   210  	createTable("users_groups_scheduler", "", "",
   211  		[]tC{
   212  			{"uid", "int", 0, false, false, ""},
   213  			{"set_group", "int", 0, false, false, ""},
   214  
   215  			{"issued_by", "int", 0, false, false, ""},
   216  			createdAt("issued_at"),
   217  			{"revert_at", "datetime", 0, false, false, ""},
   218  			{"temporary", "boolean", 0, false, false, ""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
   219  		},
   220  		[]tK{
   221  			{"uid", "primary", "", false},
   222  		},
   223  	)
   224  
   225  	// TODO: Can we use a piece of software dedicated to persistent queues for this rather than relying on the database for it?
   226  	createTable("users_avatar_queue", "", "",
   227  		[]tC{
   228  			{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   229  		},
   230  		[]tK{
   231  			{"uid", "primary", "", false},
   232  		},
   233  	)
   234  
   235  	// TODO: Should we add a users prefix to this table to fit the "unofficial convention"?
   236  	// TODO: Add an autoincrement key?
   237  	createTable("emails", "", "",
   238  		[]tC{
   239  			ccol("email", 200, ""),
   240  			{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   241  			bcol("validated", false),
   242  			ccol("token", 200, "''"),
   243  		}, nil,
   244  	)
   245  
   246  	// TODO: Allow for patterns in domains, if the bots try to shake things up there?
   247  	/*
   248  		createTable("email_domain_blacklist", "", "",
   249  			[]tC{
   250  				ccol("domain", 200, ""),
   251  				bcol("gtld", false),
   252  			},
   253  			[]tK{
   254  				{"domain", "primary"},
   255  			},
   256  		)
   257  	*/
   258  
   259  	// TODO: Implement password resets
   260  	createTable("password_resets", "", "",
   261  		[]tC{
   262  			ccol("email", 200, ""),
   263  			{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   264  			ccol("validated", 200, ""),          // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
   265  			ccol("token", 200, ""),
   266  			createdAt(),
   267  		}, nil,
   268  	)
   269  
   270  	createTable("forums", mysqlPre, mysqlCol,
   271  		[]tC{
   272  			{"fid", "int", 0, false, true, ""},
   273  			ccol("name", 100, ""),
   274  			ccol("desc", 200, ""),
   275  			ccol("tmpl", 200, "''"),
   276  			bcol("active", true),
   277  			{"order", "int", 0, false, false, "0"},
   278  			{"topicCount", "int", 0, false, false, "0"},
   279  			ccol("preset", 100, "''"),
   280  			{"parentID", "int", 0, false, false, "0"},
   281  			ccol("parentType", 50, "''"),
   282  			{"lastTopicID", "int", 0, false, false, "0"},
   283  			{"lastReplyerID", "int", 0, false, false, "0"},
   284  		},
   285  		[]tK{
   286  			{"fid", "primary", "", false},
   287  		},
   288  	)
   289  
   290  	createTable("forums_permissions", "", "",
   291  		[]tC{
   292  			{"fid", "int", 0, false, false, ""},
   293  			{"gid", "int", 0, false, false, ""},
   294  			ccol("preset", 100, "''"),
   295  			text("permissions", "{}"),
   296  		},
   297  		[]tK{
   298  			// TODO: Test to see that the compound primary key works
   299  			{"fid,gid", "primary", "", false},
   300  		},
   301  	)
   302  
   303  	createTable("topics", mysqlPre, mysqlCol,
   304  		[]tC{
   305  			{"tid", "int", 0, false, true, ""},
   306  			ccol("title", 100, ""), // TODO: Increase the max length to 200?
   307  			text("content"),
   308  			text("parsed_content"),
   309  			createdAt(),
   310  			{"lastReplyAt", "datetime", 0, false, false, ""},
   311  			{"lastReplyBy", "int", 0, false, false, ""},
   312  			{"lastReplyID", "int", 0, false, false, "0"},
   313  			{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   314  			bcol("is_closed", false),
   315  			bcol("sticky", false),
   316  			// TODO: Add an index for this
   317  			{"parentID", "int", 0, false, false, "2"},
   318  			ccol("ip", 200, "''"),
   319  			{"postCount", "int", 0, false, false, "1"},
   320  			{"likeCount", "int", 0, false, false, "0"},
   321  			{"attachCount", "int", 0, false, false, "0"},
   322  			{"words", "int", 0, false, false, "0"},
   323  			{"views", "int", 0, false, false, "0"},
   324  			//{"dayViews", "int", 0, false, false, "0"},
   325  			{"weekEvenViews", "int", 0, false, false, "0"},
   326  			{"weekOddViews", "int", 0, false, false, "0"},
   327  			///{"weekViews", "int", 0, false, false, "0"},
   328  			///{"lastWeekViews", "int", 0, false, false, "0"},
   329  			//{"monthViews", "int", 0, false, false, "0"},
   330  			// ? - A little hacky, maybe we could do something less likely to bite us with huge numbers of topics?
   331  			// TODO: Add an index for this?
   332  			//{"lastMonth", "datetime", 0, false, false, ""},
   333  			ccol("css_class", 100, "''"),
   334  			{"poll", "int", 0, false, false, "0"},
   335  			ccol("data", 200, "''"),
   336  		},
   337  		[]tK{
   338  			{"tid", "primary", "", false},
   339  			{"title", "fulltext", "", false},
   340  			{"content", "fulltext", "", false},
   341  		},
   342  	)
   343  
   344  	createTable("replies", mysqlPre, mysqlCol,
   345  		[]tC{
   346  			{"rid", "int", 0, false, true, ""},  // TODO: Rename to replyID?
   347  			{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID?
   348  			text("content"),
   349  			text("parsed_content"),
   350  			createdAt(),
   351  			{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   352  			{"lastEdit", "int", 0, false, false, "0"},
   353  			{"lastEditBy", "int", 0, false, false, "0"},
   354  			{"lastUpdated", "datetime", 0, false, false, ""},
   355  			ccol("ip", 200, "''"),
   356  			{"likeCount", "int", 0, false, false, "0"},
   357  			{"attachCount", "int", 0, false, false, "0"},
   358  			{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why?
   359  			ccol("actionType", 20, "''"),
   360  			{"poll", "int", 0, false, false, "0"},
   361  		},
   362  		[]tK{
   363  			{"rid", "primary", "", false},
   364  			{"content", "fulltext", "", false},
   365  		},
   366  	)
   367  
   368  	createTable("attachments", mysqlPre, mysqlCol,
   369  		[]tC{
   370  			{"attachID", "int", 0, false, true, ""},
   371  			{"sectionID", "int", 0, false, false, "0"},
   372  			ccol("sectionTable", 200, "forums"),
   373  			{"originID", "int", 0, false, false, ""},
   374  			ccol("originTable", 200, "replies"),
   375  			{"uploadedBy", "int", 0, false, false, ""}, // TODO; Make this a foreign key
   376  			ccol("path", 200, ""),
   377  			ccol("extra", 200, ""),
   378  		},
   379  		[]tK{
   380  			{"attachID", "primary", "", false},
   381  		},
   382  	)
   383  
   384  	createTable("revisions", mysqlPre, mysqlCol,
   385  		[]tC{
   386  			{"reviseID", "int", 0, false, true, ""},
   387  			text("content"),
   388  			{"contentID", "int", 0, false, false, ""},
   389  			ccol("contentType", 100, "replies"),
   390  			createdAt(),
   391  			// TODO: Add a createdBy column?
   392  		},
   393  		[]tK{
   394  			{"reviseID", "primary", "", false},
   395  		},
   396  	)
   397  
   398  	createTable("polls", mysqlPre, mysqlCol,
   399  		[]tC{
   400  			{"pollID", "int", 0, false, true, ""},
   401  			{"parentID", "int", 0, false, false, "0"},
   402  			ccol("parentTable", 100, "topics"), // topics, replies
   403  			{"type", "int", 0, false, false, "0"},
   404  			{"options", "json", 0, false, false, ""},
   405  			{"votes", "int", 0, false, false, "0"},
   406  		},
   407  		[]tK{
   408  			{"pollID", "primary", "", false},
   409  		},
   410  	)
   411  
   412  	createTable("polls_options", "", "",
   413  		[]tC{
   414  			{"pollID", "int", 0, false, false, ""},
   415  			{"option", "int", 0, false, false, "0"},
   416  			{"votes", "int", 0, false, false, "0"},
   417  		}, nil,
   418  	)
   419  
   420  	createTable("polls_votes", mysqlPre, mysqlCol,
   421  		[]tC{
   422  			{"pollID", "int", 0, false, false, ""},
   423  			{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   424  			{"option", "int", 0, false, false, "0"},
   425  			createdAt("castAt"),
   426  			ccol("ip", 200, "''"),
   427  		}, nil,
   428  	)
   429  
   430  	createTable("users_replies", mysqlPre, mysqlCol,
   431  		[]tC{
   432  			{"rid", "int", 0, false, true, ""},
   433  			{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   434  			text("content"),
   435  			text("parsed_content"),
   436  			createdAt(),
   437  			{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   438  			{"lastEdit", "int", 0, false, false, "0"},
   439  			{"lastEditBy", "int", 0, false, false, "0"},
   440  			ccol("ip", 200, "''"),
   441  		},
   442  		[]tK{
   443  			{"rid", "primary", "", false},
   444  		},
   445  	)
   446  
   447  	createTable("likes", "", "",
   448  		[]tC{
   449  			{"weight", "tinyint", 0, false, false, "1"},
   450  			{"targetItem", "int", 0, false, false, ""},
   451  			ccol("targetType", 50, "replies"),
   452  			{"sentBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   453  			createdAt(),
   454  			{"recalc", "tinyint", 0, false, false, "0"},
   455  		}, nil,
   456  	)
   457  
   458  	//columns("participants,createdBy,createdAt,lastReplyBy,lastReplyAt").Where("cid=?")
   459  	createTable("conversations", "", "",
   460  		[]tC{
   461  			{"cid", "int", 0, false, true, ""},
   462  			{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   463  			createdAt(),
   464  			{"lastReplyAt", "datetime", 0, false, false, ""},
   465  			{"lastReplyBy", "int", 0, false, false, ""},
   466  		},
   467  		[]tK{
   468  			{"cid", "primary", "", false},
   469  		},
   470  	)
   471  
   472  	createTable("conversations_posts", "", "",
   473  		[]tC{
   474  			{"pid", "int", 0, false, true, ""},
   475  			{"cid", "int", 0, false, false, ""},
   476  			{"createdBy", "int", 0, false, false, ""},
   477  			ccol("body", 50, ""),
   478  			ccol("post", 50, "''"),
   479  		},
   480  		[]tK{
   481  			{"pid", "primary", "", false},
   482  		},
   483  	)
   484  
   485  	createTable("conversations_participants", "", "",
   486  		[]tC{
   487  			{"uid", "int", 0, false, false, ""},
   488  			{"cid", "int", 0, false, false, ""},
   489  		}, nil,
   490  	)
   491  
   492  	/*
   493  		createTable("users_friends", "", "",
   494  			[]tC{
   495  				{"uid", "int", 0, false, false, ""},
   496  				{"uid2", "int", 0, false, false, ""},
   497  			}, nil,
   498  		)
   499  		createTable("users_friends_invites", "", "",
   500  			[]tC{
   501  				{"requester", "int", 0, false, false, ""},
   502  				{"target", "int", 0, false, false, ""},
   503  			}, nil,
   504  		)
   505  	*/
   506  
   507  	createTable("users_blocks", "", "",
   508  		[]tC{
   509  			{"blocker", "int", 0, false, false, ""},
   510  			{"blockedUser", "int", 0, false, false, ""},
   511  		}, nil,
   512  	)
   513  
   514  	createTable("activity_stream_matches", "", "",
   515  		[]tC{
   516  			{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   517  			{"asid", "int", 0, false, false, ""},    // TODO: Make this a foreign key
   518  		},
   519  		[]tK{
   520  			{"asid,asid", "foreign", "activity_stream", true},
   521  		},
   522  	)
   523  
   524  	createTable("activity_stream", "", "",
   525  		[]tC{
   526  			{"asid", "int", 0, false, true, ""},
   527  			{"actor", "int", 0, false, false, ""},      /* the one doing the act */ // TODO: Make this a foreign key
   528  			{"targetUser", "int", 0, false, false, ""}, /* the user who created the item the actor is acting on, some items like forums may lack a targetUser field */
   529  			ccol("event", 50, ""),                      /* mention, like, reply (as in the act of replying to an item, not the reply item type, you can "reply" to a forum by making a topic in it), friend_invite */
   530  			ccol("elementType", 50, ""),                /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
   531  
   532  			// replacement for elementType
   533  			tC{"elementTable", "int", 0, false, false, "0"},
   534  
   535  			{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
   536  			createdAt(),
   537  			ccol("extra", 200, "''"),
   538  		},
   539  		[]tK{
   540  			{"asid", "primary", "", false},
   541  		},
   542  	)
   543  
   544  	createTable("activity_subscriptions", "", "",
   545  		[]tC{
   546  			{"user", "int", 0, false, false, ""},     // TODO: Make this a foreign key
   547  			{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
   548  			ccol("targetType", 50, ""),               /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
   549  			{"level", "int", 0, false, false, "0"},   /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/
   550  		}, nil,
   551  	)
   552  
   553  	/* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */
   554  	createTable("settings", "", "",
   555  		[]tC{
   556  			ccol("name", 180, ""),
   557  			ccol("content", 250, ""),
   558  			ccol("type", 50, ""),
   559  			ccol("constraints", 200, "''"),
   560  		},
   561  		[]tK{
   562  			{"name", "unique", "", false},
   563  		},
   564  	)
   565  
   566  	createTable("word_filters", "", "",
   567  		[]tC{
   568  			{"wfid", "int", 0, false, true, ""},
   569  			ccol("find", 200, ""),
   570  			ccol("replacement", 200, ""),
   571  		},
   572  		[]tK{
   573  			{"wfid", "primary", "", false},
   574  		},
   575  	)
   576  
   577  	createTable("plugins", "", "",
   578  		[]tC{
   579  			ccol("uname", 180, ""),
   580  			bcol("active", false),
   581  			bcol("installed", false),
   582  		},
   583  		[]tK{
   584  			{"uname", "unique", "", false},
   585  		},
   586  	)
   587  
   588  	createTable("themes", "", "",
   589  		[]tC{
   590  			ccol("uname", 180, ""),
   591  			bcol("default", false),
   592  			//text("profileUserVars"),
   593  		},
   594  		[]tK{
   595  			{"uname", "unique", "", false},
   596  		},
   597  	)
   598  
   599  	createTable("widgets", "", "",
   600  		[]tC{
   601  			{"wid", "int", 0, false, true, ""},
   602  			{"position", "int", 0, false, false, ""},
   603  			ccol("side", 100, ""),
   604  			ccol("type", 100, ""),
   605  			bcol("active", false),
   606  			ccol("location", 100, ""),
   607  			text("data"),
   608  		},
   609  		[]tK{
   610  			{"wid", "primary", "", false},
   611  		},
   612  	)
   613  
   614  	createTable("menus", "", "",
   615  		[]tC{
   616  			{"mid", "int", 0, false, true, ""},
   617  		},
   618  		[]tK{
   619  			{"mid", "primary", "", false},
   620  		},
   621  	)
   622  
   623  	createTable("menu_items", "", "",
   624  		[]tC{
   625  			{"miid", "int", 0, false, true, ""},
   626  			{"mid", "int", 0, false, false, ""},
   627  			ccol("name", 200, "''"),
   628  			ccol("htmlID", 200, "''"),
   629  			ccol("cssClass", 200, "''"),
   630  			ccol("position", 100, ""),
   631  			ccol("path", 200, "''"),
   632  			ccol("aria", 200, "''"),
   633  			ccol("tooltip", 200, "''"),
   634  			ccol("tmplName", 200, "''"),
   635  			{"order", "int", 0, false, false, "0"},
   636  
   637  			bcol("guestOnly", false),
   638  			bcol("memberOnly", false),
   639  			bcol("staffOnly", false),
   640  			bcol("adminOnly", false),
   641  		},
   642  		[]tK{
   643  			{"miid", "primary", "", false},
   644  		},
   645  	)
   646  
   647  	createTable("pages", mysqlPre, mysqlCol,
   648  		[]tC{
   649  			{"pid", "int", 0, false, true, ""},
   650  			//ccol("path", 200, ""),
   651  			ccol("name", 200, ""),
   652  			ccol("title", 200, ""),
   653  			text("body"),
   654  			// TODO: Make this a table?
   655  			text("allowedGroups"),
   656  			{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu
   657  		},
   658  		[]tK{
   659  			{"pid", "primary", "", false},
   660  		},
   661  	)
   662  
   663  	createTable("registration_logs", "", "",
   664  		[]tC{
   665  			{"rlid", "int", 0, false, true, ""},
   666  			ccol("username", 100, ""),
   667  			{"email", "varchar", 100, false, false, ""},
   668  			ccol("failureReason", 100, ""),
   669  			bcol("success", false), // Did this attempt succeed?
   670  			ccol("ipaddress", 200, ""),
   671  			createdAt("doneAt"),
   672  		},
   673  		[]tK{
   674  			{"rlid", "primary", "", false},
   675  		},
   676  	)
   677  
   678  	createTable("login_logs", "", "",
   679  		[]tC{
   680  			{"lid", "int", 0, false, true, ""},
   681  			{"uid", "int", 0, false, false, ""},
   682  
   683  			bcol("success", false), // Did this attempt succeed?
   684  			ccol("ipaddress", 200, ""),
   685  			createdAt("doneAt"),
   686  		},
   687  		[]tK{
   688  			{"lid", "primary", "", false},
   689  		},
   690  	)
   691  
   692  	createTable("moderation_logs", "", "",
   693  		[]tC{
   694  			ccol("action", 100, ""),
   695  			{"elementID", "int", 0, false, false, ""},
   696  			ccol("elementType", 100, ""),
   697  			ccol("ipaddress", 200, ""),
   698  			{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   699  			{"doneAt", "datetime", 0, false, false, ""},
   700  			text("extra"),
   701  		}, nil,
   702  	)
   703  
   704  	createTable("administration_logs", "", "",
   705  		[]tC{
   706  			ccol("action", 100, ""),
   707  			{"elementID", "int", 0, false, false, ""},
   708  			ccol("elementType", 100, ""),
   709  			ccol("ipaddress", 200, ""),
   710  			{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
   711  			{"doneAt", "datetime", 0, false, false, ""},
   712  			text("extra"),
   713  		}, nil,
   714  	)
   715  
   716  	createTable("viewchunks", "", "",
   717  		[]tC{
   718  			{"count", "int", 0, false, false, "0"},
   719  			{"avg", "int", 0, false, false, "0"},
   720  			{"createdAt", "datetime", 0, false, false, ""},
   721  			ccol("route", 200, ""), // TODO: set a default empty here
   722  		}, nil,
   723  	)
   724  
   725  	createTable("viewchunks_agents", "", "",
   726  		[]tC{
   727  			{"count", "int", 0, false, false, "0"},
   728  			{"createdAt", "datetime", 0, false, false, ""},
   729  			ccol("browser", 200, ""), // googlebot, firefox, opera, etc.
   730  			//ccol("version",0,""), // the version of the browser or bot
   731  		}, nil,
   732  	)
   733  
   734  	createTable("viewchunks_systems", "", "",
   735  		[]tC{
   736  			{"count", "int", 0, false, false, "0"},
   737  			{"createdAt", "datetime", 0, false, false, ""},
   738  			ccol("system", 200, ""), // windows, android, unknown, etc.
   739  		}, nil,
   740  	)
   741  
   742  	createTable("viewchunks_langs", "", "",
   743  		[]tC{
   744  			{"count", "int", 0, false, false, "0"},
   745  			{"createdAt", "datetime", 0, false, false, ""},
   746  			ccol("lang", 200, ""), // en, ru, etc.
   747  		}, nil,
   748  	)
   749  
   750  	createTable("viewchunks_referrers", "", "",
   751  		[]tC{
   752  			{"count", "int", 0, false, false, "0"},
   753  			{"createdAt", "datetime", 0, false, false, ""},
   754  			ccol("domain", 200, ""),
   755  		}, nil,
   756  	)
   757  
   758  	createTable("viewchunks_forums", "", "",
   759  		[]tC{
   760  			{"count", "int", 0, false, false, "0"},
   761  			{"createdAt", "datetime", 0, false, false, ""},
   762  			{"forum", "int", 0, false, false, ""},
   763  		}, nil,
   764  	)
   765  
   766  	createTable("topicchunks", "", "",
   767  		[]tC{
   768  			{"count", "int", 0, false, false, "0"},
   769  			{"createdAt", "datetime", 0, false, false, ""},
   770  			// TODO: Add a column for the parent forum?
   771  		}, nil,
   772  	)
   773  
   774  	createTable("postchunks", "", "",
   775  		[]tC{
   776  			{"count", "int", 0, false, false, "0"},
   777  			{"createdAt", "datetime", 0, false, false, ""},
   778  			// TODO: Add a column for the parent topic / profile?
   779  		}, nil,
   780  	)
   781  
   782  	createTable("memchunks", "", "",
   783  		[]tC{
   784  			{"count", "int", 0, false, false, "0"},
   785  			{"stack", "int", 0, false, false, "0"},
   786  			{"heap", "int", 0, false, false, "0"},
   787  			{"createdAt", "datetime", 0, false, false, ""},
   788  		}, nil,
   789  	)
   790  
   791  	createTable("perfchunks", "", "",
   792  		[]tC{
   793  			{"low", "int", 0, false, false, "0"},
   794  			{"high", "int", 0, false, false, "0"},
   795  			{"avg", "int", 0, false, false, "0"},
   796  			{"createdAt", "datetime", 0, false, false, ""},
   797  		}, nil,
   798  	)
   799  
   800  	createTable("sync", "", "",
   801  		[]tC{
   802  			{"last_update", "datetime", 0, false, false, ""},
   803  		}, nil,
   804  	)
   805  
   806  	createTable("updates", "", "",
   807  		[]tC{
   808  			{"dbVersion", "int", 0, false, false, "0"},
   809  		}, nil,
   810  	)
   811  
   812  	createTable("meta", "", "",
   813  		[]tC{
   814  			ccol("name", 200, ""),
   815  			ccol("value", 200, ""),
   816  		}, nil,
   817  	)
   818  
   819  	/*createTable("tables", "", "",
   820  		[]tC{
   821  			{"id", "int", 0, false, true, ""},
   822  			ccol("name", 200, ""),
   823  		},
   824  		[]tK{
   825  			{"id", "primary", "", false},
   826  			{"name", "unique", "", false},
   827  		},
   828  	)*/
   829  
   830  	return err
   831  }