Skip to content

Commit 3dba586

Browse files
committed
feat: add account folders and tags
1 parent 4f3a8f6 commit 3dba586

22 files changed

Lines changed: 1119 additions & 26 deletions

backend/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import adminRoutes from './routes/admin';
1111
import userApiRoutes from './routes/userApi';
1212
import botRoutes from './routes/bot';
1313
import notificationRoutes from './routes/notifications';
14+
import accountOrganizationRoutes from './routes/accountOrganization';
1415
import { wsHub } from './services/wsHub';
1516
import { getBearerToken, verifySessionToken } from './utils/jwt';
1617
import { isProd } from './config/env';
@@ -97,6 +98,7 @@ export async function buildApp() {
9798
await app.register(userApiRoutes);
9899
await app.register(botRoutes);
99100
await app.register(notificationRoutes);
101+
await app.register(accountOrganizationRoutes);
100102

101103
app.setErrorHandler((error, _request, reply) => {
102104
app.log.error(error);

backend/src/db/bootstrap.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,55 @@ async function getColumnType(tableName: string, columnName: string): Promise<str
3434
return rows[0]?.column_type ?? null;
3535
}
3636

37+
async function hasConstraint(tableName: string, constraintName: string): Promise<boolean> {
38+
const rows = await queryRows<{ cnt: number }[]>(
39+
`SELECT COUNT(*) AS cnt
40+
FROM information_schema.TABLE_CONSTRAINTS
41+
WHERE TABLE_SCHEMA = DATABASE()
42+
AND TABLE_NAME = ?
43+
AND CONSTRAINT_NAME = ?`,
44+
[tableName, constraintName]
45+
);
46+
47+
return Number(rows[0]?.cnt ?? 0) > 0;
48+
}
49+
3750
async function ensureSchemaUpgrades(): Promise<void> {
51+
await execute(
52+
`CREATE TABLE IF NOT EXISTS account_folders (
53+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
54+
user_id BIGINT UNSIGNED NOT NULL,
55+
name VARCHAR(48) NOT NULL,
56+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
57+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
58+
UNIQUE KEY uq_account_folders_user_name (user_id, name),
59+
CONSTRAINT fk_account_folders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
60+
)`
61+
);
62+
63+
await execute(
64+
`CREATE TABLE IF NOT EXISTS account_tags (
65+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
66+
user_id BIGINT UNSIGNED NOT NULL,
67+
name VARCHAR(48) NOT NULL,
68+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
69+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
70+
UNIQUE KEY uq_account_tags_user_name (user_id, name),
71+
CONSTRAINT fk_account_tags_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
72+
)`
73+
);
74+
75+
await execute(
76+
`CREATE TABLE IF NOT EXISTS account_tag_assignments (
77+
account_id BIGINT UNSIGNED NOT NULL,
78+
tag_id BIGINT UNSIGNED NOT NULL,
79+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
80+
PRIMARY KEY (account_id, tag_id),
81+
CONSTRAINT fk_account_tag_assignments_account FOREIGN KEY (account_id) REFERENCES user_accounts(id) ON DELETE CASCADE,
82+
CONSTRAINT fk_account_tag_assignments_tag FOREIGN KEY (tag_id) REFERENCES account_tags(id) ON DELETE CASCADE
83+
)`
84+
);
85+
3886
if (!(await hasColumn('user_accounts', 'encrypted_revocation_code'))) {
3987
await execute(
4088
'ALTER TABLE user_accounts ADD COLUMN encrypted_revocation_code LONGTEXT NULL AFTER encrypted_ma'
@@ -47,6 +95,16 @@ async function ensureSchemaUpgrades(): Promise<void> {
4795
);
4896
}
4997

98+
if (!(await hasColumn('user_accounts', 'folder_id'))) {
99+
await execute('ALTER TABLE user_accounts ADD COLUMN folder_id BIGINT UNSIGNED NULL AFTER steamid');
100+
}
101+
102+
if (!(await hasConstraint('user_accounts', 'fk_user_accounts_folder'))) {
103+
await execute(
104+
'ALTER TABLE user_accounts ADD CONSTRAINT fk_user_accounts_folder FOREIGN KEY (folder_id) REFERENCES account_folders(id) ON DELETE SET NULL'
105+
);
106+
}
107+
50108
const hasLegacyAutoConfirm = await hasColumn('user_accounts', 'auto_confirm');
51109
const hasAutoConfirmTrades = await hasColumn('user_accounts', 'auto_confirm_trades');
52110
const hasAutoConfirmLogins = await hasColumn('user_accounts', 'auto_confirm_logins');

backend/src/jobs/confirmationPoller.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { decodeAccountSession, encodeAccountSession } from '../utils/accountSess
66
import { parseMaFile } from '../utils/mafile';
77
import {
88
generateSteamCode,
9+
hasAutomaticSessionRecovery,
910
listConfirmationsWithSessionRecovery,
1011
respondToConfirmationWithSessionRecovery
1112
} from '../services/steamService';
@@ -18,6 +19,7 @@ import {
1819
} from '../services/telegramCopy';
1920
import { wsHub } from '../services/wsHub';
2021
import { sendTelegramMessage } from '../services/telegramService';
22+
import { buildSessionExpiredMessage, clearSessionExpiredNotifications } from '../services/sessionNotificationService';
2123

2224
let timer: NodeJS.Timeout | null = null;
2325
let running = false;
@@ -137,6 +139,7 @@ async function runCycle(app: FastifyInstance): Promise<void> {
137139
);
138140

139141
for (const account of accounts) {
142+
let automaticRecoveryAvailable = false;
140143
try {
141144
const ma = parseMaFile(
142145
decryptForUser(account.encrypted_ma, account.password_hash, Number(account.user_id))
@@ -150,6 +153,7 @@ async function runCycle(app: FastifyInstance): Promise<void> {
150153
const session = sessions[0]
151154
? decodeAccountSession(sessions[0].session_json, account.password_hash, Number(account.user_id))
152155
: null;
156+
automaticRecoveryAvailable = hasAutomaticSessionRecovery(ma, session);
153157
const confirmationResult = await listConfirmationsWithSessionRecovery(ma, session);
154158
const confirmations = confirmationResult.confirmations;
155159
const nextSession = confirmationResult.session;
@@ -161,6 +165,7 @@ async function runCycle(app: FastifyInstance): Promise<void> {
161165
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
162166
[account.id, encodeAccountSession(nextSession, account.password_hash, Number(account.user_id))]
163167
);
168+
await clearSessionExpiredNotifications(Number(account.user_id), Number(account.id));
164169
}
165170
const byKind: Record<'trade' | 'login' | 'other', Set<string>> = {
166171
trade: new Set(),
@@ -297,6 +302,7 @@ async function runCycle(app: FastifyInstance): Promise<void> {
297302
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
298303
[account.id, encodeAccountSession(response.session, account.password_hash, Number(account.user_id))]
299304
);
305+
await clearSessionExpiredNotifications(Number(account.user_id), Number(account.id));
300306
}
301307

302308
if (!response.success) {
@@ -336,7 +342,7 @@ async function runCycle(app: FastifyInstance): Promise<void> {
336342
const payload = {
337343
accountId: account.id,
338344
accountAlias: account.alias,
339-
message: 'Steam session expired. Open account details and update session.'
345+
message: buildSessionExpiredMessage(automaticRecoveryAvailable, account.language)
340346
};
341347

342348
await execute(

0 commit comments

Comments
 (0)