Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/problem4/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Problem 4: Three ways to sum to n
*
* Contract used for any integer n:
* - n > 0: 1 + 2 + ... + n
* - n < 0: -1 + -2 + ... + n
* - n = 0: 0
*/

export function sum_to_n_a(n: number): number {
// Arithmetic formula.
// Time: O(1), Space: O(1). This is the most efficient implementation.
const sign = n < 0 ? -1 : 1;
const absoluteN = Math.abs(n);

return sign * ((absoluteN * (absoluteN + 1)) / 2);
}

export function sum_to_n_b(n: number): number {
// Iterative accumulation.
// Time: O(|n|), Space: O(1). Simple and memory-efficient, but slower for large |n|.
let total = 0;
const step = n < 0 ? -1 : 1;

for (let current = step; n < 0 ? current >= n : current <= n; current += step) {
total += current;
}

return total;
}

export function sum_to_n_c(n: number): number {
// Functional implementation using array generation and reduce.
// Time: O(|n|), Space: O(|n|). Clear as a demonstration, but less efficient
// because it allocates an array with one element per number being summed.
const sign = n < 0 ? -1 : 1;
const absoluteN = Math.abs(n);

return Array.from({ length: absoluteN }, (_, index) => sign * (index + 1)).reduce(
(total, value) => total + value,
0,
);
}

113 changes: 113 additions & 0 deletions src/problem5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Problem 5: A Crude Server

This is a small ExpressJS + TypeScript CRUD server for managing books.

The service persists data to a JSON database file at `data/database.json` by default. You can override that path with `DATABASE_FILE`.

## Requirements

- Node.js 20+
- npm

## Install

From the repository root:

```bash
cd backend_test/problem5
npm install
```

## Run In Development

```bash
npm run dev
```

The server starts on `http://localhost:3000`.

To use another port:

```bash
PORT=4000 npm run dev
```

To use another database file:

```bash
DATABASE_FILE=./data/local.json npm run dev
```

## Build And Run

```bash
npm run build
npm start
```

## API

### Health Check

```bash
curl http://localhost:3000/health
```

### Create A Book

```bash
curl -X POST http://localhost:3000/books \
-H "Content-Type: application/json" \
-d '{
"title": "Clean Code",
"author": "Robert C. Martin",
"status": "available",
"publishedYear": 2008
}'
```

Fields:

- `title`: required string
- `author`: required string
- `status`: optional, one of `available`, `borrowed`, `archived`
- `publishedYear`: optional integer

### List Books

```bash
curl http://localhost:3000/books
```

Basic filters:

```bash
curl "http://localhost:3000/books?status=available"
curl "http://localhost:3000/books?author=martin"
curl "http://localhost:3000/books?q=clean"
```

### Get Book Details

```bash
curl http://localhost:3000/books/:id
```

### Update A Book

```bash
curl -X PATCH http://localhost:3000/books/:id \
-H "Content-Type: application/json" \
-d '{
"status": "borrowed"
}'
```

### Delete A Book

```bash
curl -X DELETE http://localhost:3000/books/:id
```

Successful deletes return `204 No Content`.

95 changes: 95 additions & 0 deletions src/problem5/dist/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.app = void 0;
const cors_1 = __importDefault(require("cors"));
const express_1 = __importDefault(require("express"));
const helmet_1 = __importDefault(require("helmet"));
const morgan_1 = __importDefault(require("morgan"));
const zod_1 = require("zod");
const bookRepository_1 = require("./bookRepository");
const validation_1 = require("./validation");
exports.app = (0, express_1.default)();
exports.app.use((0, helmet_1.default)());
exports.app.use((0, cors_1.default)());
exports.app.use(express_1.default.json());
exports.app.use((0, morgan_1.default)("dev"));
exports.app.get("/health", (_req, res) => {
res.json({ status: "ok" });
});
exports.app.post("/books", async (req, res, next) => {
try {
const input = validation_1.createBookSchema.parse(req.body);
const book = await (0, bookRepository_1.createBook)(input);
res.status(201).json({ data: book });
}
catch (error) {
next(error);
}
});
exports.app.get("/books", async (req, res, next) => {
try {
const filters = validation_1.listBookQuerySchema.parse(req.query);
const books = await (0, bookRepository_1.listBooks)(filters);
res.json({ data: books, count: books.length });
}
catch (error) {
next(error);
}
});
exports.app.get("/books/:id", async (req, res, next) => {
try {
const book = await (0, bookRepository_1.getBookById)(req.params.id);
if (!book) {
res.status(404).json({ error: "Book not found" });
return;
}
res.json({ data: book });
}
catch (error) {
next(error);
}
});
exports.app.patch("/books/:id", async (req, res, next) => {
try {
const input = validation_1.updateBookSchema.parse(req.body);
const book = await (0, bookRepository_1.updateBook)(req.params.id, input);
if (!book) {
res.status(404).json({ error: "Book not found" });
return;
}
res.json({ data: book });
}
catch (error) {
next(error);
}
});
exports.app.delete("/books/:id", async (req, res, next) => {
try {
const deleted = await (0, bookRepository_1.deleteBook)(req.params.id);
if (!deleted) {
res.status(404).json({ error: "Book not found" });
return;
}
res.status(204).send();
}
catch (error) {
next(error);
}
});
exports.app.use((_req, res) => {
res.status(404).json({ error: "Route not found" });
});
exports.app.use((error, _req, res, _next) => {
if (error instanceof zod_1.ZodError) {
res.status(400).json({
error: "Validation failed",
details: error.flatten(),
});
return;
}
console.error(error);
res.status(500).json({ error: "Internal server error" });
});
71 changes: 71 additions & 0 deletions src/problem5/dist/bookRepository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listBooks = listBooks;
exports.getBookById = getBookById;
exports.createBook = createBook;
exports.updateBook = updateBook;
exports.deleteBook = deleteBook;
const crypto_1 = require("crypto");
const database_1 = require("./database");
async function listBooks(filters) {
const data = await database_1.database.read();
const author = filters.author?.toLowerCase();
const query = filters.q?.toLowerCase();
return data.books.filter((book) => {
if (filters.status && book.status !== filters.status) {
return false;
}
if (author && !book.author.toLowerCase().includes(author)) {
return false;
}
if (query) {
const searchable = `${book.title} ${book.author}`.toLowerCase();
if (!searchable.includes(query)) {
return false;
}
}
return true;
});
}
async function getBookById(id) {
const data = await database_1.database.read();
return data.books.find((book) => book.id === id) ?? null;
}
async function createBook(input) {
const data = await database_1.database.read();
const now = new Date().toISOString();
const book = {
id: (0, crypto_1.randomUUID)(),
...input,
createdAt: now,
updatedAt: now,
};
data.books.push(book);
await database_1.database.write(data);
return book;
}
async function updateBook(id, input) {
const data = await database_1.database.read();
const index = data.books.findIndex((book) => book.id === id);
if (index === -1) {
return null;
}
const updatedBook = {
...data.books[index],
...input,
updatedAt: new Date().toISOString(),
};
data.books[index] = updatedBook;
await database_1.database.write(data);
return updatedBook;
}
async function deleteBook(id) {
const data = await database_1.database.read();
const nextBooks = data.books.filter((book) => book.id !== id);
if (nextBooks.length === data.books.length) {
return false;
}
data.books = nextBooks;
await database_1.database.write(data);
return true;
}
38 changes: 38 additions & 0 deletions src/problem5/dist/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.database = exports.JsonDatabase = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const defaultDatabase = {
books: [],
};
class JsonDatabase {
constructor(filePath) {
this.filePath = filePath;
}
async read() {
await this.ensureFile();
const content = await fs_1.promises.readFile(this.filePath, "utf8");
return JSON.parse(content);
}
async write(data) {
await fs_1.promises.mkdir(path_1.default.dirname(this.filePath), { recursive: true });
await fs_1.promises.writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
}
async ensureFile() {
try {
await fs_1.promises.access(this.filePath);
}
catch {
await this.write(defaultDatabase);
}
}
}
exports.JsonDatabase = JsonDatabase;
const databasePath = process.env.DATABASE_FILE
? path_1.default.resolve(process.env.DATABASE_FILE)
: path_1.default.resolve(__dirname, "../data/database.json");
exports.database = new JsonDatabase(databasePath);
7 changes: 7 additions & 0 deletions src/problem5/dist/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const app_1 = require("./app");
const port = Number(process.env.PORT ?? 3000);
app_1.app.listen(port, () => {
console.log(`Problem 5 server is running on http://localhost:${port}`);
});
2 changes: 2 additions & 0 deletions src/problem5/dist/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
17 changes: 17 additions & 0 deletions src/problem5/dist/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listBookQuerySchema = exports.updateBookSchema = exports.createBookSchema = exports.bookStatusSchema = void 0;
const zod_1 = require("zod");
exports.bookStatusSchema = zod_1.z.enum(["available", "borrowed", "archived"]);
exports.createBookSchema = zod_1.z.object({
title: zod_1.z.string().trim().min(1, "title is required"),
author: zod_1.z.string().trim().min(1, "author is required"),
status: exports.bookStatusSchema.default("available"),
publishedYear: zod_1.z.number().int().min(0).max(9999).optional(),
});
exports.updateBookSchema = exports.createBookSchema.partial().refine((value) => Object.keys(value).length > 0, "at least one field is required");
exports.listBookQuerySchema = zod_1.z.object({
status: exports.bookStatusSchema.optional(),
author: zod_1.z.string().trim().optional(),
q: zod_1.z.string().trim().optional(),
});
Loading