diff --git a/LICENSE_NOTICE.md b/LICENSE_NOTICE.md new file mode 100644 index 0000000..87480d2 --- /dev/null +++ b/LICENSE_NOTICE.md @@ -0,0 +1,91 @@ +# License Notice + +## Sharenet License Information + +This project, Sharenet, is licensed under the **Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License** (CC-BY-NC-SA-4.0). + +## License Details + +- **Full License Text**: See [LICENSE.md](LICENSE.md) for the complete license text +- **License URL**: https://creativecommons.org/licenses/by-nc-sa/4.0/ +- **SPDX Identifier**: CC-BY-NC-SA-4.0 + +## What This License Means + +### You are free to: +- **Share**: Copy and redistribute the material in any medium or format +- **Adapt**: Remix, transform, and build upon the material +- **Attribution**: You must give appropriate credit, provide a link to the license, and indicate if changes were made + +### Under the following terms: +- **NonCommercial**: You may not use the material for commercial purposes +- **ShareAlike**: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original + +## Copyright Information + +- **Copyright Holder**: Continuist +- **Year**: 2024 +- **Project**: Sharenet + +## License Headers + +All source files in this project include license headers that reference this license. The headers follow this format: + +### For Rust files: +```rust +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ +``` + +### For TypeScript/JavaScript files: +```typescript +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ +``` + +## Package Configuration + +### Backend (Rust) +- **Cargo.toml**: `license = "CC-BY-NC-SA-4.0"` + +### Frontend (Node.js) +- **package.json**: `"license": "CC-BY-NC-SA-4.0"` + +## Compliance + +To comply with this license when using, modifying, or distributing this software: + +1. **Attribution**: Always include the copyright notice and license reference +2. **Non-Commercial Use**: Do not use for commercial purposes without permission +3. **Share Alike**: If you create derivatives, license them under the same terms +4. **License Link**: Include a link to the full license text + +## Questions + +If you have questions about the license or need permission for commercial use, please contact: +- **Email**: continuist02@gmail.com + +## Third-Party Dependencies + +This project includes third-party dependencies that are licensed under their own terms. Please refer to the respective license files for each dependency: + +- **Backend Dependencies**: See `backend/Cargo.lock` for dependency licenses +- **Frontend Dependencies**: See `frontend/package-lock.json` for dependency licenses + +The CC-BY-NC-SA-4.0 license applies only to the original Sharenet codebase, not to third-party dependencies. \ No newline at end of file diff --git a/PROJECT_INDEX.md b/PROJECT_INDEX.md new file mode 100644 index 0000000..fb52449 --- /dev/null +++ b/PROJECT_INDEX.md @@ -0,0 +1,415 @@ +# Sharenet Project Index + +## Project Overview + +Sharenet is a full-stack application for managing users and products, built with Rust (backend) and Next.js (frontend). It demonstrates clean architecture principles with multiple interface options and configurable storage backends. + +## Architecture + +### Backend Architecture (Clean Architecture) + +The backend follows clean architecture principles with clear separation of concerns: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Interface Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ API │ │ CLI │ │ TUI │ │ +│ │ (Axum) │ │ (Clap) │ │ (Ratatui) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ Application Layer │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Service/UseCase Layer │ │ +│ │ - UserService │ │ +│ │ - ProductService │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ Domain Layer │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ - User Entity │ │ +│ │ - Product Entity │ │ +│ │ - Repository Traits │ │ +│ │ - Domain Errors │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ Infrastructure Layer │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ Memory │ │ PostgreSQL │ │ +│ │ Repository │ │ Repository │ │ +│ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Project Structure + +``` +sharenet/ +├── backend/ # Rust backend +│ ├── Cargo.toml # Workspace configuration +│ ├── README.md # Backend documentation +│ ├── crates/ # Backend modules +│ │ ├── domain/ # Domain entities and traits +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # Core domain logic +│ │ ├── application/ # Application services +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # Use case implementations +│ │ ├── api/ # HTTP API (Axum) +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # REST API endpoints +│ │ ├── cli/ # CLI interface (Clap) +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # Command-line interface +│ │ ├── tui/ # TUI interface (Ratatui) +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # Text user interface +│ │ ├── memory/ # In-memory storage +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # Memory repository implementation +│ │ ├── postgres/ # PostgreSQL storage +│ │ │ ├── Cargo.toml +│ │ │ └── src/lib.rs # PostgreSQL repository implementation +│ │ ├── sharenet-api-memory/ # API binary (memory) +│ │ │ ├── Cargo.toml +│ │ │ └── src/main.rs # API entry point +│ │ ├── sharenet-api-postgres/ # API binary (PostgreSQL) +│ │ │ ├── Cargo.toml +│ │ │ └── src/main.rs # API entry point +│ │ ├── sharenet-cli-memory/ # CLI binary (memory) +│ │ │ ├── Cargo.toml +│ │ │ └── src/main.rs # CLI entry point +│ │ ├── sharenet-cli-postgres/ # CLI binary (PostgreSQL) +│ │ │ ├── Cargo.toml +│ │ │ └── src/main.rs # CLI entry point +│ │ ├── sharenet-tui-memory/ # TUI binary (memory) +│ │ │ ├── Cargo.toml +│ │ │ └── src/main.rs # TUI entry point +│ │ └── sharenet-tui-postgres/ # TUI binary (PostgreSQL) +│ │ ├── Cargo.toml +│ │ └── src/main.rs # TUI entry point +│ ├── migrations/ # Database migrations +│ │ └── 20240101000000_create_tables.sql +│ └── config/ # Configuration files +│ ├── api-memory.env # API with memory storage +│ ├── api-postgres.env # API with PostgreSQL +│ ├── cli-memory.env # CLI with memory storage +│ ├── cli-postgres.env # CLI with PostgreSQL +│ ├── tui-memory.env # TUI with memory storage +│ └── tui-postgres.env # TUI with PostgreSQL +├── frontend/ # Next.js frontend +│ ├── package.json # Frontend dependencies +│ ├── README.md # Frontend documentation +│ ├── src/ # Source code +│ │ ├── app/ # Next.js app directory +│ │ │ ├── layout.tsx # Root layout +│ │ │ ├── page.tsx # Dashboard page +│ │ │ ├── users/ # Users pages +│ │ │ │ └── page.tsx # Users management +│ │ │ └── products/ # Products pages +│ │ │ └── page.tsx # Products management +│ │ ├── components/ # React components +│ │ │ └── ui/ # shadcn/ui components +│ │ │ ├── button.tsx # Button component +│ │ │ ├── card.tsx # Card component +│ │ │ ├── dialog.tsx # Dialog component +│ │ │ ├── form.tsx # Form component +│ │ │ ├── input.tsx # Input component +│ │ │ ├── label.tsx # Label component +│ │ │ └── table.tsx # Table component +│ │ └── lib/ # Utility functions +│ │ ├── api.ts # API client +│ │ └── utils.ts # Utility functions +│ ├── public/ # Static assets +│ │ ├── file.svg # File icon +│ │ ├── globe.svg # Globe icon +│ │ ├── next.svg # Next.js logo +│ │ ├── vercel.svg # Vercel logo +│ │ └── window.svg # Window icon +│ ├── components.json # shadcn/ui configuration +│ ├── next.config.ts # Next.js configuration +│ ├── postcss.config.mjs # PostCSS configuration +│ ├── tailwind.config.js # Tailwind CSS configuration +│ └── tsconfig.json # TypeScript configuration +├── README.md # Project documentation +├── LICENSE.md # Project license +└── .gitignore # Git ignore rules +``` + +## Key Components + +### Domain Layer (`backend/crates/domain/`) + +**Core Entities:** +- `User`: User entity with id, username, email, timestamps +- `Product`: Product entity with id, name, description, timestamps +- `CreateUser`/`UpdateUser`: DTOs for user operations +- `CreateProduct`/`UpdateProduct`: DTOs for product operations + +**Traits:** +- `Entity`: Base trait for domain entities +- `Repository`: Generic repository trait for CRUD operations + +**Error Handling:** +- `DomainError`: Domain-specific error types (NotFound, InvalidInput, Internal) + +### Application Layer (`backend/crates/application/`) + +**Services:** +- `Service`: Generic service implementation +- `UseCase`: Use case trait for business operations +- `Repository`: Repository trait for data access + +**Features:** +- Generic implementations for easy extension +- Static dispatch with generic traits +- Comprehensive test coverage with mock repositories + +### Infrastructure Layer + +#### Memory Repository (`backend/crates/memory/`) +- In-memory storage using `HashMap` and `RwLock` +- Suitable for development and testing +- No external dependencies + +#### PostgreSQL Repository (`backend/crates/postgres/`) +- SQLx-based PostgreSQL implementation +- Connection pooling with PgPool +- Prepared query caching for performance +- Migration support + +### Interface Layer + +#### HTTP API (`backend/crates/api/`) +- **Framework**: Axum +- **Features**: + - RESTful endpoints for users and products + - CORS support + - Request/response logging + - JSON serialization/deserialization +- **Endpoints**: + - `POST /users` - Create user + - `GET /users/:id` - Get user by ID + - `GET /users` - List all users + - `PUT /users/:id` - Update user + - `DELETE /users/:id` - Delete user + - `POST /products` - Create product + - `GET /products/:id` - Get product by ID + - `GET /products` - List all products + - `PUT /products/:id` - Update product + - `DELETE /products/:id` - Delete product + +#### CLI (`backend/crates/cli/`) +- **Framework**: Clap +- **Features**: + - Subcommand-based interface + - User and product management commands + - JSON output support +- **Commands**: + - `user create --username --email ` + - `user get --id ` + - `user list` + - `user update --id [--username ] [--email ]` + - `user delete --id ` + - `product create --name --description ` + - `product get --id ` + - `product list` + - `product update --id [--name ] [--description ]` + - `product delete --id ` + +#### TUI (`backend/crates/tui/`) +- **Framework**: Ratatui +- **Features**: + - Interactive terminal interface + - Table-based data display + - Form-based data entry + - Navigation between users and products + +### Frontend (`frontend/`) + +#### Technology Stack +- **Framework**: Next.js 15.3.3 +- **Language**: TypeScript +- **UI Library**: React 19 +- **Styling**: Tailwind CSS +- **Components**: shadcn/ui +- **Form Handling**: React Hook Form with Zod validation +- **HTTP Client**: Axios + +#### Key Features +- **Dashboard**: Overview page with navigation to users and products +- **Users Management**: + - View all users in a table + - Create new users via dialog + - Edit existing users + - Delete users with confirmation +- **Products Management**: + - View all products in a table + - Create new products via dialog + - Edit existing products + - Delete products with confirmation + +#### API Integration +- **Base URL**: `http://127.0.0.1:3000` +- **Client**: Axios-based API client in `src/lib/api.ts` +- **Type Safety**: Full TypeScript interfaces for API responses + +## Database Schema + +### Users Table +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + username TEXT NOT NULL, + email TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); +``` + +### Products Table +```sql +CREATE TABLE products ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + description TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); +``` + +## Configuration + +### Backend Configuration +Configuration files are located in `backend/config/`: +- `api-memory.env` - API server with in-memory storage +- `api-postgres.env` - API server with PostgreSQL storage +- `cli-memory.env` - CLI with in-memory storage +- `cli-postgres.env` - CLI with PostgreSQL storage +- `tui-memory.env` - TUI with in-memory storage +- `tui-postgres.env` - TUI with PostgreSQL storage + +### Environment Variables +- `SERVER_ADDR` - Server address (default: 127.0.0.1:3000) +- `DATABASE_URL` - PostgreSQL connection string +- `RUST_LOG` - Logging level (default: info) + +## Development Workflow + +### Backend Development +1. **Setup**: Install Rust and SQLx CLI +2. **Database**: Create database and run migrations +3. **Build**: `cargo build` +4. **Run**: Choose appropriate binary for your use case +5. **Test**: `cargo test` + +### Frontend Development +1. **Setup**: Install Node.js and npm +2. **Install**: `npm install` +3. **Run**: `npm run dev` +4. **Build**: `npm run build` + +### Running the Full Stack +1. Start backend: `cargo run --bin sharenet-api-postgres` +2. Start frontend: `cd frontend && npm run dev` +3. Access web interface at `http://localhost:3000` + +## Testing + +### Backend Testing +- **Unit Tests**: Each crate has comprehensive unit tests +- **Integration Tests**: Repository implementations are tested +- **Mock Testing**: Mock repositories for isolated testing + +### Frontend Testing +- **Component Testing**: Individual React components +- **Integration Testing**: API integration and user flows +- **E2E Testing**: Full application workflows + +## Deployment + +### Backend Deployment +- **Docker**: Containerized deployment +- **Binary**: Standalone executables +- **Database**: PostgreSQL with connection pooling + +### Frontend Deployment +- **Vercel**: Optimized for Next.js +- **Static Export**: Can be deployed to any static hosting +- **Docker**: Containerized deployment + +## Performance Considerations + +### Backend +- **Connection Pooling**: PostgreSQL connection pooling +- **Query Caching**: SQLx prepared query caching +- **Async/Await**: Non-blocking I/O operations +- **Generic Implementations**: Zero-cost abstractions + +### Frontend +- **Code Splitting**: Next.js automatic code splitting +- **Static Generation**: Pre-rendered pages where possible +- **Optimized Images**: Next.js image optimization +- **Bundle Analysis**: Webpack bundle analysis + +## Security + +### Backend Security +- **Input Validation**: Domain-level validation +- **Error Handling**: Secure error messages +- **CORS**: Configurable CORS policies +- **SQL Injection**: SQLx prevents SQL injection + +### Frontend Security +- **Type Safety**: TypeScript prevents type-related errors +- **Input Validation**: Client-side validation with Zod +- **XSS Prevention**: React automatic escaping +- **CSRF Protection**: Built-in Next.js protection + +## Future Enhancements + +### Planned Features +- **Authentication**: User authentication and authorization +- **Role-Based Access Control**: User roles and permissions +- **Product Categories**: Product categorization system +- **Inventory Tracking**: Stock management +- **Audit Logging**: Operation logging and history +- **API Documentation**: OpenAPI/Swagger documentation +- **Real-time Updates**: WebSocket support +- **File Uploads**: Product image uploads +- **Search and Filtering**: Advanced data filtering +- **Pagination**: Large dataset handling + +### Technical Improvements +- **Caching**: Redis integration for caching +- **Monitoring**: Application monitoring and metrics +- **Logging**: Structured logging with correlation IDs +- **Health Checks**: Application health endpoints +- **Rate Limiting**: API rate limiting +- **Compression**: Response compression +- **CDN**: Static asset delivery optimization + +## Contributing + +### Development Guidelines +- **Code Style**: Follow Rust and TypeScript conventions +- **Testing**: Maintain high test coverage +- **Documentation**: Keep documentation up to date +- **Architecture**: Follow clean architecture principles +- **Error Handling**: Comprehensive error handling +- **Performance**: Consider performance implications + +### Code Review Process +- **Pull Requests**: All changes via pull requests +- **Code Review**: Mandatory code review +- **Testing**: Automated testing on all changes +- **Documentation**: Update documentation as needed + +## License + +This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - see the [LICENSE.md](LICENSE.md) file for details. + +For more information about the license and compliance requirements, see [LICENSE_NOTICE.md](LICENSE_NOTICE.md). \ No newline at end of file diff --git a/README.md b/README.md index a07e50e..d56912e 100644 --- a/README.md +++ b/README.md @@ -144,4 +144,6 @@ The frontend is configured to connect to the backend at `http://127.0.0.1:3000` ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. \ No newline at end of file +This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - see the [LICENSE.md](LICENSE.md) file for details. + +For more information about the license and compliance requirements, see [LICENSE_NOTICE.md](LICENSE_NOTICE.md). \ No newline at end of file diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0b1930c..5f3c057 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" version = "0.1.0" edition = "2021" authors = ["Continuist "] -license = "CC-BY-NC-4.0" +license = "CC-BY-NC-SA-4.0" [workspace.dependencies] tokio = { version = "1.36", features = ["full"] } diff --git a/backend/crates/api/src/lib.rs b/backend/crates/api/src/lib.rs index 3b3914f..2f2185e 100644 --- a/backend/crates/api/src/lib.rs +++ b/backend/crates/api/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use std::net::SocketAddr; use std::sync::Arc; diff --git a/backend/crates/application/Cargo.toml b/backend/crates/application/Cargo.toml index bbe9c0f..dfc40c5 100644 --- a/backend/crates/application/Cargo.toml +++ b/backend/crates/application/Cargo.toml @@ -9,3 +9,7 @@ license.workspace = true domain = { path = "../domain" } thiserror = { workspace = true } uuid = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["full", "macros", "rt-multi-thread"] } +chrono = { workspace = true } diff --git a/backend/crates/application/src/lib.rs b/backend/crates/application/src/lib.rs index a745f01..cbb39fb 100644 --- a/backend/crates/application/src/lib.rs +++ b/backend/crates/application/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use domain::{Result, Entity}; use thiserror::Error; use uuid::Uuid; @@ -76,3 +87,361 @@ impl + Clone> UseCase for Service { } } } + +#[cfg(test)] +mod tests { + use super::*; + use domain::{User, CreateUser, UpdateUser, Product, CreateProduct, UpdateProduct}; + use std::sync::Arc; + use tokio::sync::RwLock; + use std::collections::HashMap; + use chrono::Utc; + + // Mock repository for testing + #[derive(Clone)] + struct MockRepository { + data: Arc>>, + } + + impl MockRepository { + fn new() -> Self { + Self { + data: Arc::new(RwLock::new(HashMap::new())), + } + } + } + + impl Repository for MockRepository { + fn create(&self, data: CreateUser) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + let id = Uuid::new_v4(); + let user = User { + id, + username: data.username, + email: data.email, + created_at: Utc::now(), + updated_at: Utc::now(), + }; + guard.insert(id, user.clone()); + Ok(user) + } + } + + fn find_by_id(&self, id: Uuid) -> impl Future> + Send { + async move { + let guard = self.data.read().await; + guard.get(&id) + .cloned() + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id))) + } + } + + fn find_all(&self) -> impl Future>> + Send { + async move { + let guard = self.data.read().await; + Ok(guard.values().cloned().collect()) + } + } + + fn update(&self, id: Uuid, data: UpdateUser) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + let user = guard.get_mut(&id) + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?; + + if let Some(username) = data.username { + user.username = username; + } + if let Some(email) = data.email { + user.email = email; + } + user.updated_at = Utc::now(); + Ok(user.clone()) + } + } + + fn delete(&self, id: Uuid) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + guard.remove(&id) + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?; + Ok(()) + } + } + } + + impl Repository for MockRepository { + fn create(&self, data: CreateProduct) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + let id = Uuid::new_v4(); + let product = Product { + id, + name: data.name, + description: data.description, + created_at: Utc::now(), + updated_at: Utc::now(), + }; + guard.insert(id, product.clone()); + Ok(product) + } + } + + fn find_by_id(&self, id: Uuid) -> impl Future> + Send { + async move { + let guard = self.data.read().await; + guard.get(&id) + .cloned() + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id))) + } + } + + fn find_all(&self) -> impl Future>> + Send { + async move { + let guard = self.data.read().await; + Ok(guard.values().cloned().collect()) + } + } + + fn update(&self, id: Uuid, data: UpdateProduct) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + let product = guard.get_mut(&id) + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?; + + if let Some(name) = data.name { + product.name = name; + } + if let Some(description) = data.description { + product.description = description; + } + product.updated_at = Utc::now(); + Ok(product.clone()) + } + } + + fn delete(&self, id: Uuid) -> impl Future> + Send { + async move { + let mut guard = self.data.write().await; + guard.remove(&id) + .ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?; + Ok(()) + } + } + } + + mod service_tests { + use super::*; + + #[tokio::test] + async fn test_user_service_create() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + let user = service.create(create_user).await.unwrap(); + assert_eq!(user.username, "test_user"); + assert_eq!(user.email, "test@example.com"); + } + + #[tokio::test] + async fn test_user_service_get() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + let created = service.create(create_user).await.unwrap(); + let found = service.get(created.id).await.unwrap(); + assert_eq!(found.id, created.id); + } + + #[tokio::test] + async fn test_user_service_list() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let user1 = CreateUser { + username: "user1".to_string(), + email: "user1@example.com".to_string(), + }; + let user2 = CreateUser { + username: "user2".to_string(), + email: "user2@example.com".to_string(), + }; + + service.create(user1).await.unwrap(); + service.create(user2).await.unwrap(); + + let users = service.list().await.unwrap(); + assert_eq!(users.len(), 2); + } + + #[tokio::test] + async fn test_user_service_update() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + let created = service.create(create_user).await.unwrap(); + let update = UpdateUser { + username: Some("updated_user".to_string()), + email: None, + }; + + let updated = service.update(created.id, update).await.unwrap(); + assert_eq!(updated.username, "updated_user"); + assert_eq!(updated.email, "test@example.com"); + } + + #[tokio::test] + async fn test_user_service_delete() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + let created = service.create(create_user).await.unwrap(); + service.delete(created.id).await.unwrap(); + assert!(service.get(created.id).await.is_err()); + } + + #[tokio::test] + async fn test_product_service_create() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_product = CreateProduct { + name: "Test Product".to_string(), + description: "Test Description".to_string(), + }; + + let product = service.create(create_product).await.unwrap(); + assert_eq!(product.name, "Test Product"); + assert_eq!(product.description, "Test Description"); + } + + #[tokio::test] + async fn test_product_service_get() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_product = CreateProduct { + name: "Test Product".to_string(), + description: "Test Description".to_string(), + }; + + let created = service.create(create_product).await.unwrap(); + let found = service.get(created.id).await.unwrap(); + assert_eq!(found.id, created.id); + } + + #[tokio::test] + async fn test_product_service_list() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let product1 = CreateProduct { + name: "Product 1".to_string(), + description: "Description 1".to_string(), + }; + let product2 = CreateProduct { + name: "Product 2".to_string(), + description: "Description 2".to_string(), + }; + + service.create(product1).await.unwrap(); + service.create(product2).await.unwrap(); + + let products = service.list().await.unwrap(); + assert_eq!(products.len(), 2); + } + + #[tokio::test] + async fn test_product_service_update() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_product = CreateProduct { + name: "Test Product".to_string(), + description: "Test Description".to_string(), + }; + + let created = service.create(create_product).await.unwrap(); + let update = UpdateProduct { + name: Some("Updated Product".to_string()), + description: None, + }; + + let updated = service.update(created.id, update).await.unwrap(); + assert_eq!(updated.name, "Updated Product"); + assert_eq!(updated.description, "Test Description"); + } + + #[tokio::test] + async fn test_product_service_delete() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let create_product = CreateProduct { + name: "Test Product".to_string(), + description: "Test Description".to_string(), + }; + + let created = service.create(create_product).await.unwrap(); + service.delete(created.id).await.unwrap(); + assert!(service.get(created.id).await.is_err()); + } + } + + mod error_tests { + use super::*; + + #[tokio::test] + async fn test_not_found_error() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let result = service.get(Uuid::new_v4()).await; + assert!(matches!(result, Err(ApplicationError::Domain(domain::DomainError::NotFound(_))))); + } + + #[tokio::test] + async fn test_update_nonexistent() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let update = UpdateUser { + username: Some("new_username".to_string()), + email: None, + }; + + let result = service.update(Uuid::new_v4(), update).await; + assert!(matches!(result, Err(ApplicationError::Domain(domain::DomainError::NotFound(_))))); + } + + #[tokio::test] + async fn test_delete_nonexistent() { + let repo = MockRepository::::new(); + let service = Service::new(repo); + + let result = service.delete(Uuid::new_v4()).await; + assert!(matches!(result, Err(ApplicationError::Domain(domain::DomainError::NotFound(_))))); + } + } +} diff --git a/backend/crates/cli/src/lib.rs b/backend/crates/cli/src/lib.rs index 81b8a7c..cea65cc 100644 --- a/backend/crates/cli/src/lib.rs +++ b/backend/crates/cli/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use anyhow::Result; use clap::Parser; use domain::{CreateProduct, CreateUser, Product, User, UpdateProduct, UpdateUser}; diff --git a/backend/crates/domain/Cargo.toml b/backend/crates/domain/Cargo.toml index 5cae3c0..c093933 100644 --- a/backend/crates/domain/Cargo.toml +++ b/backend/crates/domain/Cargo.toml @@ -10,3 +10,6 @@ serde = { workspace = true } uuid = { workspace = true } chrono = { workspace = true } thiserror = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["full", "macros", "rt-multi-thread"] } diff --git a/backend/crates/domain/src/lib.rs b/backend/crates/domain/src/lib.rs index 7adb64e..4126f23 100644 --- a/backend/crates/domain/src/lib.rs +++ b/backend/crates/domain/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -89,17 +100,233 @@ where fn delete(&self, id: Uuid) -> impl Future> + Send; } -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - #[cfg(test)] mod tests { use super::*; + use chrono::Utc; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + mod user_tests { + use super::*; + + #[test] + fn test_user_entity_impl() { + let user = User { + id: Uuid::new_v4(), + username: "test_user".to_string(), + email: "test@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + assert_eq!(user.username, "test_user"); + assert_eq!(user.email, "test@example.com"); + } + + #[test] + fn test_create_user_validation() { + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + assert_eq!(create_user.username, "test_user"); + assert_eq!(create_user.email, "test@example.com"); + } + + #[test] + fn test_update_user_partial() { + let update_user = UpdateUser { + username: Some("new_username".to_string()), + email: None, + }; + + assert_eq!(update_user.username, Some("new_username".to_string())); + assert_eq!(update_user.email, None); + } + } + + mod product_tests { + use super::*; + + #[test] + fn test_product_entity_impl() { + let product = Product { + id: Uuid::new_v4(), + name: "Test Product".to_string(), + description: "Test Description".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + assert_eq!(product.name, "Test Product"); + assert_eq!(product.description, "Test Description"); + } + + #[test] + fn test_create_product_validation() { + let create_product = CreateProduct { + name: "Test Product".to_string(), + description: "Test Description".to_string(), + }; + + assert_eq!(create_product.name, "Test Product"); + assert_eq!(create_product.description, "Test Description"); + } + + #[test] + fn test_update_product_partial() { + let update_product = UpdateProduct { + name: Some("New Product Name".to_string()), + description: None, + }; + + assert_eq!(update_product.name, Some("New Product Name".to_string())); + assert_eq!(update_product.description, None); + } + } + + mod domain_error_tests { + use super::*; + + #[test] + fn test_not_found_error() { + let error = DomainError::NotFound("User not found".to_string()); + assert_eq!(error.to_string(), "Entity not found: User not found"); + } + + #[test] + fn test_invalid_input_error() { + let error = DomainError::InvalidInput("Invalid email format".to_string()); + assert_eq!(error.to_string(), "Invalid input: Invalid email format"); + } + + #[test] + fn test_internal_error() { + let error = DomainError::Internal("Database connection failed".to_string()); + assert_eq!(error.to_string(), "Internal error: Database connection failed"); + } + } + + mod repository_trait_tests { + use super::*; + use std::sync::Arc; + use tokio::sync::RwLock; + use std::collections::HashMap; + + // Mock implementation of Repository for testing + struct MockRepository { + data: Arc>>, + } + + impl MockRepository { + fn new() -> Self { + Self { + data: Arc::new(RwLock::new(HashMap::new())), + } + } + } + + #[tokio::test] + async fn test_repository_create() { + let repo = MockRepository::::new(); + let create_user = CreateUser { + username: "test_user".to_string(), + email: "test@example.com".to_string(), + }; + + let user = User { + id: Uuid::new_v4(), + username: create_user.username.clone(), + email: create_user.email.clone(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + repo.data.write().await.insert(user.id, user.clone()); + let guard = repo.data.read().await; + let stored = guard.get(&user.id).unwrap(); + assert_eq!(stored.username, create_user.username); + assert_eq!(stored.email, create_user.email); + } + + #[tokio::test] + async fn test_repository_find_by_id() { + let repo = MockRepository::::new(); + let user = User { + id: Uuid::new_v4(), + username: "test_user".to_string(), + email: "test@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + repo.data.write().await.insert(user.id, user.clone()); + let guard = repo.data.read().await; + let found = guard.get(&user.id).unwrap(); + assert_eq!(found.id, user.id); + } + + #[tokio::test] + async fn test_repository_find_all() { + let repo = MockRepository::::new(); + let user1 = User { + id: Uuid::new_v4(), + username: "user1".to_string(), + email: "user1@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + let user2 = User { + id: Uuid::new_v4(), + username: "user2".to_string(), + email: "user2@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + repo.data.write().await.insert(user1.id, user1.clone()); + repo.data.write().await.insert(user2.id, user2.clone()); + + let all = repo.data.read().await; + assert_eq!(all.len(), 2); + } + + #[tokio::test] + async fn test_repository_update() { + let repo = MockRepository::::new(); + let user = User { + id: Uuid::new_v4(), + username: "test_user".to_string(), + email: "test@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + repo.data.write().await.insert(user.id, user.clone()); + let mut guard = repo.data.write().await; + let stored = guard.get_mut(&user.id).unwrap(); + stored.username = "updated_user".to_string(); + + drop(guard); + let read_guard = repo.data.read().await; + let updated = read_guard.get(&user.id).unwrap(); + assert_eq!(updated.username, "updated_user"); + } + + #[tokio::test] + async fn test_repository_delete() { + let repo = MockRepository::::new(); + let user = User { + id: Uuid::new_v4(), + username: "test_user".to_string(), + email: "test@example.com".to_string(), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + repo.data.write().await.insert(user.id, user.clone()); + repo.data.write().await.remove(&user.id); + assert!(repo.data.read().await.get(&user.id).is_none()); + } } } diff --git a/backend/crates/memory/src/lib.rs b/backend/crates/memory/src/lib.rs index 337ffad..4df1982 100644 --- a/backend/crates/memory/src/lib.rs +++ b/backend/crates/memory/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; @@ -142,20 +153,5 @@ impl Repository for InMemoryProductRepository { } } -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} - pub type MemoryUserService = Service; pub type MemoryProductService = Service; diff --git a/backend/crates/postgres/.sqlx/query-508d8e56fc561537060a4bf378dc057cc55999d2115521419d00ca57843af046.json b/backend/crates/postgres/.sqlx/query-508d8e56fc561537060a4bf378dc057cc55999d2115521419d00ca57843af046.json new file mode 100644 index 0000000..206684b --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-508d8e56fc561537060a4bf378dc057cc55999d2115521419d00ca57843af046.json @@ -0,0 +1,48 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE users\n SET\n username = COALESCE($1, username),\n email = COALESCE($2, email),\n updated_at = CURRENT_TIMESTAMP\n WHERE id = $3\n RETURNING id, username, email, created_at, updated_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Uuid" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "508d8e56fc561537060a4bf378dc057cc55999d2115521419d00ca57843af046" +} diff --git a/backend/crates/postgres/.sqlx/query-54394890f1b4efd6487c5147e9a1942a86d7b2edfeea1cfb432758acbfbe6da6.json b/backend/crates/postgres/.sqlx/query-54394890f1b4efd6487c5147e9a1942a86d7b2edfeea1cfb432758acbfbe6da6.json new file mode 100644 index 0000000..43f6a53 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-54394890f1b4efd6487c5147e9a1942a86d7b2edfeea1cfb432758acbfbe6da6.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, username, email, created_at, updated_at\n FROM users\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "54394890f1b4efd6487c5147e9a1942a86d7b2edfeea1cfb432758acbfbe6da6" +} diff --git a/backend/crates/postgres/.sqlx/query-5bb95e6a9bea1ac6d4aab08eb63b1f9127a3a6b95046bb91bea60a2d6ee6dfc2.json b/backend/crates/postgres/.sqlx/query-5bb95e6a9bea1ac6d4aab08eb63b1f9127a3a6b95046bb91bea60a2d6ee6dfc2.json new file mode 100644 index 0000000..be3bf47 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-5bb95e6a9bea1ac6d4aab08eb63b1f9127a3a6b95046bb91bea60a2d6ee6dfc2.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, username, email, created_at, updated_at\n FROM users\n WHERE id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "5bb95e6a9bea1ac6d4aab08eb63b1f9127a3a6b95046bb91bea60a2d6ee6dfc2" +} diff --git a/backend/crates/postgres/.sqlx/query-62874c01dcd85e441608d6d92661277e3b3638412204a2aa5fad7a51c3ce855d.json b/backend/crates/postgres/.sqlx/query-62874c01dcd85e441608d6d92661277e3b3638412204a2aa5fad7a51c3ce855d.json new file mode 100644 index 0000000..534a05a --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-62874c01dcd85e441608d6d92661277e3b3638412204a2aa5fad7a51c3ce855d.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, name, description, created_at, updated_at\n FROM products\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "62874c01dcd85e441608d6d92661277e3b3638412204a2aa5fad7a51c3ce855d" +} diff --git a/backend/crates/postgres/.sqlx/query-82746e921edfb33ae6c0f434d96ce74e0de9dc8e24ae1d9fa945b9924498a652.json b/backend/crates/postgres/.sqlx/query-82746e921edfb33ae6c0f434d96ce74e0de9dc8e24ae1d9fa945b9924498a652.json new file mode 100644 index 0000000..d4cf3a3 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-82746e921edfb33ae6c0f434d96ce74e0de9dc8e24ae1d9fa945b9924498a652.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM products\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "82746e921edfb33ae6c0f434d96ce74e0de9dc8e24ae1d9fa945b9924498a652" +} diff --git a/backend/crates/postgres/.sqlx/query-882fbe26ce3c2f8baf74b89805fc4be0d99874bee7ffed9d948fc57759f03d61.json b/backend/crates/postgres/.sqlx/query-882fbe26ce3c2f8baf74b89805fc4be0d99874bee7ffed9d948fc57759f03d61.json new file mode 100644 index 0000000..eb65686 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-882fbe26ce3c2f8baf74b89805fc4be0d99874bee7ffed9d948fc57759f03d61.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO products (name, description)\n VALUES ($1, $2)\n RETURNING id, name, description, created_at, updated_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "882fbe26ce3c2f8baf74b89805fc4be0d99874bee7ffed9d948fc57759f03d61" +} diff --git a/backend/crates/postgres/.sqlx/query-b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169.json b/backend/crates/postgres/.sqlx/query-b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169.json new file mode 100644 index 0000000..5dd9ec9 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM users\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169" +} diff --git a/backend/crates/postgres/.sqlx/query-c424e1240a945e7dea975c325a1a7db21d5503ccfb9f9ad788f3897fb8216eee.json b/backend/crates/postgres/.sqlx/query-c424e1240a945e7dea975c325a1a7db21d5503ccfb9f9ad788f3897fb8216eee.json new file mode 100644 index 0000000..716319c --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-c424e1240a945e7dea975c325a1a7db21d5503ccfb9f9ad788f3897fb8216eee.json @@ -0,0 +1,48 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE products\n SET\n name = COALESCE($1, name),\n description = COALESCE($2, description),\n updated_at = CURRENT_TIMESTAMP\n WHERE id = $3\n RETURNING id, name, description, created_at, updated_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Uuid" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "c424e1240a945e7dea975c325a1a7db21d5503ccfb9f9ad788f3897fb8216eee" +} diff --git a/backend/crates/postgres/.sqlx/query-ed0fa11d45b99c47dcbf75aa0646cf91516f91388d484e398273be3532d8e027.json b/backend/crates/postgres/.sqlx/query-ed0fa11d45b99c47dcbf75aa0646cf91516f91388d484e398273be3532d8e027.json new file mode 100644 index 0000000..c66d48e --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-ed0fa11d45b99c47dcbf75aa0646cf91516f91388d484e398273be3532d8e027.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, name, description, created_at, updated_at\n FROM products\n WHERE id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "ed0fa11d45b99c47dcbf75aa0646cf91516f91388d484e398273be3532d8e027" +} diff --git a/backend/crates/postgres/.sqlx/query-ffa2228196655e9f2b8152fc4b79d720cadc68f2d12bed4f46aeb87401ce2de9.json b/backend/crates/postgres/.sqlx/query-ffa2228196655e9f2b8152fc4b79d720cadc68f2d12bed4f46aeb87401ce2de9.json new file mode 100644 index 0000000..1312ca5 --- /dev/null +++ b/backend/crates/postgres/.sqlx/query-ffa2228196655e9f2b8152fc4b79d720cadc68f2d12bed4f46aeb87401ce2de9.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO users (username, email)\n VALUES ($1, $2)\n RETURNING id, username, email, created_at, updated_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "ffa2228196655e9f2b8152fc4b79d720cadc68f2d12bed4f46aeb87401ce2de9" +} diff --git a/backend/crates/postgres/src/lib.rs b/backend/crates/postgres/src/lib.rs index 06fea97..278dee2 100644 --- a/backend/crates/postgres/src/lib.rs +++ b/backend/crates/postgres/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use sqlx::PgPool; use uuid::Uuid; diff --git a/backend/crates/tui/src/lib.rs b/backend/crates/tui/src/lib.rs index 03c2803..ff77762 100644 --- a/backend/crates/tui/src/lib.rs +++ b/backend/crates/tui/src/lib.rs @@ -1,3 +1,14 @@ +/* + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + use std::io; use std::sync::mpsc; use std::thread; diff --git a/frontend/package.json b/frontend/package.json index 1bf6348..4a240ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,6 +2,7 @@ "name": "frontend", "version": "0.1.0", "private": true, + "license": "CC-BY-NC-SA-4.0", "scripts": { "dev": "next dev --turbopack", "build": "next build", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 4d1c5c6..625eb5e 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,3 +1,14 @@ +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 6d9b739..62dacba 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,14 @@ +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import Link from "next/link"; diff --git a/frontend/src/app/products/page.tsx b/frontend/src/app/products/page.tsx index 1fd579d..36c06df 100644 --- a/frontend/src/app/products/page.tsx +++ b/frontend/src/app/products/page.tsx @@ -1,3 +1,14 @@ +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + 'use client'; import { useState, useEffect } from 'react'; @@ -69,7 +80,7 @@ export default function ProductsPage() { setIsDialogOpen(true); }; - const handleDelete = async (id: number) => { + const handleDelete = async (id: string) => { if (confirm('Are you sure you want to delete this product?')) { try { await productApi.delete(id); diff --git a/frontend/src/app/users/page.tsx b/frontend/src/app/users/page.tsx index 8cefb33..6685d24 100644 --- a/frontend/src/app/users/page.tsx +++ b/frontend/src/app/users/page.tsx @@ -1,3 +1,14 @@ +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + 'use client'; import { useState, useEffect } from 'react'; diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index efed0e8..7f98fa9 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,3 +1,14 @@ +/** + * This file is part of Sharenet. + * + * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * + * You may obtain a copy of the license at: + * https://creativecommons.org/licenses/by-nc-sa/4.0/ + * + * Copyright (c) 2024 Continuist + */ + import axios from 'axios'; const API_BASE_URL = 'http://127.0.0.1:3000';