update project
This commit is contained in:
@@ -12,7 +12,7 @@ const config: Knex.Config = {
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
user: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
database: process.env.DB_NAME || 'cryptowallet_v2',
|
||||
database: process.env.DB_NAME || 'cryptowallet',
|
||||
},
|
||||
migrations: {
|
||||
directory: path.resolve(__dirname, 'migrations'),
|
||||
|
||||
@@ -3,21 +3,12 @@ import type { Knex } from 'knex';
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable('users', (t) => {
|
||||
t.string('id', 26).primary();
|
||||
t.string('email', 255).notNullable().unique();
|
||||
t.string('password_hash', 255).notNullable();
|
||||
t.string('last_name', 128).nullable();
|
||||
t.string('first_name', 128).nullable();
|
||||
t.string('middle_name', 128).nullable();
|
||||
t.date('birth_date').nullable();
|
||||
t.string('crypto_wallet', 255).nullable();
|
||||
t.string('phone', 16).nullable();
|
||||
t.string('bik', 9).nullable();
|
||||
t.string('account_number', 20).nullable();
|
||||
t.string('card_number', 19).nullable();
|
||||
t.string('inn', 12).nullable();
|
||||
t.boolean('kyc_verified').notNullable().defaultTo(false);
|
||||
t.timestamp('kyc_verified_at', { useTz: true }).nullable();
|
||||
t.boolean('is_deleted').notNullable().defaultTo(false);
|
||||
t.string('username', 64).notNullable().unique();
|
||||
t.text('password_hash').notNullable();
|
||||
t.text('pin_hash').notNullable();
|
||||
t.text('encrypted_vault').notNullable();
|
||||
t.string('vault_salt', 128).notNullable();
|
||||
t.boolean('mnemonic_shown').notNullable().defaultTo(false);
|
||||
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
|
||||
@@ -3,22 +3,16 @@ import type { Knex } from 'knex';
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable('sessions', (t) => {
|
||||
t.string('id', 26).primary();
|
||||
t.string('sid', 26).notNullable().unique();
|
||||
t.string('user_id', 26).notNullable().references('id').inTable('users').onDelete('CASCADE');
|
||||
t.string('device_id', 26).nullable();
|
||||
t.string('user_agent', 500).nullable();
|
||||
t.string('first_ip', 64).nullable();
|
||||
t.string('last_ip', 64).nullable();
|
||||
t.timestamp('last_seen_at', { useTz: true }).nullable();
|
||||
t.timestamp('revoked_at', { useTz: true }).nullable();
|
||||
t.string('refresh_jti_hash', 255).nullable();
|
||||
t.timestamp('refresh_expires_at', { useTz: true }).nullable();
|
||||
t.text('refresh_token_hash').notNullable();
|
||||
t.string('user_agent').nullable();
|
||||
t.specificType('ip_address', 'inet').nullable();
|
||||
t.timestamp('expires_at', { useTz: true }).notNullable();
|
||||
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
|
||||
await knex.schema.raw('CREATE INDEX idx_sessions_user_id ON sessions(user_id)');
|
||||
await knex.schema.raw('CREATE INDEX idx_sessions_sid ON sessions(sid)');
|
||||
await knex.schema.raw('CREATE INDEX idx_sessions_expires ON sessions(expires_at)');
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
|
||||
18
apps/api/src/db/migrations/004_create_login_attempts.ts
Normal file
18
apps/api/src/db/migrations/004_create_login_attempts.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable('login_attempts', (t) => {
|
||||
t.string('id', 26).primary();
|
||||
t.string('username', 64).notNullable();
|
||||
t.specificType('ip_address', 'inet').notNullable();
|
||||
t.boolean('success').notNullable().defaultTo(false);
|
||||
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
|
||||
await knex.schema.raw('CREATE INDEX idx_login_attempts_username_created ON login_attempts(username, created_at)');
|
||||
await knex.schema.raw('CREATE INDEX idx_login_attempts_ip_created ON login_attempts(ip_address, created_at)');
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists('login_attempts');
|
||||
}
|
||||
33
apps/api/src/db/migrations/005_simplify_users_for_bitok.ts
Normal file
33
apps/api/src/db/migrations/005_simplify_users_for_bitok.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('users', (t) => {
|
||||
t.dropColumn('username');
|
||||
t.dropColumn('password_hash');
|
||||
t.dropColumn('pin_hash');
|
||||
|
||||
t.string('bitok_user_id', 26).notNullable().unique();
|
||||
t.string('email', 255).nullable();
|
||||
t.boolean('kyc_verified').notNullable().defaultTo(false);
|
||||
t.string('kyc_level', 20).nullable();
|
||||
t.boolean('deleted').notNullable().defaultTo(false);
|
||||
|
||||
t.index(['bitok_user_id'], 'idx_users_bitok_user_id');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('users', (t) => {
|
||||
t.dropIndex(['bitok_user_id'], 'idx_users_bitok_user_id');
|
||||
|
||||
t.dropColumn('bitok_user_id');
|
||||
t.dropColumn('email');
|
||||
t.dropColumn('kyc_verified');
|
||||
t.dropColumn('kyc_level');
|
||||
t.dropColumn('deleted');
|
||||
|
||||
t.string('username', 64).notNullable().unique();
|
||||
t.text('password_hash').notNullable();
|
||||
t.text('pin_hash').notNullable();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists('sessions');
|
||||
await knex.schema.dropTableIfExists('login_attempts');
|
||||
|
||||
await knex.schema.createTable('processed_events', (t) => {
|
||||
t.string('event_id', 26).primary();
|
||||
t.string('event_type', 64).notNullable();
|
||||
t.string('payload_hash', 64).notNullable();
|
||||
t.timestamp('processed_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists('processed_events');
|
||||
|
||||
await knex.schema.createTable('sessions', (t) => {
|
||||
t.string('id', 26).primary();
|
||||
t.string('user_id', 26).notNullable().references('id').inTable('users').onDelete('CASCADE');
|
||||
t.text('refresh_token_hash').notNullable();
|
||||
t.string('user_agent').nullable();
|
||||
t.specificType('ip_address', 'inet').nullable();
|
||||
t.timestamp('expires_at', { useTz: true }).notNullable();
|
||||
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
|
||||
await knex.schema.createTable('login_attempts', (t) => {
|
||||
t.string('id', 26).primary();
|
||||
t.string('username', 64).notNullable();
|
||||
t.specificType('ip_address', 'inet').notNullable();
|
||||
t.boolean('success').notNullable().defaultTo(false);
|
||||
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
49
apps/api/src/db/reset-db.ts
Normal file
49
apps/api/src/db/reset-db.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import knex from 'knex';
|
||||
|
||||
// Load .env from repo root (works when running from apps/api)
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../../../.env') });
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
|
||||
|
||||
const dbName = process.env.DB_NAME || 'cryptowallet_devphase3';
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(dbName)) {
|
||||
console.error('[DB Reset] Invalid DB_NAME');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseConnection = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
user: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
};
|
||||
|
||||
async function reset() {
|
||||
const admin = knex({
|
||||
client: 'pg',
|
||||
connection: { ...baseConnection, database: 'postgres' },
|
||||
});
|
||||
|
||||
try {
|
||||
await admin.raw(
|
||||
`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ? AND pid <> pg_backend_pid()`,
|
||||
[dbName]
|
||||
);
|
||||
} catch {
|
||||
// Ignore if no connections
|
||||
}
|
||||
|
||||
const safeName = dbName.replace(/"/g, '""');
|
||||
await admin.raw(`DROP DATABASE IF EXISTS "${safeName}"`);
|
||||
await admin.raw(`CREATE DATABASE "${safeName}"`);
|
||||
|
||||
await admin.destroy();
|
||||
console.log('[DB Reset] Database dropped and recreated:', dbName);
|
||||
}
|
||||
|
||||
reset().catch((err: unknown) => {
|
||||
console.error('[DB Reset] Failed:', err instanceof Error ? err.message : String(err));
|
||||
if (err instanceof Error && err.stack) console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user