From 5dbdf8d1d28f128e383ffe92776731b7cd0695bb Mon Sep 17 00:00:00 2001 From: Maxime Cannoodt Date: Thu, 18 Aug 2022 13:21:18 +0200 Subject: [PATCH] feat: :sparkles: log user identifier and user plugin version in write event table --- plugin | 2 +- server/package-lock.json | 108 ++++++++- server/package.json | 1 + .../migration.sql | 3 + server/prisma/schema.prisma | 20 +- server/src/app.integration.test.ts | 14 +- .../controllers/note/note.get.controller.ts | 2 +- .../controllers/note/note.post.controller.ts | 107 +++++++-- .../note/note.post.controller.unit.test.ts | 221 ++++++++++++++++++ server/src/db/__mocks__/bloomFilter.dao.ts | 4 + server/src/db/__mocks__/note.dao.ts | 6 + .../src/{controllers/note => db}/note.dao.ts | 2 +- server/src/lib/expiredNoteFilter.unit.test.ts | 5 +- .../logging/EventLogger.integration.test.ts | 2 + server/src/logging/EventLogger.ts | 4 +- server/src/tasks/deleteExpiredNotes.ts | 2 +- .../src/tasks/deleteExpiredNotes.unit.test.ts | 11 +- 17 files changed, 462 insertions(+), 52 deletions(-) create mode 100644 server/prisma/migrations/20220818185115_user_id_user_version_fields/migration.sql create mode 100644 server/src/controllers/note/note.post.controller.unit.test.ts create mode 100644 server/src/db/__mocks__/bloomFilter.dao.ts create mode 100644 server/src/db/__mocks__/note.dao.ts rename server/src/{controllers/note => db}/note.dao.ts (95%) diff --git a/plugin b/plugin index 46e53a6..27f9787 160000 --- a/plugin +++ b/plugin @@ -1 +1 @@ -Subproject commit 46e53a6d662988c9b8461d41ec4ec3e4f3085dff +Subproject commit 27f978720ff5a7ac7039a2fef8f0c44df54ea324 diff --git a/server/package-lock.json b/server/package-lock.json index 3e913d9..5b8f895 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,11 +1,11 @@ { - "name": "obsidian-note-sharing-server", + "name": "obsidian-quickshare-server", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "obsidian-note-sharing-server", + "name": "obsidian-quickshare-server", "version": "0.0.1", "license": "MIT", "dependencies": { @@ -13,6 +13,7 @@ "bloom-filters": "^3.0.0", "body-parser": "^1.20.0", "class-validator": "^0.13.2", + "crc": "^4.1.1", "dotenv": "^16.0.1", "express": "^4.18.1", "express-rate-limit": "^6.4.0", @@ -649,6 +650,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -721,6 +742,30 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1026,6 +1071,17 @@ "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", "dev": true }, + "node_modules/crc": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.1.1.tgz", + "integrity": "sha512-2U3ZqJ2phJl9ANuP2q5VS53LMpNmYU9vcpmh6nutJmsqUREhtWpTRh9yYxG7sDg3xkwaEEXytSeffTxw4cgwPg==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2272,6 +2328,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -5696,6 +5772,12 @@ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "peer": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -5755,6 +5837,16 @@ "fill-range": "^7.0.1" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "peer": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6002,6 +6094,12 @@ "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", "dev": true }, + "crc": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.1.1.tgz", + "integrity": "sha512-2U3ZqJ2phJl9ANuP2q5VS53LMpNmYU9vcpmh6nutJmsqUREhtWpTRh9yYxG7sDg3xkwaEEXytSeffTxw4cgwPg==", + "requires": {} + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -6851,6 +6949,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "peer": true + }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", diff --git a/server/package.json b/server/package.json index f0015e3..603afa1 100644 --- a/server/package.json +++ b/server/package.json @@ -20,6 +20,7 @@ "bloom-filters": "^3.0.0", "body-parser": "^1.20.0", "class-validator": "^0.13.2", + "crc": "^4.1.1", "dotenv": "^16.0.1", "express": "^4.18.1", "express-rate-limit": "^6.4.0", diff --git a/server/prisma/migrations/20220818185115_user_id_user_version_fields/migration.sql b/server/prisma/migrations/20220818185115_user_id_user_version_fields/migration.sql new file mode 100644 index 0000000..b0eae98 --- /dev/null +++ b/server/prisma/migrations/20220818185115_user_id_user_version_fields/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "event" ADD COLUMN "user_id" TEXT; +ALTER TABLE "event" ADD COLUMN "user_plugin_version" TEXT; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 1f0b268..6d38662 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -19,15 +19,17 @@ model EncryptedNote { } model event { - id Int @id @default(autoincrement()) - time DateTime @default(now()) - type String - success Boolean - size_bytes Int? - note_id String? - host String? - error String? - expire_window_days Int? + id Int @id @default(autoincrement()) + time DateTime @default(now()) + type String + success Boolean + size_bytes Int? + note_id String? + host String? + error String? + expire_window_days Int? + user_id String? + user_plugin_version String? } model BloomFilter { diff --git a/server/src/app.integration.test.ts b/server/src/app.integration.test.ts index 7b11163..b42f1b4 100644 --- a/server/src/app.integration.test.ts +++ b/server/src/app.integration.test.ts @@ -4,8 +4,6 @@ import { describe, it, expect } from "vitest"; import prisma from "./db/client"; import { deleteExpiredNotes } from "./tasks/deleteExpiredNotes"; import { EventType } from "./logging/EventLogger"; -import { getFilter } from "./db/bloomFilter.dao"; -import { getExpiredNoteFilter } from "./lib/expiredNoteFilter"; // const testNote with base64 ciphertext and hmac const testNote = { @@ -83,8 +81,12 @@ describe("GET /api/note", () => { }); describe("POST /api/note", () => { - it("returns a view_url on correct POST body", async () => { + it("returns a view_url on correct POST body (without plugin version and user id)", async () => { const res = await supertest(app).post("/api/note").send(testNote); + + if (res.statusCode !== 200) { + console.log(res.body); + } expect(res.statusCode).toBe(200); // Returned body has correct fields @@ -162,8 +164,14 @@ describe("POST /api/note", () => { const responseCodes = responses.map((res) => res.statusCode); // at least one response should be 429 + expect(responseCodes).toContain(200); expect(responseCodes).toContain(429); + // No other response codes should be present + expect( + responseCodes.map((code) => code === 429 || code === 200) + ).not.toContain(false); + // sleep for 100 ms to allow rate limiter to reset await new Promise((resolve) => setTimeout(resolve, 250)); }); diff --git a/server/src/controllers/note/note.get.controller.ts b/server/src/controllers/note/note.get.controller.ts index 9ef3b47..11a473d 100644 --- a/server/src/controllers/note/note.get.controller.ts +++ b/server/src/controllers/note/note.get.controller.ts @@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from "express"; import { getExpiredNoteFilter } from "../../lib/expiredNoteFilter"; import EventLogger from "../../logging/EventLogger"; import { getConnectingIp } from "../../util"; -import { getNote } from "./note.dao"; +import { getNote } from "../../db/note.dao"; export async function getNoteController( req: Request, res: Response, diff --git a/server/src/controllers/note/note.post.controller.ts b/server/src/controllers/note/note.post.controller.ts index 1733aeb..c739d24 100644 --- a/server/src/controllers/note/note.post.controller.ts +++ b/server/src/controllers/note/note.post.controller.ts @@ -1,20 +1,38 @@ import { EncryptedNote } from "@prisma/client"; -import { validateOrReject } from "class-validator"; import { NextFunction, Request, Response } from "express"; -import { IsBase64 } from "class-validator"; -import { createNote } from "./note.dao"; +import { crc16 as crc } from "crc"; +import { createNote } from "../../db/note.dao"; import { addDays, getConnectingIp } from "../../util"; -import EventLogger from "../../logging/EventLogger"; +import EventLogger, { WriteEvent } from "../../logging/EventLogger"; +import { + validateOrReject, + IsBase64, + IsHexadecimal, + IsNotEmpty, + ValidateIf, + ValidationError, + Matches, +} from "class-validator"; /** * Request body for creating a note */ export class NotePostRequest { @IsBase64() + @IsNotEmpty() ciphertext: string | undefined; @IsBase64() + @IsNotEmpty() hmac: string | undefined; + + @ValidateIf((o) => o.user_id != null) + @IsHexadecimal() + user_id: string | undefined; + + @ValidateIf((o) => o.plugin_version != null) + @Matches("^[0-9]+\\.[0-9]+\\.[0-9]+$") + plugin_version: string | undefined; } export async function postNoteController( @@ -22,38 +40,79 @@ export async function postNoteController( res: Response, next: NextFunction ): Promise { - const ip = getConnectingIp(req); + const event: WriteEvent = { + success: false, + host: getConnectingIp(req), + user_id: req.body.user_id, + user_plugin_version: req.body.plugin_version, + }; + // Validate request body const notePostRequest = new NotePostRequest(); Object.assign(notePostRequest, req.body); - validateOrReject(notePostRequest).catch((err) => { - res.status(400).send(err.message); - }); - const note = notePostRequest as EncryptedNote; + try { + await validateOrReject(notePostRequest); + } catch (_err: any) { + const err = _err as ValidationError; + res.status(400).send(err.toString()); + event.error = err.toString(); + EventLogger.writeEvent(event); + return; + } + + // Validate user ID, if present + if (notePostRequest.user_id && !checkId(notePostRequest.user_id)) { + console.log("invalid user id"); + res.status(400).send("Invalid user id (checksum failed)"); + event.error = "Invalid user id (checksum failed)"; + EventLogger.writeEvent(event); + return; + } + + // Create note object const EXPIRE_WINDOW_DAYS = 30; - createNote({ - ...note, + const note = { + ciphertext: notePostRequest.ciphertext as string, + hmac: notePostRequest.hmac as string, expire_time: addDays(new Date(), EXPIRE_WINDOW_DAYS), - }) + } as EncryptedNote; + + // Store note object + createNote(note) .then(async (savedNote) => { - await EventLogger.writeEvent({ - success: true, - host: ip, - note_id: savedNote.id, - size_bytes: savedNote.ciphertext.length + savedNote.hmac.length, - expire_window_days: EXPIRE_WINDOW_DAYS, - }); + event.success = true; + event.note_id = savedNote.id; + event.size_bytes = savedNote.ciphertext.length + savedNote.hmac.length; + event.expire_window_days = EXPIRE_WINDOW_DAYS; + await EventLogger.writeEvent(event); res.json({ view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`, expire_time: savedNote.expire_time, }); }) .catch(async (err) => { - await EventLogger.writeEvent({ - success: false, - host: ip, - error: err.message, - }); + event.error = err.toString(); + await EventLogger.writeEvent(event); next(err); }); } + +/** + * @param id {string} a 16 character base16 string with 12 random characters and 4 CRC characters + * @returns {boolean} true if the id is valid, false otherwise + */ +function checkId(id: string): boolean { + // check length + if (id.length !== 16) { + return false; + } + // extract the random number and the checksum + const random = id.slice(0, 12); + const checksum = id.slice(12, 16); + + // compute the CRC of the random number + const computedChecksum = crc(random).toString(16).padStart(4, "0"); + + // compare the computed checksum with the one in the id + return computedChecksum === checksum; +} diff --git a/server/src/controllers/note/note.post.controller.unit.test.ts b/server/src/controllers/note/note.post.controller.unit.test.ts new file mode 100644 index 0000000..2c23ca6 --- /dev/null +++ b/server/src/controllers/note/note.post.controller.unit.test.ts @@ -0,0 +1,221 @@ +import express from "express"; +import supertest from "supertest"; +import { vi, describe, it, beforeEach, afterEach, expect } from "vitest"; +import * as noteDao from "../../db/note.dao"; +import EventLogger from "../../logging/EventLogger"; +import { NotePostRequest, postNoteController } from "./note.post.controller"; + +vi.mock("../../db/note.dao"); +vi.mock("../../logging/EventLogger"); + +const VALID_CIPHERTEXT = Buffer.from("sample_ciphertext").toString("base64"); +const VALID_HMAC = Buffer.from("sample_hmac").toString("base64"); +const VALID_VERSION = "1.0.0"; +const MALFORMED_VERSION = "v1.0.0"; +const VALID_USER_ID = "f06536e7df6857fc"; +const MALFORMED_ID_WRONG_CRC = "f06536e7df6857fd"; +const MALFORMED_ID_WRONG_LENGTH = "0"; + +const MOCK_NOTE_ID = "1234"; + +type TestParams = { + payload: Partial; + expectedStatus: number; +}; + +const TEST_PAYLOADS: TestParams[] = [ + // Request with valid ciphertext and hmac + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + }, + expectedStatus: 200, + }, + // Request with valid ciphertext, hmac, user id, and plugin version + { + payload: { + ciphertext: VALID_CIPHERTEXT, + + hmac: VALID_HMAC, + user_id: VALID_USER_ID, + plugin_version: VALID_VERSION, + }, + expectedStatus: 200, + }, + // Request with non-base64 ciphertext + { + payload: { + ciphertext: "not_base64", + hmac: VALID_HMAC, + }, + expectedStatus: 400, + }, + // Request with non-base64 hmac + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: "not_base64", + }, + expectedStatus: 400, + }, + // Request with empty ciphertext + { + payload: { + ciphertext: "", + hmac: VALID_HMAC, + }, + expectedStatus: 400, + }, + // Request with empty hmac + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: "", + }, + expectedStatus: 400, + }, + // Request with valid user id + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + user_id: VALID_USER_ID, + }, + expectedStatus: 200, + }, + // Request with malformed user id (wrong crc) + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + user_id: MALFORMED_ID_WRONG_CRC, + }, + expectedStatus: 400, + }, + // Request with malformed user id (wrong length) + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + user_id: MALFORMED_ID_WRONG_LENGTH, + }, + expectedStatus: 400, + }, + // Request with valid plugin version + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + plugin_version: VALID_VERSION, + }, + expectedStatus: 200, + }, + // Request with malformed plugin version + { + payload: { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + plugin_version: MALFORMED_VERSION, + }, + expectedStatus: 400, + }, +]; + +describe("note.post.controller", () => { + let mockNoteDao = vi.mocked(noteDao); + let mockEventLogger = vi.mocked(EventLogger); + + const test_app = express().use(express.json()).post("/", postNoteController); + + beforeEach(() => { + // database writes always succeed + mockNoteDao.createNote.mockImplementation(async (note) => ({ + ...note, + id: MOCK_NOTE_ID, + insert_time: new Date(), + })); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it.each(TEST_PAYLOADS)("test payloads", async (params) => { + const { payload, expectedStatus } = params; + + // make request + const res = await supertest(test_app).post("/").send(payload); + expect(res.status).toBe(expectedStatus); + + // Validate reponse body + if (expectedStatus === 200) { + // validate view_url + expect(res.body).toHaveProperty("view_url"); + expect(res.body.view_url).toMatch(/^http[s]?:\/\//); + expect(res.body.view_url).toMatch(MOCK_NOTE_ID); + // validate_expire_time + expect(res.body).toHaveProperty("expire_time"); + expect(new Date(res.body.expire_time).getTime()).toBeGreaterThan( + new Date().getTime() + ); + } + + // Validate Write events + expect(mockEventLogger.writeEvent).toHaveBeenCalledOnce(); + if (expectedStatus === 200) { + expect(mockEventLogger.writeEvent).toHaveBeenCalledWith( + expect.objectContaining({ + success: true, + host: expect.any(String), + note_id: MOCK_NOTE_ID, + size_bytes: expect.any(Number), + expire_window_days: expect.any(Number), + user_id: params.payload.user_id, + user_plugin_version: params.payload.plugin_version, + }) + ); + } else { + expect(mockEventLogger.writeEvent).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + host: expect.any(String), + error: expect.any(String), + user_id: params.payload.user_id, + user_plugin_version: params.payload.plugin_version, + }) + ); + } + }); + + it("test database write failure", async () => { + // Mock database writes always fail + mockNoteDao.createNote.mockImplementation(async (note) => { + throw new Error("Database write failed"); + }); + + // Payload + const payload = { + ciphertext: VALID_CIPHERTEXT, + hmac: VALID_HMAC, + user_id: VALID_USER_ID, + plugin_version: VALID_VERSION, + }; + + // make request + const res = await supertest(test_app).post("/").send(payload); + expect(res.status).toBe(500); + + // Validate Write events + expect(mockEventLogger.writeEvent).toHaveBeenCalledOnce(); + expect(mockEventLogger.writeEvent).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + host: expect.any(String), + error: "Error: Database write failed", + user_id: payload.user_id, + user_plugin_version: payload.plugin_version, + }) + ); + }); +}); diff --git a/server/src/db/__mocks__/bloomFilter.dao.ts b/server/src/db/__mocks__/bloomFilter.dao.ts new file mode 100644 index 0000000..19f1e7e --- /dev/null +++ b/server/src/db/__mocks__/bloomFilter.dao.ts @@ -0,0 +1,4 @@ +import { vi } from "vitest"; + +export const getFilter = vi.fn(); +export const upsertFilter = vi.fn(); diff --git a/server/src/db/__mocks__/note.dao.ts b/server/src/db/__mocks__/note.dao.ts new file mode 100644 index 0000000..17194f5 --- /dev/null +++ b/server/src/db/__mocks__/note.dao.ts @@ -0,0 +1,6 @@ +import { vi } from "vitest"; + +export const getNote = vi.fn(); +export const createNote = vi.fn(); +export const getExpiredNotes = vi.fn(); +export const deleteNotes = vi.fn(); diff --git a/server/src/controllers/note/note.dao.ts b/server/src/db/note.dao.ts similarity index 95% rename from server/src/controllers/note/note.dao.ts rename to server/src/db/note.dao.ts index aa2e5c8..e91a573 100644 --- a/server/src/controllers/note/note.dao.ts +++ b/server/src/db/note.dao.ts @@ -1,5 +1,5 @@ import { EncryptedNote } from "@prisma/client"; -import prisma from "../../db/client"; +import prisma from "./client"; export async function getNote(noteId: string): Promise { return prisma.encryptedNote.findUnique({ diff --git a/server/src/lib/expiredNoteFilter.unit.test.ts b/server/src/lib/expiredNoteFilter.unit.test.ts index c2bd405..f4b46eb 100644 --- a/server/src/lib/expiredNoteFilter.unit.test.ts +++ b/server/src/lib/expiredNoteFilter.unit.test.ts @@ -3,10 +3,7 @@ import { ExpiredNoteFilter } from "./expiredNoteFilter"; import { ScalableBloomFilter } from "bloom-filters"; import * as dao from "../db/bloomFilter.dao"; -vi.mock("../db/bloomFilter.dao", () => ({ - getFilter: vi.fn(), - upsertFilter: vi.fn(), -})); +vi.mock("../db/bloomFilter.dao"); describe("Deserialization from database", () => { afterEach(() => { diff --git a/server/src/logging/EventLogger.integration.test.ts b/server/src/logging/EventLogger.integration.test.ts index 87618b7..65b823a 100644 --- a/server/src/logging/EventLogger.integration.test.ts +++ b/server/src/logging/EventLogger.integration.test.ts @@ -10,6 +10,8 @@ describe("Logging write events", () => { size_bytes: 100, success: true, expire_window_days: 30, + user_id: "test_user", + user_plugin_version: "1.0.0", }; // Is event written successfully? diff --git a/server/src/logging/EventLogger.ts b/server/src/logging/EventLogger.ts index af515ed..b2f5e84 100644 --- a/server/src/logging/EventLogger.ts +++ b/server/src/logging/EventLogger.ts @@ -17,9 +17,11 @@ interface ClientEvent extends Event { success: boolean; note_id?: string; size_bytes?: number; + user_id?: string; + user_plugin_version?: string; } -interface WriteEvent extends ClientEvent { +export interface WriteEvent extends ClientEvent { expire_window_days?: number; } diff --git a/server/src/tasks/deleteExpiredNotes.ts b/server/src/tasks/deleteExpiredNotes.ts index 9d37850..b24257c 100644 --- a/server/src/tasks/deleteExpiredNotes.ts +++ b/server/src/tasks/deleteExpiredNotes.ts @@ -1,4 +1,4 @@ -import { deleteNotes, getExpiredNotes } from "../controllers/note/note.dao"; +import { deleteNotes, getExpiredNotes } from "../db/note.dao"; import { getExpiredNoteFilter } from "../lib/expiredNoteFilter"; import EventLogger from "../logging/EventLogger"; import logger from "../logging/logger"; diff --git a/server/src/tasks/deleteExpiredNotes.unit.test.ts b/server/src/tasks/deleteExpiredNotes.unit.test.ts index ed2f7c9..920f268 100644 --- a/server/src/tasks/deleteExpiredNotes.unit.test.ts +++ b/server/src/tasks/deleteExpiredNotes.unit.test.ts @@ -1,14 +1,14 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { deleteExpiredNotes } from "./deleteExpiredNotes"; -import * as noteDao from "../controllers/note/note.dao"; +import * as noteDao from "../db/note.dao"; import EventLogger from "../logging/EventLogger"; import logger from "../logging/logger"; import * as filter from "../lib/expiredNoteFilter"; -vi.mock("../controllers/note/note.dao", () => ({ - getExpiredNotes: vi.fn(), - deleteNotes: vi.fn(), -})); +// vi.mock("../db/note.dao", () => ({ +// getExpiredNotes: vi.fn(), +// deleteNotes: vi.fn(), +// })); vi.mock("../lib/expiredNoteFilter", () => { const instance = { @@ -17,6 +17,7 @@ vi.mock("../lib/expiredNoteFilter", () => { return { getExpiredNoteFilter: () => instance }; }); +vi.mock("../db/note.dao"); vi.mock("../logging/EventLogger"); vi.spyOn(logger, "error");