Clean up obsolete resources and update documentation
- Remove all DynamoDB-related scripts and data files - Remove AWS SDK dependencies from scripts - Update environment files to remove DynamoDB/Lambda references - Update PROJECT_STRUCTURE.md to reflect current architecture - Clean up Terraform variables and examples - Add PLA Matte to special pricing (3999 RSD / 3499 RSD refill) - Make all table columns sortable - Remove old Terraform state backups - Remove temporary data import files
This commit is contained in:
@@ -4,6 +4,3 @@ NODE_ENV=production
|
||||
# API Configuration
|
||||
NEXT_PUBLIC_API_URL=https://api.filamenteka.rs/api
|
||||
|
||||
# AWS Configuration (for reference - no longer used)
|
||||
# AWS_REGION=eu-central-1
|
||||
# DYNAMODB_TABLE_NAME=filamenteka-filaments
|
||||
@@ -1,7 +1,7 @@
|
||||
# Project Structure
|
||||
|
||||
## Overview
|
||||
Filamenteka is organized with clear separation between environments, infrastructure, and application code.
|
||||
Filamenteka is organized with clear separation between frontend, API, and infrastructure code.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
@@ -10,53 +10,55 @@ filamenteka/
|
||||
├── app/ # Next.js app directory
|
||||
│ ├── page.tsx # Main page
|
||||
│ ├── layout.tsx # Root layout
|
||||
│ ├── admin/ # Admin pages
|
||||
│ │ ├── page.tsx # Admin login
|
||||
│ │ └── dashboard/ # Admin dashboard
|
||||
│ └── globals.css # Global styles
|
||||
│ └── upadaj/ # Admin pages
|
||||
│ ├── page.tsx # Admin login
|
||||
│ ├── dashboard/ # Filament management
|
||||
│ └── colors/ # Color management
|
||||
│
|
||||
├── src/ # Source code
|
||||
│ ├── components/ # React components
|
||||
│ │ ├── FilamentTable.tsx
|
||||
│ │ ├── FilamentForm.tsx
|
||||
│ │ └── ColorCell.tsx
|
||||
│ │ ├── FilamentTableV2.tsx
|
||||
│ │ ├── EnhancedFilters.tsx
|
||||
│ │ ├── ColorSwatch.tsx
|
||||
│ │ ├── InventoryBadge.tsx
|
||||
│ │ └── MaterialBadge.tsx
|
||||
│ ├── types/ # TypeScript types
|
||||
│ │ └── filament.ts
|
||||
│ ├── data/ # Data and utilities
|
||||
│ │ └── bambuLabColors.ts
|
||||
│ │ ├── filament.ts
|
||||
│ │ └── filament.v2.ts
|
||||
│ ├── services/ # API services
|
||||
│ │ └── api.ts
|
||||
│ └── styles/ # Component styles
|
||||
│ ├── index.css
|
||||
│ └── select.css
|
||||
│
|
||||
├── lambda/ # AWS Lambda functions
|
||||
│ ├── filaments/ # Filaments CRUD API
|
||||
│ │ ├── index.js
|
||||
│ │ └── package.json
|
||||
│ └── auth/ # Authentication API
|
||||
│ ├── index.js
|
||||
│ └── package.json
|
||||
├── api/ # Node.js Express API
|
||||
│ ├── server.js # Express server
|
||||
│ ├── migrate.js # Database migration script
|
||||
│ ├── package.json # API dependencies
|
||||
│ └── Dockerfile # Docker configuration
|
||||
│
|
||||
├── terraform/ # Infrastructure as Code
|
||||
│ ├── environments/ # Environment-specific configs
|
||||
│ │ ├── dev/
|
||||
│ │ └── prod/
|
||||
│ ├── main.tf # Main Terraform configuration
|
||||
│ ├── dynamodb.tf # DynamoDB tables
|
||||
│ ├── lambda.tf # Lambda functions
|
||||
│ ├── api_gateway.tf # API Gateway
|
||||
│ └── variables.tf # Variable definitions
|
||||
├── database/ # Database schemas
|
||||
│ └── schema.sql # PostgreSQL schema
|
||||
│
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── data-import/ # Data import tools
|
||||
│ │ ├── import-pdf-data.js
|
||||
│ │ └── clear-dynamo.js
|
||||
│ ├── security/ # Security checks
|
||||
├── terraform/ # Infrastructure as Code
|
||||
│ ├── main.tf # Main configuration
|
||||
│ ├── vpc.tf # VPC and networking
|
||||
│ ├── rds.tf # PostgreSQL RDS
|
||||
│ ├── ec2-api.tf # EC2 for API server
|
||||
│ ├── alb.tf # Application Load Balancer
|
||||
│ ├── ecr.tf # Docker registry
|
||||
│ ├── cloudflare-api.tf # Cloudflare DNS
|
||||
│ └── variables.tf # Variable definitions
|
||||
│
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── security/ # Security checks
|
||||
│ │ └── security-check.js
|
||||
│ └── build/ # Build scripts
|
||||
│ └── pre-commit.sh # Git pre-commit hook
|
||||
│
|
||||
├── config/ # Configuration files
|
||||
│ └── environments.js # Environment configuration
|
||||
├── config/ # Configuration files
|
||||
│ └── environments.js # Environment configuration
|
||||
│
|
||||
└── public/ # Static assets
|
||||
└── public/ # Static assets
|
||||
```
|
||||
|
||||
## Environment Files
|
||||
@@ -64,23 +66,25 @@ filamenteka/
|
||||
- `.env.development` - Development environment variables
|
||||
- `.env.production` - Production environment variables
|
||||
- `.env.local` - Local overrides (not committed)
|
||||
- `.env.development.local` - Local dev overrides (not committed)
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Environments
|
||||
- **Development**: Uses `dev` API Gateway stage and separate DynamoDB table
|
||||
- **Production**: Uses `production` API Gateway stage and main DynamoDB table
|
||||
### Architecture
|
||||
- **Frontend**: Next.js static site hosted on AWS Amplify
|
||||
- **API**: Node.js Express server running on EC2
|
||||
- **Database**: PostgreSQL on AWS RDS
|
||||
- **HTTPS**: Application Load Balancer with ACM certificate
|
||||
|
||||
### Data Flow
|
||||
1. Frontend (Next.js) → API Gateway → Lambda Functions → DynamoDB
|
||||
2. Authentication via JWT tokens stored in localStorage
|
||||
3. Real-time data updates every 5 minutes
|
||||
1. Frontend (Next.js) → HTTPS API (ALB) → Express Server (EC2) → PostgreSQL (RDS)
|
||||
2. Authentication via JWT tokens
|
||||
3. Real-time database synchronization
|
||||
|
||||
### Infrastructure
|
||||
- Managed via Terraform
|
||||
- Separate resources for dev/prod
|
||||
- AWS services: DynamoDB, Lambda, API Gateway, Amplify
|
||||
- AWS services: RDS, EC2, ALB, VPC, ECR, Amplify
|
||||
- Cloudflare for DNS management
|
||||
- Docker for API containerization
|
||||
|
||||
## Development Workflow
|
||||
|
||||
@@ -89,28 +93,27 @@ filamenteka/
|
||||
npm run dev
|
||||
```
|
||||
|
||||
2. **Deploy to Dev**
|
||||
2. **Deploy Infrastructure**
|
||||
```bash
|
||||
cd terraform
|
||||
terraform apply -var-file=environments/dev/terraform.tfvars
|
||||
terraform apply
|
||||
```
|
||||
|
||||
3. **Deploy to Production**
|
||||
```bash
|
||||
cd terraform
|
||||
terraform apply -var-file=environments/prod/terraform.tfvars
|
||||
```
|
||||
3. **Deploy API Updates**
|
||||
- API automatically pulls latest Docker image every 5 minutes
|
||||
- Or manually: SSH to EC2 and run deployment script
|
||||
|
||||
## Data Management
|
||||
## Database Management
|
||||
|
||||
### Import Data from PDF
|
||||
### Run Migrations
|
||||
```bash
|
||||
node scripts/data-import/import-pdf-data.js
|
||||
cd api
|
||||
npm run migrate
|
||||
```
|
||||
|
||||
### Clear DynamoDB Table
|
||||
### Connect to Database
|
||||
```bash
|
||||
node scripts/data-import/clear-dynamo.js
|
||||
psql postgresql://user:pass@rds-endpoint/filamenteka
|
||||
```
|
||||
|
||||
## Security
|
||||
@@ -118,4 +121,6 @@ node scripts/data-import/clear-dynamo.js
|
||||
- No hardcoded credentials
|
||||
- JWT authentication for admin
|
||||
- Environment-specific configurations
|
||||
- Pre-commit security checks
|
||||
- Pre-commit security checks
|
||||
- HTTPS everywhere
|
||||
- VPC isolation for backend services
|
||||
@@ -2,15 +2,11 @@
|
||||
const environments = {
|
||||
development: {
|
||||
name: 'development',
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api',
|
||||
dynamoTableName: 'filamenteka-filaments-dev',
|
||||
awsRegion: 'eu-central-1'
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL || 'https://api.filamenteka.rs/api'
|
||||
},
|
||||
production: {
|
||||
name: 'production',
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL,
|
||||
dynamoTableName: 'filamenteka-filaments',
|
||||
awsRegion: 'eu-central-1'
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
422
data.json
422
data.json
@@ -1,422 +0,0 @@
|
||||
[
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Mistletoe Green",
|
||||
"boja_hex": "#3a5a40",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 2,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Indingo Purple",
|
||||
"boja_hex": "#4b0082",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Black",
|
||||
"boja_hex": "#000000",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 2,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Black",
|
||||
"boja_hex": "#000000",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Jade White",
|
||||
"boja_hex": "#f0f8ff",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Gray",
|
||||
"boja_hex": "#808080",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Red",
|
||||
"boja_hex": "#ff0000",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Hot Pink",
|
||||
"boja_hex": "#ff69b4",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Cocoa Brown",
|
||||
"boja_hex": "#d2691e",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "White",
|
||||
"boja_hex": "#ffffff",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Coton Candy Cloud",
|
||||
"boja_hex": "#ffb6c1",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Sunflower Yellow",
|
||||
"boja_hex": "#ffda03",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Yellow",
|
||||
"boja_hex": "#ffff00",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Magenta",
|
||||
"boja_hex": "#ff00ff",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Beige",
|
||||
"boja_hex": "#f5f5dc",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Cyan",
|
||||
"boja_hex": "#00ffff",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Scarlet Red",
|
||||
"boja_hex": "#ff2400",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Mandarin Orange",
|
||||
"boja_hex": "#ff8c00",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Marine Blue",
|
||||
"boja_hex": "#000080",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Charcoal",
|
||||
"boja_hex": "#36454f",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Ivory White",
|
||||
"boja_hex": "#fffff0",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Ivory White",
|
||||
"boja_hex": "#fffff0",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Ash Gray",
|
||||
"boja_hex": "#b2beb5",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Cobalt Blue",
|
||||
"boja_hex": "#0047ab",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Turquoise",
|
||||
"boja_hex": "#40e0d0",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Nardo Gray",
|
||||
"boja_hex": "#4d4d4d",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Bright Green",
|
||||
"boja_hex": "#66ff00",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Matte",
|
||||
"boja": "Charcoal",
|
||||
"boja_hex": "#36454f",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Basic",
|
||||
"boja": "Gold",
|
||||
"boja_hex": "#ffd700",
|
||||
"refill": "Da",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Glow",
|
||||
"boja": "Glow Green",
|
||||
"boja_hex": "#39ff14",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "Wood",
|
||||
"boja": "Black Walnut",
|
||||
"boja_hex": "#5d4e37",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "CF",
|
||||
"boja": "Black",
|
||||
"boja_hex": "#000000",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PLA",
|
||||
"finish": "CF",
|
||||
"boja": "Jeans Blue",
|
||||
"boja_hex": "#5dadec",
|
||||
"refill": "Ne",
|
||||
"vakum": "Ne",
|
||||
"otvoreno": "Da",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "PETG",
|
||||
"finish": "Basic",
|
||||
"boja": "Black",
|
||||
"boja_hex": "#000000",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
},
|
||||
{
|
||||
"brand": "BambuLab",
|
||||
"tip": "ABS",
|
||||
"finish": "Basic",
|
||||
"boja": "Black",
|
||||
"boja_hex": "#000000",
|
||||
"refill": "Ne",
|
||||
"vakum": "Da",
|
||||
"otvoreno": "Ne",
|
||||
"kolicina": 1,
|
||||
"cena": 0
|
||||
}
|
||||
]
|
||||
141
import-data.js
141
import-data.js
@@ -1,141 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
|
||||
// Read the data file
|
||||
const data = JSON.parse(fs.readFileSync('./data.json', 'utf8'));
|
||||
const uniqueColors = JSON.parse(fs.readFileSync('./unique_colors.json', 'utf8'));
|
||||
|
||||
const API_URL = 'https://api.filamenteka.rs/api';
|
||||
|
||||
// First, get auth token
|
||||
async function getAuthToken() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const postData = JSON.stringify({
|
||||
username: process.env.ADMIN_USERNAME || 'admin',
|
||||
password: process.env.ADMIN_PASSWORD || 'admin'
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: 'api.filamenteka.rs',
|
||||
path: '/api/login',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': postData.length
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const response = JSON.parse(data);
|
||||
resolve(response.token);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Make authenticated request
|
||||
async function makeRequest(method, path, data, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const postData = data ? JSON.stringify(data) : '';
|
||||
|
||||
const options = {
|
||||
hostname: 'api.filamenteka.rs',
|
||||
path: `/api${path}`,
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
};
|
||||
|
||||
if (postData) {
|
||||
options.headers['Content-Length'] = postData.length;
|
||||
}
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let responseData = '';
|
||||
res.on('data', (chunk) => responseData += chunk);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
try {
|
||||
resolve(responseData ? JSON.parse(responseData) : null);
|
||||
} catch {
|
||||
resolve(responseData);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
if (postData) req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function importData() {
|
||||
try {
|
||||
console.log('Getting auth token...');
|
||||
const token = await getAuthToken();
|
||||
console.log('Authentication successful!');
|
||||
|
||||
// Import colors first
|
||||
console.log('\nImporting colors...');
|
||||
for (const color of uniqueColors) {
|
||||
try {
|
||||
await makeRequest('POST', '/colors', {
|
||||
name: color.name,
|
||||
hex: color.hex
|
||||
}, token);
|
||||
console.log(`✓ Added color: ${color.name}`);
|
||||
} catch (err) {
|
||||
if (err.message.includes('already exists')) {
|
||||
console.log(`⚠ Color already exists: ${color.name}`);
|
||||
} else {
|
||||
console.error(`✗ Failed to add color ${color.name}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import filaments
|
||||
console.log('\nImporting filaments...');
|
||||
for (const filament of data) {
|
||||
try {
|
||||
await makeRequest('POST', '/filaments', {
|
||||
brand: filament.brand,
|
||||
tip: filament.tip,
|
||||
finish: filament.finish,
|
||||
boja: filament.boja,
|
||||
bojaHex: filament.boja_hex,
|
||||
refill: filament.refill,
|
||||
vakum: filament.vakum,
|
||||
otvoreno: filament.otvoreno,
|
||||
kolicina: filament.kolicina.toString(),
|
||||
cena: filament.cena.toString()
|
||||
}, token);
|
||||
console.log(`✓ Added filament: ${filament.brand} ${filament.tip} ${filament.finish} - ${filament.boja}`);
|
||||
} catch (err) {
|
||||
console.error(`✗ Failed to add filament:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nData import completed!');
|
||||
} catch (err) {
|
||||
console.error('Import failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
importData();
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
const AWS = require('aws-sdk');
|
||||
|
||||
// Configure AWS
|
||||
AWS.config.update({
|
||||
region: process.env.AWS_REGION || 'eu-central-1'
|
||||
});
|
||||
|
||||
const dynamodb = new AWS.DynamoDB.DocumentClient();
|
||||
const TABLE_NAME = process.env.DYNAMODB_TABLE_NAME || 'filamenteka-filaments';
|
||||
|
||||
async function clearTable() {
|
||||
console.log(`Clearing all items from ${TABLE_NAME}...`);
|
||||
|
||||
try {
|
||||
// First, scan to get all items
|
||||
const scanParams = {
|
||||
TableName: TABLE_NAME,
|
||||
ProjectionExpression: 'id'
|
||||
};
|
||||
|
||||
const items = [];
|
||||
let lastEvaluatedKey = null;
|
||||
|
||||
do {
|
||||
if (lastEvaluatedKey) {
|
||||
scanParams.ExclusiveStartKey = lastEvaluatedKey;
|
||||
}
|
||||
|
||||
const result = await dynamodb.scan(scanParams).promise();
|
||||
items.push(...result.Items);
|
||||
lastEvaluatedKey = result.LastEvaluatedKey;
|
||||
} while (lastEvaluatedKey);
|
||||
|
||||
console.log(`Found ${items.length} items to delete`);
|
||||
|
||||
// Delete in batches of 25
|
||||
const chunks = [];
|
||||
for (let i = 0; i < items.length; i += 25) {
|
||||
chunks.push(items.slice(i, i + 25));
|
||||
}
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const params = {
|
||||
RequestItems: {
|
||||
[TABLE_NAME]: chunk.map(item => ({
|
||||
DeleteRequest: { Key: { id: item.id } }
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
await dynamodb.batchWrite(params).promise();
|
||||
console.log(`Deleted ${chunk.length} items`);
|
||||
}
|
||||
|
||||
console.log('Table cleared successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error clearing table:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
clearTable();
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
const AWS = require('aws-sdk');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Configure AWS
|
||||
AWS.config.update({
|
||||
region: process.env.AWS_REGION || 'eu-central-1'
|
||||
});
|
||||
|
||||
const dynamodb = new AWS.DynamoDB.DocumentClient();
|
||||
const TABLE_NAME = process.env.DYNAMODB_TABLE_NAME || 'filamenteka-filaments';
|
||||
|
||||
async function clearTable() {
|
||||
console.log(`Clearing all items from ${TABLE_NAME}...`);
|
||||
|
||||
try {
|
||||
// First, scan to get all items
|
||||
const scanParams = {
|
||||
TableName: TABLE_NAME,
|
||||
ProjectionExpression: 'id'
|
||||
};
|
||||
|
||||
const items = [];
|
||||
let lastEvaluatedKey = null;
|
||||
|
||||
do {
|
||||
if (lastEvaluatedKey) {
|
||||
scanParams.ExclusiveStartKey = lastEvaluatedKey;
|
||||
}
|
||||
|
||||
const result = await dynamodb.scan(scanParams).promise();
|
||||
items.push(...result.Items);
|
||||
lastEvaluatedKey = result.LastEvaluatedKey;
|
||||
} while (lastEvaluatedKey);
|
||||
|
||||
console.log(`Found ${items.length} items to delete`);
|
||||
|
||||
if (items.length === 0) {
|
||||
console.log('Table is already empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete in batches of 25
|
||||
const chunks = [];
|
||||
for (let i = 0; i < items.length; i += 25) {
|
||||
chunks.push(items.slice(i, i + 25));
|
||||
}
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const params = {
|
||||
RequestItems: {
|
||||
[TABLE_NAME]: chunk.map(item => ({
|
||||
DeleteRequest: { Key: { id: item.id } }
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
await dynamodb.batchWrite(params).promise();
|
||||
console.log(`Deleted ${chunk.length} items`);
|
||||
}
|
||||
|
||||
console.log('Table cleared successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error clearing table:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function importData() {
|
||||
console.log('Importing data from PDF...');
|
||||
|
||||
try {
|
||||
// Read the PDF data
|
||||
const pdfData = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, 'pdf-filaments.json'), 'utf8')
|
||||
);
|
||||
|
||||
console.log(`Found ${pdfData.length} filaments to import`);
|
||||
|
||||
// Process each filament
|
||||
const timestamp = new Date().toISOString();
|
||||
const processedFilaments = pdfData.map(filament => {
|
||||
// Determine status based on vakum and otvoreno fields
|
||||
let status = 'new';
|
||||
if (filament.otvoreno && filament.otvoreno.toLowerCase().includes('otvorena')) {
|
||||
status = 'opened';
|
||||
} else if (filament.refill && filament.refill.toLowerCase() === 'da') {
|
||||
status = 'refill';
|
||||
}
|
||||
|
||||
// Clean up finish field - if empty, default to "Basic"
|
||||
const finish = filament.finish || 'Basic';
|
||||
|
||||
return {
|
||||
id: uuidv4(),
|
||||
...filament,
|
||||
finish,
|
||||
status,
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp
|
||||
};
|
||||
});
|
||||
|
||||
// Import to DynamoDB in batches
|
||||
const chunks = [];
|
||||
for (let i = 0; i < processedFilaments.length; i += 25) {
|
||||
chunks.push(processedFilaments.slice(i, i + 25));
|
||||
}
|
||||
|
||||
let totalImported = 0;
|
||||
for (const chunk of chunks) {
|
||||
const params = {
|
||||
RequestItems: {
|
||||
[TABLE_NAME]: chunk.map(item => ({
|
||||
PutRequest: { Item: item }
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
await dynamodb.batchWrite(params).promise();
|
||||
totalImported += chunk.length;
|
||||
console.log(`Imported ${totalImported}/${processedFilaments.length} items`);
|
||||
}
|
||||
|
||||
console.log('Import completed successfully!');
|
||||
|
||||
// Verify the import
|
||||
const scanParams = {
|
||||
TableName: TABLE_NAME,
|
||||
Select: 'COUNT'
|
||||
};
|
||||
|
||||
const result = await dynamodb.scan(scanParams).promise();
|
||||
console.log(`\nVerification: ${result.Count} total items now in DynamoDB`);
|
||||
|
||||
// Show sample data
|
||||
const sampleParams = {
|
||||
TableName: TABLE_NAME,
|
||||
Limit: 3
|
||||
};
|
||||
|
||||
const sampleResult = await dynamodb.scan(sampleParams).promise();
|
||||
console.log('\nSample imported data:');
|
||||
sampleResult.Items.forEach(item => {
|
||||
console.log(`- ${item.brand} ${item.tip} ${item.finish} - ${item.boja} (${item.status})`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error importing data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('PDF Data Import Tool');
|
||||
console.log('===================');
|
||||
|
||||
// Clear existing data
|
||||
await clearTable();
|
||||
|
||||
// Import new data
|
||||
await importData();
|
||||
|
||||
console.log('\n✅ Import completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('\n❌ Import failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
const AWS = require('aws-sdk');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// Configure AWS
|
||||
AWS.config.update({
|
||||
region: process.env.AWS_REGION || 'eu-central-1'
|
||||
});
|
||||
|
||||
const dynamodb = new AWS.DynamoDB.DocumentClient();
|
||||
const TABLE_NAME = process.env.DYNAMODB_TABLE_NAME || 'filamenteka-filaments';
|
||||
|
||||
// Color mappings for common filament colors
|
||||
const colorMappings = {
|
||||
'Black': '#000000',
|
||||
'White': '#FFFFFF',
|
||||
'Red': '#FF0000',
|
||||
'Blue': '#0000FF',
|
||||
'Green': '#00FF00',
|
||||
'Yellow': '#FFFF00',
|
||||
'Orange': '#FFA500',
|
||||
'Purple': '#800080',
|
||||
'Gray': '#808080',
|
||||
'Grey': '#808080',
|
||||
'Silver': '#C0C0C0',
|
||||
'Gold': '#FFD700',
|
||||
'Brown': '#964B00',
|
||||
'Pink': '#FFC0CB',
|
||||
'Cyan': '#00FFFF',
|
||||
'Magenta': '#FF00FF',
|
||||
'Beige': '#F5F5DC',
|
||||
'Transparent': '#FFFFFF00',
|
||||
// Specific Bambu Lab colors
|
||||
'Mistletoe Green': '#50C878',
|
||||
'Indingo Purple': '#4B0082',
|
||||
'Jade White': '#F0F8FF',
|
||||
'Hot Pink': '#FF69B4',
|
||||
'Cocoa Brown': '#D2691E',
|
||||
'Cotton Candy Cloud': '#FFB6C1',
|
||||
'Sunflower Yellow': '#FFDA03',
|
||||
'Scarlet Red': '#FF2400',
|
||||
'Mandarin Orange': '#FF8C00',
|
||||
'Marine Blue': '#0066CC',
|
||||
'Charcoal': '#36454F',
|
||||
'Ivory White': '#FFFFF0',
|
||||
'Ash Gray': '#B2BEB5',
|
||||
'Cobalt Blue': '#0047AB',
|
||||
'Turquoise': '#40E0D0',
|
||||
'Nardo Gray': '#4A4A4A',
|
||||
'Bright Green': '#66FF00',
|
||||
'Glow Green': '#90EE90',
|
||||
'Black Walnut': '#5C4033',
|
||||
'Jeans Blue': '#5670A1',
|
||||
'Forest Green': '#228B22',
|
||||
'Lavender Purple': '#B57EDC'
|
||||
};
|
||||
|
||||
function getColorHex(colorName) {
|
||||
// Try exact match first
|
||||
if (colorMappings[colorName]) {
|
||||
return colorMappings[colorName];
|
||||
}
|
||||
|
||||
// Try to find color in the name
|
||||
for (const [key, value] of Object.entries(colorMappings)) {
|
||||
if (colorName.toLowerCase().includes(key.toLowerCase())) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseInventory(oldFilament) {
|
||||
let total = 1;
|
||||
let vacuum = 0;
|
||||
let opened = 0;
|
||||
|
||||
// Parse kolicina (quantity)
|
||||
if (oldFilament.kolicina) {
|
||||
const qty = parseInt(oldFilament.kolicina);
|
||||
if (!isNaN(qty)) {
|
||||
total = qty;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse vakum field
|
||||
if (oldFilament.vakum) {
|
||||
const vakumLower = oldFilament.vakum.toLowerCase();
|
||||
if (vakumLower.includes('vakuum') || vakumLower.includes('vakum')) {
|
||||
// Check for multiplier
|
||||
const match = vakumLower.match(/x(\d+)/);
|
||||
vacuum = match ? parseInt(match[1]) : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse otvoreno field
|
||||
if (oldFilament.otvoreno) {
|
||||
const otvorenoLower = oldFilament.otvoreno.toLowerCase();
|
||||
if (otvorenoLower.includes('otvorena') || otvorenoLower.includes('otvoreno')) {
|
||||
// Check for multiplier
|
||||
const match = otvorenoLower.match(/(\d+)x/);
|
||||
if (match) {
|
||||
opened = parseInt(match[1]);
|
||||
} else {
|
||||
const match2 = otvorenoLower.match(/x(\d+)/);
|
||||
opened = match2 ? parseInt(match2[1]) : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate available
|
||||
const available = vacuum + opened;
|
||||
const inUse = Math.max(0, total - available);
|
||||
|
||||
return {
|
||||
total: total || 1,
|
||||
available: available,
|
||||
inUse: inUse,
|
||||
locations: {
|
||||
vacuum: vacuum,
|
||||
opened: opened,
|
||||
printer: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function determineStorageCondition(oldFilament) {
|
||||
if (oldFilament.vakum && oldFilament.vakum.toLowerCase().includes('vakuum')) {
|
||||
return 'vacuum';
|
||||
}
|
||||
if (oldFilament.otvoreno && oldFilament.otvoreno.toLowerCase().includes('otvorena')) {
|
||||
return 'opened';
|
||||
}
|
||||
return 'sealed';
|
||||
}
|
||||
|
||||
function parseMaterial(oldFilament) {
|
||||
const base = oldFilament.tip || 'PLA';
|
||||
let modifier = null;
|
||||
|
||||
if (oldFilament.finish && oldFilament.finish !== 'Basic' && oldFilament.finish !== '') {
|
||||
modifier = oldFilament.finish;
|
||||
}
|
||||
|
||||
// Handle special PLA types
|
||||
if (base === 'PLA' && modifier) {
|
||||
// These are actually base materials, not modifiers
|
||||
if (modifier === 'PETG' || modifier === 'ABS' || modifier === 'TPU') {
|
||||
return { base: modifier, modifier: null };
|
||||
}
|
||||
}
|
||||
|
||||
return { base, modifier };
|
||||
}
|
||||
|
||||
function generateSKU(brand, material, color) {
|
||||
const brandCode = brand.substring(0, 3).toUpperCase();
|
||||
const materialCode = material.base.substring(0, 3);
|
||||
const colorCode = color.name.substring(0, 3).toUpperCase();
|
||||
const random = Math.random().toString(36).substring(2, 5).toUpperCase();
|
||||
return `${brandCode}-${materialCode}-${colorCode}-${random}`;
|
||||
}
|
||||
|
||||
function migrateFilament(oldFilament) {
|
||||
const material = parseMaterial(oldFilament);
|
||||
const inventory = parseInventory(oldFilament);
|
||||
const colorHex = getColorHex(oldFilament.boja);
|
||||
|
||||
const newFilament = {
|
||||
// Keep existing fields
|
||||
id: oldFilament.id || uuidv4(),
|
||||
sku: generateSKU(oldFilament.brand, material, { name: oldFilament.boja }),
|
||||
|
||||
// Product info
|
||||
brand: oldFilament.brand,
|
||||
type: oldFilament.tip || 'PLA',
|
||||
material: material,
|
||||
color: {
|
||||
name: oldFilament.boja || 'Unknown',
|
||||
hex: colorHex
|
||||
},
|
||||
|
||||
// Physical properties
|
||||
weight: {
|
||||
value: 1000, // Default to 1kg
|
||||
unit: 'g'
|
||||
},
|
||||
diameter: 1.75, // Standard diameter
|
||||
|
||||
// Inventory
|
||||
inventory: inventory,
|
||||
|
||||
// Pricing
|
||||
pricing: {
|
||||
purchasePrice: oldFilament.cena ? parseFloat(oldFilament.cena) : null,
|
||||
currency: 'RSD',
|
||||
supplier: null,
|
||||
purchaseDate: null
|
||||
},
|
||||
|
||||
// Condition
|
||||
condition: {
|
||||
isRefill: oldFilament.refill === 'Da',
|
||||
openedDate: oldFilament.otvoreno ? new Date().toISOString() : null,
|
||||
expiryDate: null,
|
||||
storageCondition: determineStorageCondition(oldFilament),
|
||||
humidity: null
|
||||
},
|
||||
|
||||
// Metadata
|
||||
tags: [],
|
||||
notes: null,
|
||||
images: [],
|
||||
|
||||
// Timestamps
|
||||
createdAt: oldFilament.createdAt || new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
lastUsed: null,
|
||||
|
||||
// Keep old structure temporarily for backwards compatibility
|
||||
_legacy: {
|
||||
tip: oldFilament.tip,
|
||||
finish: oldFilament.finish,
|
||||
boja: oldFilament.boja,
|
||||
refill: oldFilament.refill,
|
||||
vakum: oldFilament.vakum,
|
||||
otvoreno: oldFilament.otvoreno,
|
||||
kolicina: oldFilament.kolicina,
|
||||
cena: oldFilament.cena,
|
||||
status: oldFilament.status
|
||||
}
|
||||
};
|
||||
|
||||
// Add tags based on properties
|
||||
if (material.modifier === 'Silk') newFilament.tags.push('silk');
|
||||
if (material.modifier === 'Matte') newFilament.tags.push('matte');
|
||||
if (material.modifier === 'CF') newFilament.tags.push('engineering', 'carbon-fiber');
|
||||
if (material.modifier === 'Wood') newFilament.tags.push('specialty', 'wood-fill');
|
||||
if (material.modifier === 'Glow') newFilament.tags.push('specialty', 'glow-in-dark');
|
||||
if (material.base === 'PETG') newFilament.tags.push('engineering', 'chemical-resistant');
|
||||
if (material.base === 'ABS') newFilament.tags.push('engineering', 'high-temp');
|
||||
if (material.base === 'TPU') newFilament.tags.push('flexible', 'engineering');
|
||||
if (newFilament.condition.isRefill) newFilament.tags.push('refill', 'eco-friendly');
|
||||
|
||||
return newFilament;
|
||||
}
|
||||
|
||||
async function migrateData() {
|
||||
console.log('Starting migration to new data structure...');
|
||||
|
||||
try {
|
||||
// Scan all existing items
|
||||
const scanParams = {
|
||||
TableName: TABLE_NAME
|
||||
};
|
||||
|
||||
const items = [];
|
||||
let lastEvaluatedKey = null;
|
||||
|
||||
do {
|
||||
if (lastEvaluatedKey) {
|
||||
scanParams.ExclusiveStartKey = lastEvaluatedKey;
|
||||
}
|
||||
|
||||
const result = await dynamodb.scan(scanParams).promise();
|
||||
items.push(...result.Items);
|
||||
lastEvaluatedKey = result.LastEvaluatedKey;
|
||||
} while (lastEvaluatedKey);
|
||||
|
||||
console.log(`Found ${items.length} items to migrate`);
|
||||
|
||||
// Check if already migrated
|
||||
if (items.length > 0 && items[0].material && items[0].inventory) {
|
||||
console.log('Data appears to be already migrated!');
|
||||
const confirm = process.argv.includes('--force');
|
||||
if (!confirm) {
|
||||
console.log('Use --force flag to force migration');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate each item
|
||||
const migratedItems = items.map(item => migrateFilament(item));
|
||||
|
||||
// Show sample
|
||||
console.log('\nSample migrated data:');
|
||||
console.log(JSON.stringify(migratedItems[0], null, 2));
|
||||
|
||||
// Update items in batches
|
||||
const chunks = [];
|
||||
for (let i = 0; i < migratedItems.length; i += 25) {
|
||||
chunks.push(migratedItems.slice(i, i + 25));
|
||||
}
|
||||
|
||||
console.log(`\nUpdating ${migratedItems.length} items in DynamoDB...`);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const params = {
|
||||
RequestItems: {
|
||||
[TABLE_NAME]: chunk.map(item => ({
|
||||
PutRequest: { Item: item }
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
await dynamodb.batchWrite(params).promise();
|
||||
console.log(`Updated ${chunk.length} items`);
|
||||
}
|
||||
|
||||
console.log('\n✅ Migration completed successfully!');
|
||||
|
||||
// Show summary
|
||||
const summary = {
|
||||
totalItems: migratedItems.length,
|
||||
brands: [...new Set(migratedItems.map(i => i.brand))],
|
||||
materials: [...new Set(migratedItems.map(i => i.material.base))],
|
||||
modifiers: [...new Set(migratedItems.map(i => i.material.modifier).filter(Boolean))],
|
||||
storageConditions: [...new Set(migratedItems.map(i => i.condition.storageCondition))],
|
||||
totalInventory: migratedItems.reduce((sum, i) => sum + i.inventory.total, 0),
|
||||
availableInventory: migratedItems.reduce((sum, i) => sum + i.inventory.available, 0)
|
||||
};
|
||||
|
||||
console.log('\nMigration Summary:');
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration
|
||||
if (require.main === module) {
|
||||
console.log('Data Structure Migration Tool');
|
||||
console.log('============================');
|
||||
console.log('This will migrate all filaments to the new structure');
|
||||
console.log('Old data will be preserved in _legacy field\n');
|
||||
|
||||
migrateData();
|
||||
}
|
||||
1019
scripts/package-lock.json
generated
1019
scripts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "filamenteka-scripts",
|
||||
"version": "1.0.0",
|
||||
"description": "Migration and utility scripts for Filamenteka",
|
||||
"scripts": {
|
||||
"migrate": "node migrate-with-parser.js",
|
||||
"migrate:clear": "node migrate-with-parser.js --clear"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.1472.0",
|
||||
"axios": "^1.6.2",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"dotenv": "^16.3.1",
|
||||
"uuid": "^9.0.1"
|
||||
}
|
||||
}
|
||||
@@ -250,16 +250,16 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
<th onClick={() => handleSort('color.name')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Boja
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th onClick={() => handleSort('inventory.total')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Stanje
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th onClick={() => handleSort('weight.value')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Težina
|
||||
</th>
|
||||
<th onClick={() => handleSort('pricing.purchasePrice')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Cena
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th onClick={() => handleSort('condition.isRefill')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
@@ -294,8 +294,8 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
{(() => {
|
||||
// PLA Basic pricing logic
|
||||
if (filament.material.base === 'PLA' && !filament.material.modifier) {
|
||||
// PLA Basic and Matte pricing logic
|
||||
if (filament.material.base === 'PLA' && (!filament.material.modifier || filament.material.modifier === 'Matte')) {
|
||||
if (filament.condition.isRefill && filament.condition.storageCondition !== 'opened') {
|
||||
return '3.499 RSD';
|
||||
} else if (!filament.condition.isRefill && filament.condition.storageCondition === 'vacuum') {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# Copy this file to terraform.tfvars and fill in your values
|
||||
|
||||
github_repository = "https://github.com/yourusername/filamenteka"
|
||||
github_token = "ghp_your_github_token_here"
|
||||
# GitHub repository for Amplify
|
||||
github_repository = "https://github.com/yourusername/filamenteka"
|
||||
github_token = "ghp_your_github_token_here"
|
||||
|
||||
# Admin Authentication
|
||||
jwt_secret = "your-secret-key-at-least-32-characters-long"
|
||||
admin_username = "admin"
|
||||
admin_password_hash = "bcrypt-hash-generated-by-generate-password-hash.js"
|
||||
# Domain configuration
|
||||
domain_name = "filamenteka.yourdomain.com"
|
||||
|
||||
# Optional: Custom domain
|
||||
# domain_name = "filamenteka.yourdomain.com"
|
||||
# Cloudflare configuration (optional)
|
||||
cloudflare_api_token = "your-cloudflare-api-token"
|
||||
@@ -1,118 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Sunflower Yellow",
|
||||
"hex": "#ffda03"
|
||||
},
|
||||
{
|
||||
"name": "Hot Pink",
|
||||
"hex": "#ff69b4"
|
||||
},
|
||||
{
|
||||
"name": "Indingo Purple",
|
||||
"hex": "#4b0082"
|
||||
},
|
||||
{
|
||||
"name": "Coton Candy Cloud",
|
||||
"hex": "#ffb6c1"
|
||||
},
|
||||
{
|
||||
"name": "Nardo Gray",
|
||||
"hex": "#4d4d4d"
|
||||
},
|
||||
{
|
||||
"name": "Bright Green",
|
||||
"hex": "#66ff00"
|
||||
},
|
||||
{
|
||||
"name": "Glow Green",
|
||||
"hex": "#39ff14"
|
||||
},
|
||||
{
|
||||
"name": "Cocoa Brown",
|
||||
"hex": "#d2691e"
|
||||
},
|
||||
{
|
||||
"name": "White",
|
||||
"hex": "#ffffff"
|
||||
},
|
||||
{
|
||||
"name": "Beige",
|
||||
"hex": "#f5f5dc"
|
||||
},
|
||||
{
|
||||
"name": "Gold",
|
||||
"hex": "#ffd700"
|
||||
},
|
||||
{
|
||||
"name": "Black",
|
||||
"hex": "#000000"
|
||||
},
|
||||
{
|
||||
"name": "Cyan",
|
||||
"hex": "#00ffff"
|
||||
},
|
||||
{
|
||||
"name": "Gray",
|
||||
"hex": "#808080"
|
||||
},
|
||||
{
|
||||
"name": "Cobalt Blue",
|
||||
"hex": "#0047ab"
|
||||
},
|
||||
{
|
||||
"name": "Magenta",
|
||||
"hex": "#ff00ff"
|
||||
},
|
||||
{
|
||||
"name": "Charcoal",
|
||||
"hex": "#36454f"
|
||||
},
|
||||
{
|
||||
"name": "Mistletoe Green",
|
||||
"hex": "#3a5a40"
|
||||
},
|
||||
{
|
||||
"name": "Black Walnut",
|
||||
"hex": "#5d4e37"
|
||||
},
|
||||
{
|
||||
"name": "Jeans Blue",
|
||||
"hex": "#5dadec"
|
||||
},
|
||||
{
|
||||
"name": "Mandarin Orange",
|
||||
"hex": "#ff8c00"
|
||||
},
|
||||
{
|
||||
"name": "Ash Gray",
|
||||
"hex": "#b2beb5"
|
||||
},
|
||||
{
|
||||
"name": "Ivory White",
|
||||
"hex": "#fffff0"
|
||||
},
|
||||
{
|
||||
"name": "Red",
|
||||
"hex": "#ff0000"
|
||||
},
|
||||
{
|
||||
"name": "Turquoise",
|
||||
"hex": "#40e0d0"
|
||||
},
|
||||
{
|
||||
"name": "Scarlet Red",
|
||||
"hex": "#ff2400"
|
||||
},
|
||||
{
|
||||
"name": "Marine Blue",
|
||||
"hex": "#000080"
|
||||
},
|
||||
{
|
||||
"name": "Jade White",
|
||||
"hex": "#f0f8ff"
|
||||
},
|
||||
{
|
||||
"name": "Yellow",
|
||||
"hex": "#ffff00"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user