mbkauthe 4.9.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/db.sql CHANGED
@@ -1,276 +1,328 @@
1
+ -- imports
1
2
 
2
- DO $$
3
- BEGIN
4
- IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role') THEN
5
- CREATE TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest', 'member');
6
- END IF;
7
- END
8
- $$;
3
+ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
9
4
 
5
+
6
+ -- Table: Users
10
7
  CREATE TABLE IF NOT EXISTS "Users" (
11
- id SERIAL PRIMARY KEY,
12
- "UserName" VARCHAR(50) NOT NULL UNIQUE,
13
- "Active" BOOLEAN DEFAULT FALSE,
14
- "Role" role DEFAULT 'NormalUser' NOT NULL,
15
- "HaveMailAccount" BOOLEAN DEFAULT FALSE,
16
- "AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
17
- "created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
18
- "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
19
- "last_login" TIMESTAMP WITH TIME ZONE,
20
- -- Store password hashes only (PBKDF2 / future algorithms). Do not store plaintext passwords.
21
- "PasswordEnc" VARCHAR(255),
22
-
23
- "FullName" VARCHAR(255),
24
- "email" TEXT DEFAULT 'support@mbktech.org',
25
- "Image" TEXT DEFAULT 'https://portal.mbktech.org/Assets/Images/M.png',
26
- "Bio" TEXT DEFAULT 'I am ....',
27
- "SocialAccounts" TEXT DEFAULT '{}',
28
- "Positions" jsonb DEFAULT '{"Not_Permanent":"Member Is Not Permanent"}',
29
- "resetToken" TEXT,
30
- "resetTokenExpires" TimeStamp,
31
- "resetAttempts" INTEGER DEFAULT '0',
32
- "lastResetAttempt" TimeStamp WITH TIME ZONE
8
+ id integer GENERATED ALWAYS AS IDENTITY NOT NULL,
9
+ "UserName" text,
10
+ "Password" text DEFAULT '12345670'::text,
11
+ "Active" boolean DEFAULT false,
12
+ "Role" text DEFAULT 'NormalUser'::text,
13
+ "HaveMailAccount" boolean DEFAULT false,
14
+ "AllowedApps" jsonb DEFAULT '["Portal", "mbkauthe"]'::jsonb,
15
+ created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
16
+ updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
17
+ last_login timestamp with time zone,
18
+ "PasswordEnc" character varying(255),
19
+ "FullName" character varying(100),
20
+ email text DEFAULT 'support@mbktech.org'::text,
21
+ "Image" text DEFAULT 'https://portal.mbktech.org/icon.svg'::text,
22
+ "Bio" text DEFAULT 'I am ....'::text,
23
+ "SocialAccounts" text DEFAULT '{}'::text,
24
+ "Positions" jsonb DEFAULT '{"Not_Permanent": "Member Is Not Permanent"}'::jsonb,
25
+ CONSTRAINT "Users_pkey" PRIMARY KEY (id),
26
+ CONSTRAINT "Users_UserName_key" UNIQUE ("UserName")
33
27
  );
34
-
35
-
36
- CREATE INDEX IF NOT EXISTS idx_users_username ON "Users" USING BTREE ("UserName");
37
- CREATE INDEX IF NOT EXISTS idx_users_role ON "Users" USING BTREE ("Role");
38
- CREATE INDEX IF NOT EXISTS idx_users_active ON "Users" USING BTREE ("Active");
39
- CREATE INDEX IF NOT EXISTS idx_users_email ON "Users" USING BTREE ("email");
40
- CREATE INDEX IF NOT EXISTS idx_users_last_login ON "Users" USING BTREE (last_login);
41
- -- JSONB GIN indexes for common filters/queries on JSON fields
42
- CREATE INDEX IF NOT EXISTS idx_users_allowedapps_gin ON "Users" USING GIN ("AllowedApps");
43
- CREATE INDEX IF NOT EXISTS idx_users_positions_gin ON "Users" USING GIN ("Positions");
44
-
45
- -- OAuth user tables (depend on "Users")
46
- -- GitHub users table
47
- CREATE TABLE IF NOT EXISTS user_github (
48
- id SERIAL PRIMARY KEY,
49
- user_name VARCHAR(50) REFERENCES "Users"("UserName"),
50
- github_id VARCHAR(255) UNIQUE,
51
- github_username VARCHAR(255),
52
- installation_id BIGINT,
53
- installation_target_type VARCHAR(32),
54
- access_token TEXT,
55
- created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
56
- updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
28
+ CREATE INDEX IF NOT EXISTS idx_users_active ON "Users" USING btree ("Active");
29
+ CREATE INDEX IF NOT EXISTS idx_users_allowedapps_gin ON "Users" USING gin ("AllowedApps");
30
+ CREATE INDEX IF NOT EXISTS idx_users_email ON "Users" USING btree (email);
31
+ CREATE INDEX IF NOT EXISTS idx_users_last_login ON "Users" USING btree (last_login);
32
+ CREATE INDEX IF NOT EXISTS idx_users_positions_gin ON "Users" USING gin ("Positions");
33
+ CREATE INDEX IF NOT EXISTS idx_users_role ON "Users" USING btree ("Role");
34
+ CREATE INDEX IF NOT EXISTS idx_users_username_cover ON "Users" USING btree ("UserName") INCLUDE ("Active", "Role");
35
+ COMMENT ON TABLE "Users" IS '[core]';
36
+
37
+ -- Table: ApiTokens
38
+ CREATE TABLE IF NOT EXISTS "ApiTokens" (
39
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
40
+ "UserName" character varying(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
41
+ "Name" character varying(255) NOT NULL,
42
+ "TokenHash" character varying(128) NOT NULL,
43
+ "Prefix" character varying(32) NOT NULL,
44
+ "Permissions" jsonb DEFAULT '{"scope": "read-only", "allowedApps": null}'::jsonb NOT NULL,
45
+ "LastUsed" timestamp with time zone,
46
+ "CreatedAt" timestamp with time zone DEFAULT now(),
47
+ "ExpiresAt" timestamp with time zone,
48
+ CONSTRAINT "ApiTokens_pkey" PRIMARY KEY (id),
49
+ CONSTRAINT "ApiTokens_TokenHash_key" UNIQUE ("TokenHash"),
50
+ CONSTRAINT chk_apitokens_permissions_scope CHECK ((("Permissions" ->> 'scope'::text) = ANY (ARRAY['read-only'::text, 'write'::text]))),
51
+ CONSTRAINT chk_apitokens_name_not_empty CHECK ((length(TRIM(BOTH FROM "Name")) > 0)),
52
+ CONSTRAINT chk_apitokens_expires_future CHECK ((("ExpiresAt" IS NULL) OR ("ExpiresAt" > "CreatedAt")))
57
53
  );
58
-
59
- ALTER TABLE user_github
60
- ADD COLUMN IF NOT EXISTS installation_id BIGINT,
61
- ADD COLUMN IF NOT EXISTS installation_target_type VARCHAR(32);
62
-
63
- -- Add indexes for performance optimization
64
- CREATE INDEX IF NOT EXISTS idx_user_github_github_id ON user_github (github_id);
65
- CREATE INDEX IF NOT EXISTS idx_user_github_user_name ON user_github (user_name);
66
-
67
- -- Google users table
68
- CREATE TABLE IF NOT EXISTS user_google (
69
- id SERIAL PRIMARY KEY,
70
- user_name VARCHAR(50) REFERENCES "Users"("UserName"),
71
- google_id VARCHAR(255) UNIQUE,
72
- google_email VARCHAR(255),
73
- access_token TEXT,
74
- created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
75
- updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
54
+ CREATE INDEX IF NOT EXISTS idx_apitokens_expires ON "ApiTokens" USING btree ("ExpiresAt") WHERE ("ExpiresAt" IS NOT NULL);
55
+ CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_gin ON "ApiTokens" USING gin ("Permissions");
56
+ CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_scope ON "ApiTokens" USING btree ((("Permissions" ->> 'scope'::text)));
57
+ CREATE INDEX IF NOT EXISTS idx_apitokens_username_created ON "ApiTokens" USING btree ("UserName", "CreatedAt" DESC);
58
+ COMMENT ON TABLE "ApiTokens" IS '[core]';
59
+
60
+ -- Table: PasswordResets
61
+ CREATE TABLE IF NOT EXISTS "PasswordResets" (
62
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
63
+ "UserName" character varying(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
64
+ "resetToken" text,
65
+ "resetTokenExpires" timestamp with time zone,
66
+ "resetAttempts" integer DEFAULT 0,
67
+ "lastResetAttempt" timestamp with time zone,
68
+ created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
69
+ CONSTRAINT "PasswordResets_pkey" PRIMARY KEY (id)
76
70
  );
71
+ CREATE INDEX IF NOT EXISTS idx_password_resets_token ON "PasswordResets" USING btree ("resetToken");
72
+ COMMENT ON TABLE "PasswordResets" IS '[core]';
77
73
 
78
- -- Add indexes for performance optimization
79
- CREATE INDEX IF NOT EXISTS idx_user_google_google_id ON user_google (google_id);
80
- CREATE INDEX IF NOT EXISTS idx_user_google_user_name ON user_google (user_name);
81
-
82
-
83
- -- Application Sessions table (stores multiple concurrent sessions per user)
84
- -- Note: this is separate from the express-session store table named "session"
74
+ -- Table: Sessions
85
75
  CREATE TABLE IF NOT EXISTS "Sessions" (
86
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- requires pgcrypto or uuid-ossp
87
- "UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
88
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
89
- expires_at TIMESTAMP WITH TIME ZONE,
90
- meta JSONB
76
+ id uuid DEFAULT gen_random_uuid() NOT NULL,
77
+ "UserName" character varying(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
78
+ created_at timestamp with time zone DEFAULT now(),
79
+ expires_at timestamp with time zone,
80
+ meta jsonb,
81
+ CONSTRAINT "Sessions_pkey" PRIMARY KEY (id)
91
82
  );
83
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON "Sessions" USING btree (expires_at) WHERE (expires_at IS NOT NULL);
84
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_created ON "Sessions" USING btree ("UserName", created_at);
85
+ CREATE INDEX IF NOT EXISTS idx_sessions_username_expires ON "Sessions" USING btree ("UserName", expires_at);
86
+ COMMENT ON TABLE "Sessions" IS '[core]';
92
87
 
93
- -- Indexes optimized by username instead of numeric user id
94
- CREATE INDEX IF NOT EXISTS idx_sessions_username ON "Sessions" ("UserName");
95
- CREATE INDEX IF NOT EXISTS idx_sessions_user_created ON "Sessions" ("UserName", created_at);
96
-
97
- -- Support expiry-based cleanup and validity checks
98
- CREATE INDEX IF NOT EXISTS idx_sessions_username_expires
99
- ON "Sessions" ("UserName", expires_at);
100
-
101
- CREATE INDEX IF NOT EXISTS idx_sessions_expires
102
- ON "Sessions" (expires_at)
103
- WHERE expires_at IS NOT NULL;
104
-
105
- -- Optional (Postgres 11+): covering indexes for hot-path lookups (validateSession)
106
- -- These can enable index-only scans for the exact columns used in auth middleware.
107
- CREATE INDEX IF NOT EXISTS idx_sessions_id_cover
108
- ON "Sessions" (id)
109
- INCLUDE ("UserName", expires_at);
110
-
111
- CREATE INDEX IF NOT EXISTS idx_users_username_cover
112
- ON "Users" ("UserName")
113
- INCLUDE ("Active", "Role");
114
-
115
-
116
- CREATE TABLE IF NOT EXISTS "session" (
117
- sid VARCHAR(33) PRIMARY KEY NOT NULL,
118
- sess JSONB NOT NULL,
119
- expire TimeStamp WITH TIME ZONE NOT NULL,
120
- username TEXT,
121
- last_activity TimeStamp WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
88
+ -- Table: TrustedDevices
89
+ CREATE TABLE IF NOT EXISTS "TrustedDevices" (
90
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
91
+ "UserName" character varying(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
92
+ "DeviceToken" character varying(64) NOT NULL,
93
+ "DeviceName" character varying(255),
94
+ "UserAgent" text,
95
+ "IpAddress" character varying(45),
96
+ "CreatedAt" timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
97
+ "ExpiresAt" timestamp with time zone NOT NULL,
98
+ "LastUsed" timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
99
+ CONSTRAINT "TrustedDevices_pkey" PRIMARY KEY (id),
100
+ CONSTRAINT "TrustedDevices_DeviceToken_key" UNIQUE ("DeviceToken")
122
101
  );
102
+ CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires ON "TrustedDevices" USING btree ("ExpiresAt");
103
+ CREATE INDEX IF NOT EXISTS idx_trusted_devices_username_expires ON "TrustedDevices" USING btree ("UserName", "ExpiresAt");
104
+ CREATE INDEX IF NOT EXISTS idx_trusted_devices_token_user_expires ON "TrustedDevices" USING btree ("DeviceToken", "UserName", "ExpiresAt");
105
+ COMMENT ON TABLE "TrustedDevices" IS '[core]';
123
106
 
124
- -- Add indexes for performance optimization
125
- CREATE INDEX IF NOT EXISTS idx_session_expire ON "session" ("expire");
126
- CREATE INDEX IF NOT EXISTS idx_session_user_id ON "session" ((sess->'user'->>'id'));
127
-
128
-
129
-
107
+ -- Table: TwoFA
130
108
  CREATE TABLE IF NOT EXISTS "TwoFA" (
131
- "UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
132
- "TwoFAStatus" boolean NOT NULL,
133
- "TwoFASecret" TEXT
109
+ "UserName" text NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
110
+ "TwoFAStatus" boolean DEFAULT false NOT NULL,
111
+ "TwoFASecret" text,
112
+ CONSTRAINT "TwoFA_pkey" PRIMARY KEY ("UserName")
134
113
  );
114
+ CREATE INDEX IF NOT EXISTS idx_twofa_username_status ON "TwoFA" USING btree ("UserName", "TwoFAStatus");
115
+ COMMENT ON TABLE "TwoFA" IS '[core]';
116
+
117
+ -- Table: ai_history_chatapi
118
+ CREATE TABLE IF NOT EXISTS ai_history_chatapi (
119
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
120
+ conversation_id uuid DEFAULT uuid_generate_v4() NOT NULL,
121
+ conversation_history jsonb NOT NULL,
122
+ created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
123
+ updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
124
+ username character varying(100) NOT NULL,
125
+ is_deleted boolean DEFAULT false,
126
+ CONSTRAINT ai_history_chatapi_pkey PRIMARY KEY (id)
127
+ );
128
+ CREATE INDEX IF NOT EXISTS idx_chat_deleted ON ai_history_chatapi USING btree (is_deleted);
129
+ CREATE INDEX IF NOT EXISTS idx_chat_history_gin ON ai_history_chatapi USING gin (conversation_history);
130
+ CREATE INDEX IF NOT EXISTS idx_chat_username ON ai_history_chatapi USING btree (username);
131
+ COMMENT ON TABLE "ai_history_chatapi" IS '[core]';
132
+
133
+ -- Table: app_access_requests
134
+ CREATE TABLE IF NOT EXISTS app_access_requests (
135
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
136
+ username character varying(255) NOT NULL,
137
+ app character varying(255) NOT NULL,
138
+ message text,
139
+ status character varying(20) DEFAULT 'pending'::character varying,
140
+ admin_notes text,
141
+ reviewed_by character varying(255),
142
+ created_at timestamp without time zone DEFAULT now(),
143
+ updated_at timestamp without time zone DEFAULT now(),
144
+ CONSTRAINT app_access_requests_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying])::text[]))),
145
+ CONSTRAINT app_access_requests_pkey PRIMARY KEY (id)
146
+ );
147
+ COMMENT ON TABLE "app_access_requests" IS '[core]';
135
148
 
136
- -- Add indexes for performance optimization
137
- CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName");
138
- CREATE INDEX IF NOT EXISTS idx_twofa_username_status ON "TwoFA" ("UserName", "TwoFAStatus");
139
-
140
-
141
-
142
- CREATE TABLE IF NOT EXISTS "TrustedDevices" (
143
- "id" SERIAL PRIMARY KEY,
144
- "UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
145
- "DeviceToken" VARCHAR(64) UNIQUE NOT NULL,
146
- "DeviceName" VARCHAR(255),
147
- "UserAgent" TEXT,
148
- "IpAddress" VARCHAR(45),
149
- "CreatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
150
- "ExpiresAt" TIMESTAMP WITH TIME ZONE NOT NULL,
151
- "LastUsed" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
149
+ -- Table: plan_upgrade_requests
150
+ CREATE TABLE IF NOT EXISTS plan_upgrade_requests (
151
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
152
+ username character varying(255) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
153
+ curr_role character varying(50) NOT NULL,
154
+ requested_plan character varying(50) NOT NULL,
155
+ req_role character varying(50) NOT NULL,
156
+ reason text NOT NULL,
157
+ experience text,
158
+ portfolio character varying(500),
159
+ linkedin character varying(500),
160
+ github character varying(500),
161
+ additional_info text,
162
+ status character varying(20) DEFAULT 'pending'::character varying,
163
+ admin_notes text,
164
+ reviewed_by character varying(255),
165
+ created_at timestamp without time zone DEFAULT now(),
166
+ updated_at timestamp without time zone DEFAULT now(),
167
+ CONSTRAINT plan_upgrade_requests_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying])::text[]))),
168
+ CONSTRAINT plan_upgrade_requests_pkey PRIMARY KEY (id)
152
169
  );
170
+ CREATE INDEX IF NOT EXISTS idx_created_at ON plan_upgrade_requests USING btree (created_at);
171
+ CREATE INDEX IF NOT EXISTS idx_req_role_status ON plan_upgrade_requests USING btree (req_role, status);
172
+ CREATE INDEX IF NOT EXISTS idx_requested_plan ON plan_upgrade_requests USING btree (requested_plan);
173
+ CREATE INDEX IF NOT EXISTS idx_status ON plan_upgrade_requests USING btree (status);
174
+ CREATE INDEX IF NOT EXISTS idx_status_created_at ON plan_upgrade_requests USING btree (status, created_at DESC);
175
+ CREATE INDEX IF NOT EXISTS idx_username_status ON plan_upgrade_requests USING btree (username, status);
176
+ COMMENT ON TABLE "plan_upgrade_requests" IS '[core]';
177
+
178
+ -- Table: session
179
+ CREATE TABLE IF NOT EXISTS session (
180
+ sid character varying NOT NULL,
181
+ sess json NOT NULL,
182
+ expire timestamp(6) without time zone NOT NULL,
183
+ username text REFERENCES "Users"("UserName") ON DELETE CASCADE,
184
+ last_activity timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
185
+ CONSTRAINT session_pkey PRIMARY KEY (sid)
186
+ );
187
+ CREATE INDEX IF NOT EXISTS idx_session_expire ON session USING btree (expire);
188
+ CREATE INDEX IF NOT EXISTS idx_session_last_activity ON session USING btree (last_activity);
189
+ CREATE INDEX IF NOT EXISTS idx_session_user_id ON session USING btree ((((sess -> 'user'::text) ->> 'id'::text)));
190
+ COMMENT ON TABLE "session" IS '[core]';
191
+
192
+ -- Table: todos
193
+ CREATE TABLE IF NOT EXISTS todos (
194
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
195
+ username character varying(50) NOT NULL REFERENCES "Users"("UserName"),
196
+ title character varying(255) NOT NULL,
197
+ description text,
198
+ completed boolean DEFAULT false,
199
+ type character varying(20) DEFAULT 'personal'::character varying,
200
+ assigned boolean DEFAULT false,
201
+ assigneduser character varying(50) DEFAULT 'none'::character varying,
202
+ created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
203
+ updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
204
+ CONSTRAINT todos_type_check CHECK (((type)::text = ANY ((ARRAY['personal'::character varying, 'admin'::character varying])::text[]))),
205
+ CONSTRAINT todos_pkey PRIMARY KEY (id)
206
+ );
207
+ CREATE INDEX IF NOT EXISTS idx_todos_assigned ON todos USING btree (assigned);
208
+ CREATE INDEX IF NOT EXISTS idx_todos_assigneduser ON todos USING btree (assigneduser);
209
+ CREATE INDEX IF NOT EXISTS idx_todos_completed ON todos USING btree (completed);
210
+ CREATE INDEX IF NOT EXISTS idx_todos_description ON todos USING btree (description);
211
+ CREATE INDEX IF NOT EXISTS idx_todos_title ON todos USING btree (title);
212
+ CREATE INDEX IF NOT EXISTS idx_todos_type ON todos USING btree (type);
213
+ CREATE INDEX IF NOT EXISTS idx_todos_type_completed ON todos USING btree (type, completed);
214
+ CREATE INDEX IF NOT EXISTS idx_todos_username_type ON todos USING btree (username, type);
215
+ COMMENT ON TABLE "todos" IS '[core]';
216
+
217
+ -- Table: user_github
218
+ CREATE TABLE IF NOT EXISTS user_github (
219
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
220
+ user_name text REFERENCES "Users"("UserName") ON DELETE CASCADE,
221
+ github_id character varying(255),
222
+ github_username character varying(255),
223
+ access_token text,
224
+ created_at timestamp without time zone DEFAULT now(),
225
+ updated_at timestamp without time zone DEFAULT now(),
226
+ installation_id bigint,
227
+ installation_target_type character varying(32),
228
+ CONSTRAINT user_github_pkey PRIMARY KEY (id),
229
+ CONSTRAINT user_github_github_id_key UNIQUE (github_id)
230
+ );
231
+ CREATE INDEX IF NOT EXISTS idx_user_github_user_name ON user_github USING btree (user_name);
232
+ COMMENT ON TABLE "user_github" IS '[core]';
153
233
 
154
- -- Add indexes for performance optimization
155
- CREATE INDEX IF NOT EXISTS idx_trusted_devices_token ON "TrustedDevices"("DeviceToken");
156
- CREATE INDEX IF NOT EXISTS idx_trusted_devices_username ON "TrustedDevices"("UserName");
157
- CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires ON "TrustedDevices"("ExpiresAt");
234
+ -- Table: user_google
235
+ CREATE TABLE IF NOT EXISTS user_google (
236
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
237
+ user_name character varying(50) REFERENCES "Users"("UserName"),
238
+ google_id character varying(255),
239
+ google_email character varying(255),
240
+ access_token text,
241
+ created_at timestamp with time zone DEFAULT now(),
242
+ updated_at timestamp with time zone DEFAULT now(),
243
+ CONSTRAINT user_google_pkey PRIMARY KEY (id),
244
+ CONSTRAINT user_google_google_id_key UNIQUE (google_id)
245
+ );
246
+ COMMENT ON TABLE "user_google" IS '[core]';
247
+
248
+
249
+ -- Table: categories
250
+ CREATE TABLE IF NOT EXISTS categories (
251
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
252
+ name character varying(255) NOT NULL,
253
+ slug character varying(255) NOT NULL,
254
+ description text,
255
+ image_url character varying(255),
256
+ created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
257
+ updated_at timestamp without time zone,
258
+ CONSTRAINT categories_pkey PRIMARY KEY (id),
259
+ CONSTRAINT categories_name_key UNIQUE (name),
260
+ CONSTRAINT categories_slug_key UNIQUE (slug)
261
+ );
262
+ COMMENT ON TABLE "categories" IS '[core]';
263
+
264
+ -- Table: markdowns
265
+ CREATE TABLE IF NOT EXISTS markdowns (
266
+ category character varying(255) NOT NULL,
267
+ filename character varying(255) NOT NULL,
268
+ content text,
269
+ CONSTRAINT markdowns_pkey PRIMARY KEY (category, filename)
270
+ );
271
+ CREATE INDEX IF NOT EXISTS idx_markdowns_category ON markdowns USING btree (category);
272
+ CREATE INDEX IF NOT EXISTS idx_markdowns_filename ON markdowns USING btree (filename);
273
+ COMMENT ON TABLE "markdowns" IS '[core]';
274
+
275
+ -- Table: notifications
276
+ CREATE TABLE IF NOT EXISTS notifications (
277
+ id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
278
+ username character varying(50) NOT NULL,
279
+ type character varying(20) NOT NULL,
280
+ message text NOT NULL,
281
+ is_read boolean DEFAULT false,
282
+ created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'UTC'::text),
283
+ CONSTRAINT notifications_type_check CHECK (((type)::text = ANY ((ARRAY['info'::character varying, 'warning'::character varying, 'error'::character varying, 'success'::character varying])::text[]))),
284
+ CONSTRAINT notifications_pkey PRIMARY KEY (id)
285
+ );
286
+ CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications USING btree (created_at);
287
+ CREATE INDEX IF NOT EXISTS idx_notifications_username ON notifications USING btree (username);
288
+ COMMENT ON TABLE "notifications" IS '[core]';
289
+
290
+ -- Table: profile_content
291
+ CREATE TABLE IF NOT EXISTS profile_content (
292
+ username character varying(255) NOT NULL,
293
+ filename character varying(255) NOT NULL,
294
+ content text,
295
+ CONSTRAINT profile_content_pkey PRIMARY KEY (username, filename)
296
+ );
297
+ COMMENT ON TABLE "profile_content" IS '[core]';
298
+
299
+ -- Table: website_access_logs
300
+ CREATE TABLE IF NOT EXISTS website_access_logs (
301
+ log_id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
302
+ ip_address character varying(50),
303
+ page_url text,
304
+ user_agent text,
305
+ referrer text,
306
+ method character varying(10),
307
+ status_code integer,
308
+ browser_language character varying(255),
309
+ user_id integer,
310
+ action_type character varying(255),
311
+ access_timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
312
+ username character varying(255),
313
+ session_id text,
314
+ created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
315
+ CONSTRAINT website_access_logs_pkey PRIMARY KEY (log_id)
316
+ );
317
+ CREATE INDEX IF NOT EXISTS idx_website_access_logs_action_type ON website_access_logs USING btree (action_type);
318
+ CREATE INDEX IF NOT EXISTS idx_website_access_logs_session_id ON website_access_logs USING btree (session_id);
319
+ CREATE INDEX IF NOT EXISTS idx_website_access_logs_timestamp ON website_access_logs USING btree (access_timestamp);
320
+ CREATE INDEX IF NOT EXISTS idx_website_access_logs_user_id ON website_access_logs USING btree (user_id);
321
+ COMMENT ON TABLE "website_access_logs" IS '[core]';
158
322
 
159
323
 
160
324
  -- Seed users (hash-only). Default passwords are "12345678" for the sample accounts below.
161
- -- Hashes were generated using mbkauthe's `hashPassword(password, username)`.
325
+ -- Hashes were generated using mbkauthe's "hashPassword(password, username)" function.
162
326
  INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount", "FullName")
163
327
  VALUES ('support', 'b8b10c1c9006d8c30ab81c412463c65ff6dae3293d9bfbaf5fd8e275081d0947f000a828004e2fbd3a8f6ef5a35ae3eddd4c57b00ecab376b12e607a16a57459', 'SuperAdmin', true, false, 'Support User')
164
328
  ON CONFLICT ("UserName") DO NOTHING;
165
-
166
-
167
- -- API Tokens for persistent programmatic access
168
- CREATE TABLE IF NOT EXISTS "ApiTokens" (
169
- "id" SERIAL PRIMARY KEY,
170
- "UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
171
- "Name" VARCHAR(255) NOT NULL CHECK (LENGTH(TRIM("Name")) > 0),
172
- "TokenHash" VARCHAR(128) NOT NULL UNIQUE,
173
- "Prefix" VARCHAR(32) NOT NULL,
174
- "Permissions" JSONB NOT NULL DEFAULT '{"scope":"read-only","allowedApps":null}'::jsonb
175
- CHECK ("Permissions"->>'scope' IN ('read-only', 'write')),
176
- "LastUsed" TIMESTAMP WITH TIME ZONE,
177
- "CreatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
178
- "ExpiresAt" TIMESTAMP WITH TIME ZONE
179
- CHECK ("ExpiresAt" IS NULL OR "ExpiresAt" > "CreatedAt")
180
- );
181
-
182
- -- Basic indexes
183
- CREATE INDEX IF NOT EXISTS idx_apitokens_tokenhash
184
- ON "ApiTokens" ("TokenHash");
185
-
186
- CREATE INDEX IF NOT EXISTS idx_apitokens_username
187
- ON "ApiTokens" ("UserName");
188
-
189
- -- Performance indexes
190
- CREATE INDEX IF NOT EXISTS idx_apitokens_tokenhash_expires
191
- ON "ApiTokens" ("TokenHash", "ExpiresAt")
192
- WHERE "ExpiresAt" IS NOT NULL;
193
-
194
- CREATE INDEX IF NOT EXISTS idx_apitokens_username_created
195
- ON "ApiTokens" ("UserName", "CreatedAt" DESC);
196
-
197
- CREATE INDEX IF NOT EXISTS idx_apitokens_expires
198
- ON "ApiTokens" ("ExpiresAt")
199
- WHERE "ExpiresAt" IS NOT NULL;
200
-
201
- -- JSONB indexes for fast permission queries
202
- CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_gin
203
- ON "ApiTokens" USING GIN ("Permissions");
204
-
205
- CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_scope
206
- ON "ApiTokens" (("Permissions"->>'scope'));
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
- -- WebPortal user table
221
-
222
- -- todos table for user tasks, with indexes for performance optimization
223
- CREATE TABLE IF NOT EXISTS "todos" (
224
- "id" SERIAL PRIMARY KEY,
225
- "username" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName"),
226
- "title" VARCHAR(255) NOT NULL,
227
- "description" TEXT,
228
- "completed" BOOLEAN DEFAULT false,
229
- "type" VARCHAR(20) DEFAULT 'personal' CHECK ("type" IN ('personal', 'admin')),
230
- "assigned" BOOLEAN DEFAULT false,
231
- "assigneduser" VARCHAR(50) DEFAULT 'none',
232
- "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
233
- "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
234
- );
235
-
236
- CREATE INDEX IF NOT EXISTS idx_todos_username ON "todos" USING BTREE ("username");
237
- CREATE INDEX IF NOT EXISTS idx_todos_type ON "todos" USING BTREE ("type");
238
- CREATE INDEX IF NOT EXISTS idx_todos_completed ON "todos" USING BTREE ("completed");
239
- CREATE INDEX IF NOT EXISTS idx_todos_type_completed ON "todos" USING BTREE ("type", "completed");
240
- CREATE INDEX IF NOT EXISTS idx_todos_assigned ON "todos" USING BTREE ("assigned");
241
- CREATE INDEX IF NOT EXISTS idx_todos_assigneduser ON "todos" USING BTREE ("assigneduser");
242
- CREATE INDEX IF NOT EXISTS idx_todos_username_type ON "todos" USING BTREE ("username", "type");
243
- CREATE INDEX IF NOT EXISTS idx_todos_title ON "todos" USING BTREE ("title");
244
- CREATE INDEX IF NOT EXISTS idx_todos_description ON "todos" USING BTREE ("description");
245
-
246
-
247
-
248
-
249
-
250
- -- Plan upgrade requests table
251
- CREATE TABLE IF NOT EXISTS plan_upgrade_requests (
252
- id SERIAL PRIMARY KEY,
253
- username VARCHAR(50) REFERENCES "Users"("UserName"),
254
- curr_role VARCHAR(50) NOT NULL,
255
- requested_plan VARCHAR(50) NOT NULL,
256
- req_role VARCHAR(50) NOT NULL,
257
- reason TEXT NOT NULL,
258
- experience TEXT,
259
- portfolio VARCHAR(500),
260
- linkedin VARCHAR(500),
261
- github VARCHAR(500),
262
- additional_info TEXT,
263
- status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
264
- admin_notes TEXT,
265
- reviewed_by VARCHAR(255),
266
- created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
267
- updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
268
- );
269
-
270
- CREATE INDEX IF NOT EXISTS idx_username ON plan_upgrade_requests("username");
271
- CREATE INDEX IF NOT EXISTS idx_status ON plan_upgrade_requests(status);
272
- CREATE INDEX IF NOT EXISTS idx_created_at ON plan_upgrade_requests(created_at);
273
- CREATE INDEX IF NOT EXISTS idx_requested_plan ON plan_upgrade_requests(requested_plan);
274
- CREATE INDEX IF NOT EXISTS idx_status_created_at ON plan_upgrade_requests(status, created_at DESC);
275
- CREATE INDEX IF NOT EXISTS idx_username_status ON plan_upgrade_requests("username", status);
276
- CREATE INDEX IF NOT EXISTS idx_req_role_status ON plan_upgrade_requests("req_role", status);
package/index.js CHANGED
@@ -5,6 +5,7 @@ import path from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { renderError, renderPage } from "#response.js";
7
7
  import { packageJson } from "#config.js";
8
+ import { createLogger } from "./lib/utils/logger.js";
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
@@ -12,6 +13,7 @@ const isDevMode = process.env.test === "dev";
12
13
  const DEV_PORT = 5555;
13
14
  const viewsPath = path.join(__dirname, "views");
14
15
  const packageVersion = packageJson.version;
16
+ const logServer = createLogger("server");
15
17
 
16
18
  const app = express();
17
19
 
@@ -58,7 +60,7 @@ const renderDevError = (res, req, code, error, message, page, details) => render
58
60
  });
59
61
 
60
62
  if (isDevMode) {
61
- console.log(`[mbkauthe] Dev mode is enabled. Starting server in dev mode.`);
63
+ logServer(`Dev mode is enabled. Starting server in dev mode.`);
62
64
 
63
65
  app.get(["/dashboard", "/home", "/"], (req, res) => res.redirect("/mbkauthe/"));
64
66
 
@@ -75,12 +77,12 @@ if (isDevMode) {
75
77
  ));
76
78
 
77
79
  app.use((req, res) => {
78
- console.log(`[mbkauthe] Path not found: ${req.method} ${req.url}`);
80
+ logServer(`Path not found: ${req.method} ${req.url}`);
79
81
  renderDevError(res, req, 404, "Not Found", "The requested page was not found.", "/mbkauthe/login");
80
82
  });
81
83
 
82
84
  app.listen(DEV_PORT, () => {
83
- console.log(`[mbkauthe] Server running on http://localhost:${DEV_PORT}`);
85
+ logServer(`Server running on http://localhost:${DEV_PORT}`);
84
86
  });
85
87
  }
86
88