Інтерфейси для моделей даних
Створи систему інтерфейсів для інтернет-магазину: User, Product, Order. Використай optional/readonly властивості, extending, generics та utility types.
Завдання
1. Базові інтерфейси
Створи наступні інтерфейси:
// Мітки часу (використовується в інших інтерфейсах)
interface Timestamps {
readonly createdAt: Date;
updatedAt: Date;
}
// Користувач
interface User extends Timestamps {
readonly id: number;
name: string;
email: string;
role: "customer" | "seller" | "admin";
phone?: string; // optional
address?: Address; // optional
}
// Адреса
interface Address {
city: string;
street: string;
zip: string;
country?: string; // optional, default "Україна"
}
// Товар
interface Product extends Timestamps {
readonly id: number;
title: string;
description: string;
price: number; // в копійках (цілі числа!)
category: ProductCategory;
inStock: boolean;
quantity: number;
sellerId: number; // ID продавця (User з role "seller")
tags?: string[]; // optional
}
// Категорія товару
type ProductCategory = "electronics" | "clothing" | "books" | "food" | "other";
2. Замовлення
// Елемент замовлення
interface OrderItem {
product: Pick<Product, "id" | "title" | "price">; // тільки потрібні поля
quantity: number;
totalPrice: number; // price * quantity
}
// Статус замовлення
type OrderStatus = "pending" | "confirmed" | "shipped" | "delivered" | "cancelled";
// Замовлення
interface Order extends Timestamps {
readonly id: number;
userId: number;
items: OrderItem[]; // мінімум 1 елемент
status: OrderStatus;
totalAmount: number; // сума всіх items
shippingAddress: Address;
notes?: string;
}
3. CRUD типи з Utility Types
Створи типи для створення та оновлення сутностей:
// Для створення -- без id та timestamps (вони генеруються автоматично)
type CreateUser = // Omit<User, ...>
// Для оновлення -- всі поля optional, крім id
type UpdateUser = // Partial<...> & Pick<...>
// Аналогічно для Product та Order
type CreateProduct = // ...
type UpdateProduct = // ...
type CreateOrder = // ...
type UpdateOrder = // ...
4. Generic API Response
// Відповідь API
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
// Відповідь зі списком та пагінацією
interface PaginatedResponse<T> extends ApiResponse<T[]> {
total: number;
page: number;
perPage: number;
totalPages: number;
}
// Відповідь з помилкою
interface ApiError {
success: false;
error: string;
code: number;
details?: Record<string, string>; // поле -> повідомлення про помилку
}
5. Функції для роботи з даними
Реалізуй наступні функції з правильними типами:
// Створити замовлення
function createOrder(userId: number, items: OrderItem[], address: Address): CreateOrder {
// ...
}
// Обчислити загальну суму замовлення
function calculateOrderTotal(items: OrderItem[]): number {
// ...
}
// Фільтрувати товари за категорією
function filterProducts(products: Product[], category: ProductCategory): Product[] {
// ...
}
// Знайти товари в наявності
function getInStockProducts(products: Product[]): Product[] {
// ...
}
// Отримати публічний профіль (без email та phone)
function getPublicProfile(user: User): Pick<User, "id" | "name" | "role"> {
// ...
}
// Згрупувати замовлення за статусом
function groupOrdersByStatus(orders: Order[]): Record<OrderStatus, Order[]> {
// ...
}
6. Generic Storage Interface
// Generic CRUD storage
interface Storage<T extends { id: number }> {
getAll(): T[];
getById(id: number): T | undefined;
create(item: Omit<T, "id" | "createdAt" | "updatedAt">): T;
update(id: number, updates: Partial<Omit<T, "id" | "createdAt">>): T | undefined;
delete(id: number): boolean;
find(predicate: (item: T) => boolean): T[];
}
Реалізуй createStorage<T>() функцію, яка повертає об'єкт, що реалізує Storage<T>. Автоматично генеруй id, createdAt, updatedAt.
Приклад використання
// Створення storage
const userStorage = createStorage<User>();
const productStorage = createStorage<Product>();
// Створення користувача
const user = userStorage.create({
name: "Олексій",
email: "alex@test.com",
role: "customer",
});
// user.id -- автоматично згенерований
// user.createdAt -- автоматично
// Оновлення
userStorage.update(user.id, { phone: "+380123456789" });
// Пошук
const admins = userStorage.find((u) => u.role === "admin");
// Створення замовлення
const laptop: Product = productStorage.create({
title: "Ноутбук",
description: "Потужний ноутбук",
price: 2500000, // 25000.00 грн у копійках
category: "electronics",
inStock: true,
quantity: 10,
sellerId: 1,
});
const order = createOrder(user.id, [
{
product: { id: laptop.id, title: laptop.title, price: laptop.price },
quantity: 1,
totalPrice: laptop.price,
},
], {
city: "Київ",
street: "Хрещатик, 1",
zip: "01001",
});
console.log(calculateOrderTotal(order.items));
console.log(getPublicProfile(user));
Бонус
- Додай
Validator<T>interface з методомvalidate(data: unknown): data is Tта реалізуй валідатори для User, Product, Order - Створи
type DeepReadonly<T>-- рекурсивний Readonly, який робить readonly і вкладені об'єкти - Додай подію
onChangeу Storage, яка повідомляє про зміни (Observer pattern з generics) - Створи
type RequireAtLeastOne<T>-- тип, де хоча б одне поле обов'язкове
Критерії оцінки
| Критерій | Бали |
|---|---|
| Базові інтерфейси (User, Address, Product, Timestamps) | 15 |
| Інтерфейси замовлення (Order, OrderItem, OrderStatus) | 15 |
| CRUD типи з Utility Types (Create/Update для кожної сутності) | 15 |
| Generic ApiResponse та PaginatedResponse | 10 |
| Функції з правильними типами (6 функцій) | 20 |
| Generic Storage з реалізацією createStorage | 25 |
| Бонус: Validator / DeepReadonly / onChange / RequireAtLeastOne | +20 |