rokojori-auth/source/server/db.ts

150 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-07-13 03:48:45 +00:00
import fs from 'fs';
import path from 'path';
import { randomUUID } from 'crypto';
const DATA_DIR = path.join( __dirname, '..', '..', 'build', 'data' );
if ( !fs.existsSync( DATA_DIR ) ) fs.mkdirSync( DATA_DIR, { recursive: true } );
export interface UserProduct
{
id: string;
acquiredAt: string;
source: string;
}
export interface User
{
id: string;
email: string;
passwordHash: string;
roles: string[];
products: UserProduct[];
settings: Record<string, unknown>;
createdAt: string;
}
export interface RefreshToken
{
token: string;
userId: string;
expiresAt: string;
}
export interface ResetToken
{
token: string;
userId: string;
expiresAt: string;
}
function read<T>( table: string ): T[]
{
const file = path.join( DATA_DIR, `${table}.json` );
if ( !fs.existsSync( file ) ) return [];
return JSON.parse( fs.readFileSync( file, 'utf8' ) ) as T[];
}
function write<T>( table: string, rows: T[] ): void
{
fs.writeFileSync( path.join( DATA_DIR, `${table}.json` ), JSON.stringify( rows, null, 2 ) );
}
export const users =
{
all: (): User[] => read<User>( 'users' ),
findById: ( id: string ): User | undefined =>
read<User>( 'users' ).find( u => u.id === id ),
findByEmail: ( email: string ): User | undefined =>
read<User>( 'users' ).find( u => u.email.toLowerCase() === email.toLowerCase() ),
create( email: string, passwordHash: string ): User
{
const rows = read<User>( 'users' );
if ( rows.find( u => u.email.toLowerCase() === email.toLowerCase() ) ) throw new Error( 'Taken' );
const user: User =
{
id: randomUUID(),
email,
passwordHash,
roles: [ 'user' ],
products: [],
settings: {},
createdAt: new Date().toISOString()
};
write( 'users', [ ...rows, user ] );
return user;
},
update( id: string, patch: Partial<Omit<User, 'id' | 'createdAt'>> ): void
{
const rows = read<User>( 'users' ).map( u => u.id === id ? { ...u, ...patch } : u );
write( 'users', rows );
},
delete( id: string ): void
{
write( 'users', read<User>( 'users' ).filter( u => u.id !== id ) );
}
};
export const refreshTokens =
{
find: ( token: string ): RefreshToken | undefined =>
read<RefreshToken>( 'refreshTokens' ).find( t => t.token === token ),
2026-07-17 18:34:36 +00:00
create( userId: string, ttlMs: number = 30 * 24 * 60 * 60 * 1000 ): RefreshToken
2026-07-13 03:48:45 +00:00
{
const token: RefreshToken =
{
token: randomUUID(),
userId,
2026-07-17 18:34:36 +00:00
expiresAt: new Date( Date.now() + ttlMs ).toISOString()
2026-07-13 03:48:45 +00:00
};
2026-07-17 18:34:36 +00:00
2026-07-13 03:48:45 +00:00
write( 'refreshTokens', [ ...read<RefreshToken>( 'refreshTokens' ), token ] );
2026-07-17 18:34:36 +00:00
2026-07-13 03:48:45 +00:00
return token;
},
delete( token: string ): void
{
write( 'refreshTokens', read<RefreshToken>( 'refreshTokens' ).filter( t => t.token !== token ) );
},
deleteForUser( userId: string ): void
{
write( 'refreshTokens', read<RefreshToken>( 'refreshTokens' ).filter( t => t.userId !== userId ) );
}
};
export const resetTokens =
{
find: ( token: string ): ResetToken | undefined =>
read<ResetToken>( 'resetTokens' ).find( t => t.token === token ),
create( userId: string ): ResetToken
{
const token: ResetToken =
{
token: randomUUID(),
userId,
expiresAt: new Date( Date.now() + 60 * 60 * 1000 ).toISOString()
};
const existing = read<ResetToken>( 'resetTokens' ).filter( t => t.userId !== userId );
write( 'resetTokens', [ ...existing, token ] );
return token;
},
delete( token: string ): void
{
write( 'resetTokens', read<ResetToken>( 'resetTokens' ).filter( t => t.token !== token ) );
},
deleteForUser( userId: string ): void
{
write( 'resetTokens', read<ResetToken>( 'resetTokens' ).filter( t => t.userId !== userId ) );
2026-07-13 03:48:45 +00:00
}
};