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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 136 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,139 @@ Thumbs.db
# Git-specific
*.orig

# Ignore plugin build outputs if copied manually
assets/plugins/*.wasm
# Environment and sensitive files
.env
.env.local
.env.development
.env.test
.env.production
*.key
*.pem
*.p12
*.pfx
secrets.json
config.json

# Test files
test_*.rs
test_*.js
test_*.py
test_*.ts
test_*.tsx
*_test.rs
*_test.js
*_test.py
*_test.ts
*_test.tsx
*.test.rs
*.test.js
*.test.py
*.test.ts
*.test.tsx
*.spec.rs
*.spec.js
*.spec.py
*.spec.ts
*.spec.tsx

# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# Build outputs
dist/
build/
out/
.next/
.nuxt/
.vuepress/dist

# Coverage reports
coverage/
*.lcov
.nyc_output

# Temporary files
*.tmp
*.temp
.cache/
.parcel-cache/

# Database files
*.db
*.sqlite
*.sqlite3

# Backup files
*.bak
*.backup
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Rust specific
**/target/
Cargo.lock

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# pipenv
Pipfile.lock

# PEP 582
__pypackages__/

# Celery
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json
8 changes: 6 additions & 2 deletions sandcrate-backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[package]
name = "sandcrate-backend"
edition = "2021"
default-run = "sandcrate-backend"

[dependencies]
axum = { version = "0.7", features = ["ws"] }
axum = { version = "0.7", features = ["ws", "multipart"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand All @@ -17,4 +18,7 @@ tower-http = { version = "0.5", features = ["cors"] }
axum-extra = { version = "0.9", features = ["typed-header"] }
tokio-tungstenite = "0.21"
futures-util = { version = "0.3", features = ["sink"] }
uuid = { version = "1.0", features = ["v4"] }
uuid = { version = "1.0", features = ["v4"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid", "migrate"] }
dotenv = "0.15"
async-trait = "0.1"
17 changes: 17 additions & 0 deletions sandcrate-backend/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Database Configuration
DATABASE_URL=postgresql://sandcrate:sandcrate@localhost:5432/sandcrate

# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRATION_HOURS=24

# Server Configuration
SERVER_HOST=127.0.0.1
SERVER_PORT=3000

# Plugin Configuration
PLUGINS_DIR=../assets/plugins
MAX_PLUGIN_SIZE_MB=50

# Logging
LOG_LEVEL=info
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
-- Create custom types
CREATE TYPE plugin_status AS ENUM ('active', 'inactive', 'error', 'processing');
CREATE TYPE execution_status AS ENUM ('running', 'completed', 'failed', 'cancelled');
CREATE TYPE user_role AS ENUM ('admin', 'user', 'guest');

-- Create users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE,
name VARCHAR(255) NOT NULL,
role user_role NOT NULL DEFAULT 'user',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMP WITH TIME ZONE
);

-- Create plugins table
CREATE TABLE plugins (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL UNIQUE,
file_path TEXT NOT NULL,
file_size BIGINT NOT NULL,
description TEXT,
version VARCHAR(50) NOT NULL DEFAULT '1.0.0',
author VARCHAR(255),
tags TEXT[] DEFAULT '{}',
status plugin_status NOT NULL DEFAULT 'active',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
last_executed_at TIMESTAMP WITH TIME ZONE,
execution_count INTEGER NOT NULL DEFAULT 0,
average_execution_time_ms BIGINT
);

-- Create plugin_executions table
CREATE TABLE plugin_executions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
plugin_id UUID NOT NULL REFERENCES plugins(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
session_id VARCHAR(255),
parameters JSONB,
result TEXT,
error TEXT,
execution_time_ms BIGINT NOT NULL DEFAULT 0,
status execution_status NOT NULL DEFAULT 'running',
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE
);

-- Create indexes for better performance
CREATE INDEX idx_plugins_status ON plugins(status);
CREATE INDEX idx_plugins_created_at ON plugins(created_at DESC);
CREATE INDEX idx_plugins_filename ON plugins(filename);
CREATE INDEX idx_plugin_executions_plugin_id ON plugin_executions(plugin_id);
CREATE INDEX idx_plugin_executions_started_at ON plugin_executions(started_at DESC);
CREATE INDEX idx_plugin_executions_user_id ON plugin_executions(user_id);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);

-- Create function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';

-- Create triggers to automatically update updated_at
CREATE TRIGGER update_plugins_updated_at BEFORE UPDATE ON plugins
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- Insert default admin user
INSERT INTO users (username, name, role, is_active)
VALUES ('admin', 'System Administrator', 'admin', true)
ON CONFLICT (username) DO NOTHING;
51 changes: 51 additions & 0 deletions sandcrate-backend/scripts/init_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::env;
use dotenv::dotenv;

use sandcrate_backend::{
DatabaseConfig, create_pool, PostgresPluginRepository, PluginService
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok();

println!("Initializing Sandcrate database...");

let db_config = DatabaseConfig::default();
let db_pool = create_pool(&db_config).await?;

println!("Database connection established");

let plugin_repo = PostgresPluginRepository::new(db_pool.clone());
let plugin_service = PluginService::new(Box::new(plugin_repo));

let plugins_dir = env::var("PLUGINS_DIR").unwrap_or_else(|_| "../assets/plugins".to_string());
println!("Syncing plugins from: {}", plugins_dir);

match plugin_service.sync_plugins_from_filesystem(&plugins_dir).await {
Ok(plugins) => {
println!("Synced {} plugins from filesystem", plugins.len());
for plugin in plugins {
println!(" - {} (v{})", plugin.name, plugin.version);
}
}
Err(e) => {
println!("Failed to sync plugins: {}", e);
}
}

println!("\nAll plugins in database:");
match plugin_service.list_plugins(None, None).await {
Ok(plugins) => {
for plugin in plugins {
println!(" - {} (v{}) - {}", plugin.name, plugin.version, plugin.status);
}
}
Err(e) => {
println!("Failed to list plugins: {}", e);
}
}

println!("\nDatabase initialization completed!");
Ok(())
}
49 changes: 49 additions & 0 deletions sandcrate-backend/scripts/setup_db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash

# Database setup script for Sandcrate
set -e

echo "🚀 Setting up PostgreSQL database for Sandcrate..."

# Check if PostgreSQL is installed
if ! command -v psql &> /dev/null; then
echo "❌ PostgreSQL is not installed. Please install PostgreSQL first."
echo " Ubuntu/Debian: sudo apt-get install postgresql postgresql-contrib"
echo " macOS: brew install postgresql"
echo " Windows: Download from https://www.postgresql.org/download/windows/"
exit 1
fi

# Check if PostgreSQL service is running
if ! pg_isready -q; then
echo "❌ PostgreSQL service is not running. Please start PostgreSQL first."
echo " Ubuntu/Debian: sudo systemctl start postgresql"
echo " macOS: brew services start postgresql"
exit 1
fi

# Create database and user
echo "📦 Creating database and user..."

# Create user (if it doesn't exist)
psql -U postgres -c "CREATE USER sandcrate WITH PASSWORD 'sandcrate';" 2>/dev/null || echo "User sandcrate already exists"

# Create database (if it doesn't exist)
psql -U postgres -c "CREATE DATABASE sandcrate OWNER sandcrate;" 2>/dev/null || echo "Database sandcrate already exists"

# Grant privileges
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE sandcrate TO sandcrate;"

echo "✅ Database setup completed!"
echo ""
echo "📋 Next steps:"
echo "1. Copy env.example to .env and update the DATABASE_URL if needed"
echo "2. Run migrations: cargo sqlx migrate run"
echo "3. Start the backend: cargo run"
echo ""
echo "🔗 Database connection:"
echo " Host: localhost"
echo " Port: 5432"
echo " Database: sandcrate"
echo " User: sandcrate"
echo " Password: sandcrate"
Loading
Loading