Viktoria435 commited on
Commit
916df2b
·
1 Parent(s): 22df730

Refactor serialization logic in BookService, VisitorService, and WorkerService

Browse files

- Simplified the serialization of book, visitor, and worker objects by removing unnecessary asynchronous calls to serializeTxtRow.
- Updated the writeLines method to directly use the synchronous map function for improved performance.
- Removed unused key-manager, crypto, lzss, and offset-encryption utility files to clean up the codebase.

src/book/book.service.ts CHANGED
@@ -34,7 +34,7 @@ export class BookService {
34
  status: BookStatus.AVAILABLE,
35
  };
36
  const lines = await this.file.readLines();
37
- lines.push(await serializeTxtRow(newBook));
38
  await this.file.writeLines(lines);
39
  return newBook;
40
  }
@@ -44,9 +44,7 @@ export class BookService {
44
  const index = books.findIndex((b) => b.id === id);
45
  if (index === -1) return null;
46
  books[index] = { ...books[index], ...dto };
47
- await this.file.writeLines(
48
- await Promise.all(books.map((book) => serializeTxtRow(book))),
49
- );
50
  return books[index];
51
  }
52
 
@@ -55,9 +53,7 @@ export class BookService {
55
  const index = books.findIndex((b) => b.id === id);
56
  if (index === -1) return false;
57
  books.splice(index, 1);
58
- await this.file.writeLines(
59
- await Promise.all(books.map((book) => serializeTxtRow(book))),
60
- );
61
  return true;
62
  }
63
 
@@ -81,9 +77,7 @@ export class BookService {
81
  borrowed.push(book);
82
  }
83
 
84
- await this.file.writeLines(
85
- await Promise.all(books.map((book) => serializeTxtRow(book))),
86
- );
87
  return borrowed;
88
  }
89
 
@@ -115,9 +109,7 @@ export class BookService {
115
  returned.push(book);
116
  }
117
 
118
- await this.file.writeLines(
119
- await Promise.all(books.map((book) => serializeTxtRow(book))),
120
- );
121
  return returned;
122
  }
123
  }
 
34
  status: BookStatus.AVAILABLE,
35
  };
36
  const lines = await this.file.readLines();
37
+ lines.push(serializeTxtRow(newBook));
38
  await this.file.writeLines(lines);
39
  return newBook;
40
  }
 
44
  const index = books.findIndex((b) => b.id === id);
45
  if (index === -1) return null;
46
  books[index] = { ...books[index], ...dto };
47
+ await this.file.writeLines(books.map(serializeTxtRow));
 
 
48
  return books[index];
49
  }
50
 
 
53
  const index = books.findIndex((b) => b.id === id);
54
  if (index === -1) return false;
55
  books.splice(index, 1);
56
+ await this.file.writeLines(books.map(serializeTxtRow));
 
 
57
  return true;
58
  }
59
 
 
77
  borrowed.push(book);
78
  }
79
 
80
+ await this.file.writeLines(books.map(serializeTxtRow));
 
 
81
  return borrowed;
82
  }
83
 
 
109
  returned.push(book);
110
  }
111
 
112
+ await this.file.writeLines(books.map(serializeTxtRow));
 
 
113
  return returned;
114
  }
115
  }
src/common/key-manager.ts DELETED
@@ -1,51 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import {
4
- decryptWithAlphabet,
5
- encryptWithAlphabet,
6
- } from 'src/utils/offset-encryption.util';
7
-
8
- const dataDir = path.join(process.cwd(), 'src/data/encrypted/');
9
- const keyFile = path.join(dataDir, '.encryption_key');
10
-
11
- const MASTER_KEY = process.env.MASTER_KEY || 'RSIOT_SECRET_KEY_VARIAHT1';
12
-
13
- export async function ensureDataDir(): Promise<void> {
14
- try {
15
- await fs.access(dataDir);
16
- } catch {
17
- await fs.mkdir(dataDir, { recursive: true });
18
- }
19
- }
20
-
21
- export async function getOrCreateKey(): Promise<string> {
22
- await ensureDataDir();
23
-
24
- try {
25
- const encryptedKey = await fs.readFile(keyFile, 'utf-8');
26
- return decryptWithAlphabet(encryptedKey, MASTER_KEY);
27
- } catch (err: unknown) {
28
- if (err instanceof Error && err.message.includes('ENOENT')) {
29
- const newKey = generateRandomKey(16);
30
- await saveKey(newKey);
31
- return newKey;
32
- }
33
- throw err;
34
- }
35
- }
36
-
37
- export async function saveKey(key: string): Promise<void> {
38
- await ensureDataDir();
39
- const encryptedKey = encryptWithAlphabet(key, MASTER_KEY);
40
- await fs.writeFile(keyFile, encryptedKey, 'utf-8');
41
- }
42
-
43
- export function generateRandomKey(length: number): string {
44
- const chars =
45
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
46
- let key = '';
47
- for (let i = 0; i < length; i++) {
48
- key += chars.charAt(Math.floor(Math.random() * chars.length));
49
- }
50
- return key;
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/crypto.utils.ts DELETED
@@ -1,23 +0,0 @@
1
- import { lzssEncode, lzssDecode } from './lzss.util';
2
- import {
3
- encryptWithAlphabet,
4
- decryptWithAlphabet,
5
- } from './offset-encryption.util';
6
- import { getOrCreateKey } from 'src/common/key-manager';
7
-
8
- export async function encryptData(data: string): Promise<string> {
9
- const compressed = lzssEncode(data);
10
- const key = await getOrCreateKey();
11
- const encrypted = encryptWithAlphabet(compressed, key);
12
-
13
- return encrypted;
14
- }
15
-
16
- export async function decryptData(encrypted: string): Promise<string> {
17
- const key = await getOrCreateKey();
18
- const decrypted = decryptWithAlphabet(encrypted, key);
19
- const decompressed = lzssDecode(decrypted);
20
- console.log('decompressed', decompressed);
21
-
22
- return decompressed;
23
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/file.utils.ts CHANGED
@@ -1,13 +1,11 @@
1
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
  import { Link } from '../common/Link';
3
- import { decryptData, encryptData } from './crypto.utils';
4
-
5
  function isLinkString(value: string | undefined) {
6
  if (!value) return false;
7
  return /^[a-zA-Z]+:[^:]+$/.test(value);
8
  }
9
 
10
- function parseTxtRowInternal(row: string): any {
11
  if (typeof row !== 'string') {
12
  throw new Error('parseTxtRow: row is not string: ' + JSON.stringify(row));
13
  }
@@ -53,7 +51,7 @@ function parseTxtRowInternal(row: string): any {
53
  return obj;
54
  }
55
 
56
- function serializeTxtRowInternal(obj: any): string {
57
  return Object.entries(obj)
58
  .map(([key, value]) => {
59
  if (value instanceof Link) {
@@ -76,13 +74,3 @@ function serializeTxtRowInternal(obj: any): string {
76
  })
77
  .join(';');
78
  }
79
-
80
- export async function parseTxtRow(encryptedRow: string): Promise<any> {
81
- const decrypted = await decryptData(encryptedRow);
82
- return parseTxtRowInternal(decrypted);
83
- }
84
-
85
- export async function serializeTxtRow(obj: any): Promise<string> {
86
- const serialized = serializeTxtRowInternal(obj);
87
- return await encryptData(serialized);
88
- }
 
1
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
  import { Link } from '../common/Link';
 
 
3
  function isLinkString(value: string | undefined) {
4
  if (!value) return false;
5
  return /^[a-zA-Z]+:[^:]+$/.test(value);
6
  }
7
 
8
+ export function parseTxtRow(row: string): any {
9
  if (typeof row !== 'string') {
10
  throw new Error('parseTxtRow: row is not string: ' + JSON.stringify(row));
11
  }
 
51
  return obj;
52
  }
53
 
54
+ export function serializeTxtRow(obj: any): string {
55
  return Object.entries(obj)
56
  .map(([key, value]) => {
57
  if (value instanceof Link) {
 
74
  })
75
  .join(';');
76
  }
 
 
 
 
 
 
 
 
 
 
src/utils/lzss.util.ts DELETED
@@ -1,165 +0,0 @@
1
- export interface LZSSMatch {
2
- offset: number;
3
- length: number;
4
- }
5
-
6
- export interface LZSSToken {
7
- type: 'literal' | 'match';
8
- value: string | LZSSMatch;
9
- }
10
-
11
- // Параметры LZSS
12
- const WINDOW_SIZE = 4096; // Размер окна поиска (12 бит)
13
- const LOOKAHEAD_SIZE = 18; // Размер буфера упреждения
14
- const MIN_MATCH_LENGTH = 3; // Минимальная длина совпадения
15
-
16
- export function lzssEncode(text: string): string {
17
- if (!text) return '';
18
-
19
- const tokens: LZSSToken[] = [];
20
- let pos = 0;
21
-
22
- while (pos < text.length) {
23
- let bestMatch: LZSSMatch | null = null;
24
- let bestLength = 0;
25
-
26
- // Определяем начало окна поиска
27
- const windowStart = Math.max(0, pos - WINDOW_SIZE);
28
- const lookaheadEnd = Math.min(text.length, pos + LOOKAHEAD_SIZE);
29
-
30
- // Ищем самое длинное совпадение в окне
31
- for (let i = windowStart; i < pos; i++) {
32
- let matchLength = 0;
33
-
34
- // Считаем длину совпадения
35
- while (
36
- pos + matchLength < lookaheadEnd &&
37
- text[i + matchLength] === text[pos + matchLength]
38
- ) {
39
- matchLength++;
40
- }
41
-
42
- // Если найдено лучшее совпадение
43
- if (matchLength >= MIN_MATCH_LENGTH && matchLength > bestLength) {
44
- bestLength = matchLength;
45
- bestMatch = {
46
- offset: pos - i,
47
- length: matchLength,
48
- };
49
- }
50
- }
51
-
52
- if (bestMatch) {
53
- tokens.push({
54
- type: 'match',
55
- value: bestMatch,
56
- });
57
- pos += bestMatch.length;
58
- } else {
59
- tokens.push({
60
- type: 'literal',
61
- value: text[pos],
62
- });
63
- pos++;
64
- }
65
- }
66
-
67
- // Кодируем токены в бинарную строку
68
- return encodeTokens(tokens);
69
- }
70
-
71
- function encodeTokens(tokens: LZSSToken[]): string {
72
- let result = '';
73
-
74
- for (const token of tokens) {
75
- if (token.type === 'literal') {
76
- // Флаг 1 означает литерал, затем 8 бит символа
77
- result += '1';
78
- const char = token.value as string;
79
- const charCode = char.charCodeAt(0);
80
- result += charCode.toString(2).padStart(16, '0'); // 16 бит для Unicode
81
- } else {
82
- // Флаг 0 означает совпадение
83
- result += '0';
84
- const match = token.value as LZSSMatch;
85
- // 12 бит для смещения, 6 бит для длины (максимум 18+3=21)
86
- result += match.offset.toString(2).padStart(12, '0');
87
- result += (match.length - MIN_MATCH_LENGTH).toString(2).padStart(6, '0');
88
- }
89
- }
90
-
91
- return result;
92
- }
93
-
94
- export function lzssDecode(encoded: string): string {
95
- if (!encoded) return '';
96
-
97
- let result = '';
98
- let pos = 0;
99
-
100
- while (pos < encoded.length) {
101
- const flag = encoded[pos];
102
- pos++;
103
-
104
- if (flag === '1') {
105
- // Литерал: читаем 16 бит
106
- if (pos + 16 > encoded.length) {
107
- console.error('Неполный литерал на позиции', pos);
108
- break;
109
- }
110
- const charCode = parseInt(encoded.substring(pos, pos + 16), 2);
111
- result += String.fromCharCode(charCode);
112
- pos += 16;
113
- } else if (flag === '0') {
114
- // Совпадение: читаем 12 бит смещения и 6 бит длины
115
- if (pos + 18 > encoded.length) {
116
- console.error('Неполное совпадение на позиции', pos);
117
- break;
118
- }
119
-
120
- const offset = parseInt(encoded.substring(pos, pos + 12), 2);
121
- pos += 12;
122
- const length =
123
- parseInt(encoded.substring(pos, pos + 6), 2) + MIN_MATCH_LENGTH;
124
- pos += 6;
125
-
126
- // Копируем из уже декодированной части
127
- const startPos = result.length - offset;
128
-
129
- if (startPos < 0) {
130
- console.error(
131
- `Некорректное смещение: ${offset} на позиции результата ${result.length}`,
132
- );
133
- break;
134
- }
135
-
136
- for (let i = 0; i < length; i++) {
137
- result += result[startPos + i];
138
- }
139
- } else {
140
- console.error('Неизвестный флаг:', flag);
141
- break;
142
- }
143
- }
144
-
145
- return result;
146
- }
147
-
148
- // Функция для статистики сжатия
149
- export function getLZSSStats(
150
- original: string,
151
- encoded: string,
152
- ): {
153
- originalSize: number;
154
- compressedSize: number;
155
- compressionRatio: number;
156
- } {
157
- const originalSize = original.length * 16; // 16 бит на символ (UTF-16)
158
- const compressedSize = encoded.length;
159
-
160
- return {
161
- originalSize,
162
- compressedSize,
163
- compressionRatio: compressedSize / originalSize,
164
- };
165
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/offset-encryption.util.ts DELETED
@@ -1,55 +0,0 @@
1
- export const CUSTOM_ALPHABET =
2
- 'abcdefghijklmnopqrstuvwxyz' +
3
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
4
- '0123456789' +
5
- '.,;:=|!?()[]{}"\'-_/\\@#$%^&* ';
6
-
7
- const alphabetMap = new Map<string, number>();
8
-
9
- for (let i = 0; i < CUSTOM_ALPHABET.length; i++) {
10
- alphabetMap.set(CUSTOM_ALPHABET[i], i + 1);
11
- }
12
-
13
- function getPos(char: string): number {
14
- const pos = alphabetMap.get(char);
15
- if (!pos) throw new Error(`Character "${char}" is missing in the alphabet`);
16
- return pos;
17
- }
18
-
19
- function getChar(pos: number): string {
20
- return CUSTOM_ALPHABET[pos - 1];
21
- }
22
-
23
- export function encryptWithAlphabet(text: string, key: string): string {
24
- let result = '';
25
- const N = CUSTOM_ALPHABET.length;
26
-
27
- for (let i = 0; i < text.length; i++) {
28
- const tPos = getPos(text[i]);
29
- const kPos = getPos(key[i % key.length]);
30
-
31
- let encPos = tPos + kPos;
32
- if (encPos > N) encPos -= N;
33
-
34
- result += getChar(encPos);
35
- }
36
-
37
- return result;
38
- }
39
-
40
- export function decryptWithAlphabet(encrypted: string, key: string): string {
41
- let result = '';
42
- const N = CUSTOM_ALPHABET.length;
43
-
44
- for (let i = 0; i < encrypted.length; i++) {
45
- const ePos = getPos(encrypted[i]);
46
- const kPos = getPos(key[i % key.length]);
47
-
48
- let decPos = ePos - kPos;
49
- if (decPos <= 0) decPos += N;
50
-
51
- result += getChar(decPos);
52
- }
53
-
54
- return result;
55
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/visitor/visitor.service.ts CHANGED
@@ -46,7 +46,7 @@ export class VisitorService {
46
  };
47
 
48
  const lines = await this.file.readLines();
49
- lines.push(await serializeTxtRow(newVisitor));
50
  await this.file.writeLines(lines);
51
 
52
  return newVisitor;
@@ -59,9 +59,7 @@ export class VisitorService {
59
  throw new NotFoundException(`Visitor with id ${id} not found`);
60
 
61
  visitors[index] = { ...visitors[index], ...dto };
62
- await this.file.writeLines(
63
- await Promise.all(visitors.map((visitor) => serializeTxtRow(visitor))),
64
- );
65
  return visitors[index];
66
  }
67
 
@@ -78,9 +76,7 @@ export class VisitorService {
78
  }
79
 
80
  visitors.splice(index, 1);
81
- await this.file.writeLines(
82
- await Promise.all(visitors.map((visitor) => serializeTxtRow(visitor))),
83
- );
84
  }
85
 
86
  async addCurrentBooks(visitorId: string, books: Book[]): Promise<void> {
@@ -94,9 +90,7 @@ export class VisitorService {
94
  visitors[visitorIndex].currentBooks.push(
95
  ...books.map((b) => bookLinkManager.toLink(b.id)),
96
  );
97
- await this.file.writeLines(
98
- await Promise.all(visitors.map((visitor) => serializeTxtRow(visitor))),
99
- );
100
  }
101
 
102
  async moveToHistory(visitorId: string, books: Book[]): Promise<void> {
@@ -124,9 +118,7 @@ export class VisitorService {
124
  visitor.history.push(removedBook);
125
  }
126
 
127
- await this.file.writeLines(
128
- await Promise.all(visitors.map((visitor) => serializeTxtRow(visitor))),
129
- );
130
  }
131
 
132
  async hasBook(visitorId: string, bookId: string): Promise<boolean> {
 
46
  };
47
 
48
  const lines = await this.file.readLines();
49
+ lines.push(serializeTxtRow(newVisitor));
50
  await this.file.writeLines(lines);
51
 
52
  return newVisitor;
 
59
  throw new NotFoundException(`Visitor with id ${id} not found`);
60
 
61
  visitors[index] = { ...visitors[index], ...dto };
62
+ await this.file.writeLines(visitors.map(serializeTxtRow));
 
 
63
  return visitors[index];
64
  }
65
 
 
76
  }
77
 
78
  visitors.splice(index, 1);
79
+ await this.file.writeLines(visitors.map(serializeTxtRow));
 
 
80
  }
81
 
82
  async addCurrentBooks(visitorId: string, books: Book[]): Promise<void> {
 
90
  visitors[visitorIndex].currentBooks.push(
91
  ...books.map((b) => bookLinkManager.toLink(b.id)),
92
  );
93
+ await this.file.writeLines(visitors.map(serializeTxtRow));
 
 
94
  }
95
 
96
  async moveToHistory(visitorId: string, books: Book[]): Promise<void> {
 
118
  visitor.history.push(removedBook);
119
  }
120
 
121
+ await this.file.writeLines(visitors.map(serializeTxtRow));
 
 
122
  }
123
 
124
  async hasBook(visitorId: string, bookId: string): Promise<boolean> {
src/worker/worker.service.ts CHANGED
@@ -74,9 +74,7 @@ export class WorkerService {
74
  };
75
 
76
  workers.push(newWorker);
77
- await this.file.writeLines(
78
- await Promise.all(workers.map((worker) => serializeTxtRow(worker))),
79
- );
80
 
81
  return newWorker;
82
  }
@@ -94,9 +92,7 @@ export class WorkerService {
94
  ...workers[index],
95
  ...dto,
96
  };
97
- await this.file.writeLines(
98
- await Promise.all(workers.map((worker) => serializeTxtRow(worker))),
99
- );
100
  return workers[index];
101
  }
102
 
@@ -107,9 +103,7 @@ export class WorkerService {
107
  if (index === -1) return false;
108
 
109
  workers.splice(index, 1);
110
- await this.file.writeLines(
111
- await Promise.all(workers.map((worker) => serializeTxtRow(worker))),
112
- );
113
 
114
  return true;
115
  }
@@ -135,8 +129,6 @@ export class WorkerService {
135
 
136
  workers[index] = target;
137
 
138
- await this.file.writeLines(
139
- await Promise.all(workers.map((worker) => serializeTxtRow(worker))),
140
- );
141
  }
142
  }
 
74
  };
75
 
76
  workers.push(newWorker);
77
+ await this.file.writeLines(workers.map(serializeTxtRow));
 
 
78
 
79
  return newWorker;
80
  }
 
92
  ...workers[index],
93
  ...dto,
94
  };
95
+ await this.file.writeLines(workers.map(serializeTxtRow));
 
 
96
  return workers[index];
97
  }
98
 
 
103
  if (index === -1) return false;
104
 
105
  workers.splice(index, 1);
106
+ await this.file.writeLines(workers.map(serializeTxtRow));
 
 
107
 
108
  return true;
109
  }
 
129
 
130
  workers[index] = target;
131
 
132
+ await this.file.writeLines(workers.map(serializeTxtRow));
 
 
133
  }
134
  }