feat: initialize backend structure with core modules and services

- Add base project structure with Cargo.toml and .env
- Implement core modules: models, services, repositories, controllers
- Set up logging, error handling, and middleware
- Configure routes and API documentation
- Add crypto and websocket functionality
- Include basic user management system
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-12 01:37:08 -03:00
1 parent 23ba2c34ad
commit f82191dd83
30 files changed
+5957 -1

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
---
alwaysApply: true
---
## Prompt Versão 2 (Conciso)
```
## ESPECIALISTA EM RUST PARA INICIANTES - MODO DIDÁTICO DIRETO
### Configuração Principal
Você é um instrutor de Rust especializado em ensino para iniciantes absolutos. Sua comunicação segue estas regras rigorosamente:
**Concisão com Qualidade:**
- Máximo 2-3 frases por explicação, sem floreios
- Uma idea por parágrafo
- Omitir redundâncias
**Explicação de Erros:**
- Estrutura: [O que aconteceu] → [Por quê] → [Como corrigir]
- Sempre com analogia/comparação simples
- Nunca exceda 1 parágrafo por erro
**Sequência de Raciocínio:**
1. Identifique qual conceito está sendo abordado
2. Relate com conhecimento anterior do usuário
3. Forneça exemplo funcional mínimo
4. Mostre o erro comum e correção
### Comportamento na Interação
- Quando o usuário pede uma explicação: context breve → conceito → código mínimo funcionando
- Quando ocorre erro: analise o tipo de erro → explique por que ocorreu → solução em 1-2 linhas de correção
- Se pedir sobre ownership/borrow checker: use metáfora de "empréstimo de livro" ou similar
### Exemplos de Tom Esperado
**Ruim:** "A propriedade em Rust é um conceito fundamental que permite..."
**Bom:** "Propriedade = só um dono por vez (como uma chave única)."
**Ruim:** "Este erro ocorre porque a função tenta acessar a variável..."
**Bom:** "Erro: tentou usar `x` após move. Por quê? `x` foi transferido. Solução: use referência `&x` ou clone se precisar cópia."
### Foco de Aprendizado
Prioridades: Variables → Data Types → Functions → Control Flow → Ownership/Borrowing → Enums/Structs → Error Handling
Adapte profundidade conforme necessário, mas SEMPRE mantenha a brevidade.
View File
Whitespace-only changes.
+5 -1
View File
@@ -14,4 +14,8 @@ target/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
# Added by cargo
/target
Generated
+2745
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "OllaHubBackend"
version = "0.1.0"
edition = "2021"
[dependencies]
actix = "0.13.5"
actix-rt = "2.11.0"
actix-web = "4.11.0"
actix-web-actors = "4.3.0"
actix-web-lab = "0.24.3"
awc = "3.8.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sysinfo = "0.30"
tokio = { version = "1", features = ["sync","rt","macros"] }
aes-gcm = "0.10"
rand = "0.8"
base64 = "0.21"
dirs = "5.0"
uuid = { version = "1", features = ["v4","serde"] }
hex = "0.4"
utoipa = { version = "4", features = ["actix_extras"] }
utoipa-swagger-ui = { version = "4", features = ["actix-web"] }
validator = { version = "0.16", features = ["derive"] }
jsonwebtoken = "9"
+88
View File
@@ -0,0 +1,88 @@
# Ollama – Verificação, Instalação e Inicialização
Você quer garantir que o Ollama esteja instalado e rodando sem drama. Este backend expõe dois endpoints para isso: um que verifica o estado atual e outro que instala e inicia o serviço quando necessário.
Uma linha direta: porta local é `11434`. Se a API responde, tá funcionando.
## O que os endpoints fazem
- `GET /ollama/install-check`
- Checa se o binário está no `PATH`
- Lê a versão (`ollama -v`) quando disponível
- Verifica se o serviço está ativo (Linux via `systemctl`; Windows/macOS por processo/API)
- Testa a API local: `/api/version` e `/api/tags`
- `POST /ollama/install`
- Instala o Ollama conforme seu sistema operacional
- Configura serviço (quando aplicável)
- Inicia e valida a API com polling
- Faz rollback básico se algo falhar (para serviço no Linux)
## Requisitos mínimos
- Porta `11434` livre (localhost)
- Rede para baixar pacotes (instalação)
- Permissões adequadas:
- Linux: `systemctl` (para `enable/start`) pode exigir privilégios
- Windows: instalador pode abrir GUI; usamos fallback por `.zip` quando possível
- macOS: se tiver Homebrew, instalamos; caso contrário, baixe manualmente
### GPU (opcional)
- NVIDIA: bibliotecas CUDA adequadas do pacote do Ollama
- AMD: ROCm (Windows requer pacote adicional)
- CPU-only funciona, só é mais lento
## Detalhes por sistema
### Linux
- Instalação oficial via script:
- `curl -fsSL https://ollama.com/install.sh | sh`
- Serviço: `systemctl enable ollama` e `systemctl start ollama`
- Verificação de serviço: `systemctl is-active ollama`
- API local: `http://localhost:11434`
- Ajustes de segurança (se necessário): SELinux/AppArmor
### Windows
- Preferência: `OllamaSetup.exe` (instala no seu perfil)
- Fallback: pacote `.zip` standalone para `LOCALAPPDATA\Programs\Ollama`
- Início do serviço: `ollama serve` (se não rodando)
- API local: `http://localhost:11434`
- Variáveis de ambiente: defina `OLLAMA_HOST`, `OLLAMA_MODELS` em Configurações do Windows
- Login item: desative em `Task Manager > Startup apps` se não quiser iniciar junto com o sistema
### macOS
- Se tiver Homebrew: `brew install ollama`
- Senão: baixe o app em https://ollama.com/download/
- Início do serviço: `ollama serve` se o CLI estiver disponível
- API local: `http://localhost:11434`
## Erros comuns e soluções
- Sem permissão para `systemctl`
- Solução: rode com privilégios ou instale para usuário e inicie com `ollama serve`
- Download bloqueado por proxy/antivírus
- Solução: autorize o binário/endpoint, tente novamente; em último caso, baixe manualmente
- API inacessível após instalação
- Solução: verifique logs (`systemctl status ollama` no Linux, `%LOCALAPPDATA%\Ollama\server.log` no Windows), confirme porta 11434 livre
## Exemplos rápidos
Verificar instalação:
```bash
curl http://localhost:8080/ollama/install-check
```
Instalar e iniciar:
```bash
curl -X POST http://localhost:8080/ollama/install
```
## Observações finais
- MacOS/Windows costumam registrar Ollama como login item. Desative se preferir.
- Linux: se não usar `systemd`, inicie com `ollama serve` e considere ferramentas alternativas de serviço.
- Se algo quebrar, os logs do endpoint vão apontar a etapa e o erro. Isso ajuda a corrigir rápido.
+967
View File
@@ -0,0 +1,967 @@
{
"metadata": {
"source": "ollama.com/library + buscas: uncensored, abliterated, code, reasoning",
"collection_date": "2025-10-29",
"total_models": 270,
"note": "Integração de modelos oficiais + comunitários (uncensored/abliterated variants). Modelos de reasoning incluem variantes com foco em raciocínio (ex: CoT, logical deduction). Duplicatas removidas; métricas atualizadas onde possível. API para lista remota ainda limitada (ver GitHub #8241)."
},
"models": [
{
"name": "gpt-oss",
"description": "OpenAI's open-weight models designed for powerful reasoning, agentic tasks, and versatile developer use cases",
"capabilities": ["tools", "thinking", "cloud"],
"parameter_sizes": ["20b", "120b"],
"pulls": "3.8M",
"tags_count": 5,
"last_updated": "2 weeks ago",
"url": "https://ollama.com/library/gpt-oss"
},
{
"name": "qwen3-vl",
"description": "The most powerful vision-language model in the Qwen model family to date",
"capabilities": ["vision", "cloud"],
"parameter_sizes": ["2b", "4b", "8b", "30b", "32b"],
"pulls": "22.8K",
"tags_count": 49,
"last_updated": "13 minutes ago",
"url": "https://ollama.com/library/qwen3-vl"
},
{
"name": "deepseek-r1",
"description": "DeepSeek-R1 is a family of open reasoning models with performance approaching that of leading models, such as O3 and Gemini 2.5 Pro",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["1.5b", "7b", "8b", "14b", "32b", "70b", "671b"],
"pulls": "68.4M",
"tags_count": 35,
"last_updated": "3 months ago",
"url": "https://ollama.com/library/deepseek-r1"
},
{
"name": "qwen3-coder",
"description": "Alibaba's performant long context models for agentic and coding tasks",
"capabilities": ["tools", "cloud"],
"parameter_sizes": ["30b", "480b"],
"pulls": "575.7K",
"tags_count": 10,
"last_updated": "1 month ago",
"url": "https://ollama.com/library/qwen3-coder"
},
{
"name": "gemma3",
"description": "The current, most capable model that runs on a single GPU",
"capabilities": ["vision"],
"parameter_sizes": ["270m", "1b", "4b", "12b", "27b"],
"pulls": "23.1M",
"tags_count": 26,
"last_updated": "2 months ago",
"url": "https://ollama.com/library/gemma3"
},
{
"name": "glm-4.6",
"description": "Advanced agentic, reasoning and coding capabilities",
"capabilities": ["cloud"],
"parameter_sizes": [],
"pulls": "11.8K",
"tags_count": 1,
"last_updated": "2 weeks ago",
"url": "https://ollama.com/library/glm-4.6"
},
{
"name": "embeddinggemma",
"description": "EmbeddingGemma is a 300M parameter embedding model from Google",
"capabilities": ["embedding"],
"parameter_sizes": ["300m"],
"pulls": "111.2K",
"tags_count": 5,
"last_updated": "1 month ago",
"url": "https://ollama.com/library/embeddinggemma"
},
{
"name": "qwen3",
"description": "Qwen3 is the latest generation of large language models in Qwen series, offering a comprehensive suite of dense and mixture-of-experts (MoE) models",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["0.6b", "1.7b", "4b", "8b", "14b", "30b", "32b", "235b"],
"pulls": "11.7M",
"tags_count": 58,
"last_updated": "2 weeks ago",
"url": "https://ollama.com/library/qwen3"
},
{
"name": "deepseek-v3.1",
"description": "DeepSeek-V3.1-Terminus is a hybrid model that supports both thinking mode and non-thinking mode",
"capabilities": ["tools", "thinking", "cloud"],
"parameter_sizes": ["671b"],
"pulls": "120.6K",
"tags_count": 8,
"last_updated": "1 month ago",
"url": "https://ollama.com/library/deepseek-v3.1"
},
{
"name": "llama3.1",
"description": "Llama 3.1 is a new state-of-the-art model from Meta available in 8B, 70B and 405B parameter sizes",
"capabilities": ["tools"],
"parameter_sizes": ["8b", "70b", "405b"],
"pulls": "105.1M",
"tags_count": 93,
"last_updated": "11 months ago",
"url": "https://ollama.com/library/llama3.1"
},
{
"name": "nomic-embed-text",
"description": "A high-performing open embedding model with a large token context window",
"capabilities": ["embedding"],
"parameter_sizes": [],
"pulls": "44.3M",
"tags_count": 3,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/nomic-embed-text"
},
{
"name": "llama3.2",
"description": "Meta's Llama 3.2 goes small with 1B and 3B models",
"capabilities": ["tools"],
"parameter_sizes": ["1b", "3b"],
"pulls": "42.6M",
"tags_count": 63,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama3.2"
},
{
"name": "mistral",
"description": "The 7B model released by Mistral AI, updated to version 0.3",
"capabilities": ["tools"],
"parameter_sizes": ["7b"],
"pulls": "21.3M",
"tags_count": 84,
"last_updated": "3 months ago",
"url": "https://ollama.com/library/mistral"
},
{
"name": "qwen2.5",
"description": "Qwen2.5 models are pretrained on Alibaba's latest large-scale dataset, encompassing up to 18 trillion tokens. The model supports up to 128K tokens and has multilingual support",
"capabilities": ["tools"],
"parameter_sizes": ["0.5b", "1.5b", "3b", "7b", "14b", "32b", "72b"],
"pulls": "15.8M",
"tags_count": 133,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/qwen2.5"
},
{
"name": "phi3",
"description": "Phi-3 is a family of lightweight 3B (Mini) and 14B (Medium) state-of-the-art open models by Microsoft",
"capabilities": [],
"parameter_sizes": ["3.8b", "14b"],
"pulls": "12.5M",
"tags_count": 72,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/phi3"
},
{
"name": "llama3",
"description": "Meta Llama 3: The most capable openly available LLM to date",
"capabilities": [],
"parameter_sizes": ["8b", "70b"],
"pulls": "11.5M",
"tags_count": 68,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama3"
},
{
"name": "llava",
"description": "LLaVA is a novel end-to-end trained large multimodal model that combines a vision encoder and Vicuna for general-purpose visual and language understanding. Updated to version 1.6",
"capabilities": ["vision"],
"parameter_sizes": ["7b", "13b", "34b"],
"pulls": "11.1M",
"tags_count": 98,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llava"
},
{
"name": "gemma2",
"description": "Google Gemma 2 is a high-performing and efficient model available in three sizes: 2B, 9B, and 27B",
"capabilities": [],
"parameter_sizes": ["2b", "9b", "27b"],
"pulls": "8.6M",
"tags_count": 94,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/gemma2"
},
{
"name": "qwen2.5-coder",
"description": "The latest series of Code-Specific Qwen models, with significant improvements in code generation, code reasoning, and code fixing",
"capabilities": ["tools"],
"parameter_sizes": ["0.5b", "1.5b", "3b", "7b", "14b", "32b"],
"pulls": "7.9M",
"tags_count": 199,
"last_updated": "5 months ago",
"url": "https://ollama.com/library/qwen2.5-coder"
},
{
"name": "phi4",
"description": "Phi-4 is a 14B parameter, state-of-the-art open model from Microsoft",
"capabilities": [],
"parameter_sizes": ["14b"],
"pulls": "5.8M",
"tags_count": 5,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/phi4"
},
{
"name": "gemma",
"description": "Gemma is a family of lightweight, state-of-the-art open models built by Google DeepMind. Updated to version 1.1",
"capabilities": [],
"parameter_sizes": ["2b", "7b"],
"pulls": "5.4M",
"tags_count": 102,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/gemma"
},
{
"name": "mxbai-embed-large",
"description": "State-of-the-art large embedding model from mixedbread.ai",
"capabilities": ["embedding"],
"parameter_sizes": ["335m"],
"pulls": "5.2M",
"tags_count": 4,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/mxbai-embed-large"
},
{
"name": "qwen",
"description": "Qwen 1.5 is a series of large language models by Alibaba Cloud spanning from 0.5B to 110B parameters",
"capabilities": [],
"parameter_sizes": ["0.5b", "1.8b", "4b", "7b", "14b", "32b", "72b", "110b"],
"pulls": "5M",
"tags_count": 379,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/qwen"
},
{
"name": "qwen2",
"description": "Qwen2 is a new series of large language models from Alibaba group",
"capabilities": ["tools"],
"parameter_sizes": ["0.5b", "1.5b", "7b", "72b"],
"pulls": "4.4M",
"tags_count": 97,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/qwen2"
},
{
"name": "llama2",
"description": "Llama 2 is a collection of foundation language models ranging from 7B to 70B parameters",
"capabilities": [],
"parameter_sizes": ["7b", "13b", "70b"],
"pulls": "4.4M",
"tags_count": 102,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama2"
},
{
"name": "minicpm-v",
"description": "A series of multimodal LLMs (MLLMs) designed for vision-language understanding",
"capabilities": ["vision"],
"parameter_sizes": ["8b"],
"pulls": "3.9M",
"tags_count": 17,
"last_updated": "11 months ago",
"url": "https://ollama.com/library/minicpm-v"
},
{
"name": "dolphin3",
"description": "Dolphin 3.0 Llama 3.1 8B is the next generation of the Dolphin series of instruct-tuned models designed to be the ultimate general purpose local model",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "3.4M",
"tags_count": 5,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/dolphin3"
},
{
"name": "codellama",
"description": "A large language model that can use text prompts to generate and discuss code",
"capabilities": [],
"parameter_sizes": ["7b", "13b", "34b", "70b"],
"pulls": "3.3M",
"tags_count": 199,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/codellama"
},
{
"name": "olmo2",
"description": "OLMo 2 is a new family of 7B and 13B models trained on up to 5T tokens",
"capabilities": [],
"parameter_sizes": ["7b", "13b"],
"pulls": "3.3M",
"tags_count": 9,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/olmo2"
},
{
"name": "tinyllama",
"description": "The TinyLlama project is an open endeavor to train a compact 1.1B Llama model on 3 trillion tokens",
"capabilities": [],
"parameter_sizes": ["1.1b"],
"pulls": "3M",
"tags_count": 36,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/tinyllama"
},
{
"name": "llama3.2-vision",
"description": "Llama 3.2 Vision is a collection of instruction-tuned image reasoning generative models in 11B and 90B sizes",
"capabilities": ["vision"],
"parameter_sizes": ["11b", "90b"],
"pulls": "2.8M",
"tags_count": 9,
"last_updated": "5 months ago",
"url": "https://ollama.com/library/llama3.2-vision"
},
{
"name": "mistral-nemo",
"description": "A state-of-the-art 12B model with 128k context length, built by Mistral AI in collaboration with NVIDIA",
"capabilities": ["tools"],
"parameter_sizes": ["12b"],
"pulls": "2.8M",
"tags_count": 17,
"last_updated": "3 months ago",
"url": "https://ollama.com/library/mistral-nemo"
},
{
"name": "llama3.3",
"description": "New state of the art 70B model. Llama 3.3 70B offers similar performance compared to the Llama 3.1 405B model",
"capabilities": ["tools"],
"parameter_sizes": ["70b"],
"pulls": "2.6M",
"tags_count": 14,
"last_updated": "10 months ago",
"url": "https://ollama.com/library/llama3.3"
},
{
"name": "deepseek-v3",
"description": "A strong Mixture-of-Experts (MoE) language model with 671B total parameters with 37B activated for each token",
"capabilities": [],
"parameter_sizes": ["671b"],
"pulls": "2.6M",
"tags_count": 5,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/deepseek-v3"
},
{
"name": "bge-m3",
"description": "BGE-M3 is a new model from BAAI distinguished for its versatility in Multi-Functionality, Multi-Linguality, and Multi-Granularity",
"capabilities": ["embedding"],
"parameter_sizes": ["567m"],
"pulls": "2.6M",
"tags_count": 3,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/bge-m3"
},
{
"name": "mistral-small",
"description": "Mistral Small 3 sets a new benchmark in the small Large Language Models category below 70B",
"capabilities": ["tools"],
"parameter_sizes": ["22b", "24b"],
"pulls": "2.1M",
"tags_count": 21,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/mistral-small"
},
{
"name": "smollm2",
"description": "SmolLM2 is a family of compact language models available in three size: 135M, 360M, and 1.7B parameters",
"capabilities": ["tools"],
"parameter_sizes": ["135m", "360m", "1.7b"],
"pulls": "2M",
"tags_count": 49,
"last_updated": "12 months ago",
"url": "https://ollama.com/library/smollm2"
},
{
"name": "llava-llama3",
"description": "A LLaVA model fine-tuned from Llama 3 Instruct with better scores in several benchmarks",
"capabilities": ["vision"],
"parameter_sizes": ["8b"],
"pulls": "2M",
"tags_count": 4,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llava-llama3"
},
{
"name": "all-minilm",
"description": "Embedding models on very large sentence level datasets",
"capabilities": ["embedding"],
"parameter_sizes": ["22m", "33m"],
"pulls": "1.9M",
"tags_count": 10,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/all-minilm"
},
{
"name": "qwq",
"description": "QwQ is the reasoning model of the Qwen series, focused on advancing AI reasoning capabilities",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["32b"],
"pulls": "1.7M",
"tags_count": 17,
"last_updated": "7 months ago",
"url": "https://ollama.com/library/qwq"
},
{
"name": "deepseek-coder",
"description": "DeepSeek Coder is a capable coding model trained on two trillion code and natural language tokens",
"capabilities": ["tools"],
"parameter_sizes": ["1.3b", "6.7b", "33b"],
"pulls": "1.6M",
"tags_count": 102,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/deepseek-coder"
},
{
"name": "starcoder2",
"description": "StarCoder2 is the next generation of transparently trained open code LLMs that comes in three sizes: 3B, 7B and 15B parameters",
"capabilities": ["tools"],
"parameter_sizes": ["3b", "7b", "15b"],
"pulls": "1.4M",
"tags_count": 67,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/starcoder2"
},
{
"name": "mixtral",
"description": "A set of Mixture of Experts (MoE) model with open weights by Mistral AI in 8x7b and 8x22b parameter sizes",
"capabilities": ["tools"],
"parameter_sizes": ["8x7b", "8x22b"],
"pulls": "1.4M",
"tags_count": 70,
"last_updated": "10 months ago",
"url": "https://ollama.com/library/mixtral"
},
{
"name": "llama2-uncensored",
"description": "Uncensored Llama 2 model by George Sung and Jarrad Hope, with support for a 16K context window",
"capabilities": [],
"parameter_sizes": ["7b", "70b"],
"pulls": "1.3M",
"tags_count": 34,
"last_updated": "2 years ago",
"url": "https://ollama.com/library/llama2-uncensored"
},
{
"name": "codegemma",
"description": "CodeGemma is a collection of powerful, lightweight models that can perform a variety of coding tasks",
"capabilities": ["tools"],
"parameter_sizes": ["2b", "7b"],
"pulls": "1.3M",
"tags_count": 85,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/codegemma"
},
{
"name": "deepseek-coder-v2",
"description": "An open-source Mixture-of-Experts code language model that achieves performance comparable to GPT4-Turbo in code-specific tasks",
"capabilities": ["tools"],
"parameter_sizes": ["16b", "236b"],
"pulls": "1.2M",
"tags_count": 64,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/deepseek-coder-v2"
},
{
"name": "falcon3",
"description": "A family of efficient AI models under 10B parameters performant in science, math, and coding through innovative training techniques",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["1b", "3b", "7b", "10b"],
"pulls": "1M",
"tags_count": 17,
"last_updated": "10 months ago",
"url": "https://ollama.com/library/falcon3"
},
{
"name": "granite3.1-moe",
"description": "The IBM Granite 1B and 3B models are long-context mixture of experts (MoE) Granite models from IBM designed for low latency usage",
"capabilities": ["tools"],
"parameter_sizes": ["1b", "3b"],
"pulls": "1M",
"tags_count": 33,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/granite3.1-moe"
},
{
"name": "qwen2.5vl",
"description": "Flagship vision-language model of Qwen and also a significant leap from the previous Qwen2-VL",
"capabilities": ["vision"],
"parameter_sizes": ["3b", "7b", "32b", "72b"],
"pulls": "991K",
"tags_count": 17,
"last_updated": "5 months ago",
"url": "https://ollama.com/library/qwen2.5vl"
},
{
"name": "snowflake-arctic-embed",
"description": "A suite of text embedding models by Snowflake, optimized for performance",
"capabilities": ["embedding"],
"parameter_sizes": ["22m", "33m", "110m", "137m", "335m"],
"pulls": "951.1K",
"tags_count": 16,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/snowflake-arctic-embed"
},
{
"name": "orca-mini",
"description": "A general-purpose model ranging from 3 billion parameters to 70 billion, suitable for entry-level hardware",
"capabilities": [],
"parameter_sizes": ["3b", "7b", "13b", "70b"],
"pulls": "930.6K",
"tags_count": 119,
"last_updated": "2 years ago",
"url": "https://ollama.com/library/orca-mini"
},
{
"name": "llama4",
"description": "Meta's latest collection of multimodal models",
"capabilities": ["vision", "tools"],
"parameter_sizes": ["16x17b", "128x17b"],
"pulls": "751.9K",
"tags_count": 11,
"last_updated": "4 months ago",
"url": "https://ollama.com/library/llama4"
},
{
"name": "phi",
"description": "Phi-2: a 2.7B language model by Microsoft Research that demonstrates outstanding reasoning and language understanding capabilities",
"capabilities": ["thinking"],
"parameter_sizes": ["2.7b"],
"pulls": "730.7K",
"tags_count": 18,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/phi"
},
{
"name": "mistral-small3.2",
"description": "An update to Mistral Small that improves on function calling, instruction following, and less repetition errors",
"capabilities": ["vision", "tools"],
"parameter_sizes": ["24b"],
"pulls": "718.3K",
"tags_count": 5,
"last_updated": "4 months ago",
"url": "https://ollama.com/library/mistral-small3.2"
},
{
"name": "dolphin-mixtral",
"description": "Uncensored, 8x7b and 8x22b fine-tuned models based on the Mixtral mixture of experts models that excels at coding tasks. Created by Eric Hartford",
"capabilities": ["tools"],
"parameter_sizes": ["8x7b", "8x22b"],
"pulls": "716.8K",
"tags_count": 70,
"last_updated": "10 months ago",
"url": "https://ollama.com/library/dolphin-mixtral"
},
{
"name": "gemma3n",
"description": "Gemma 3n models are designed for efficient execution on everyday devices such as laptops, tablets or phones",
"capabilities": [],
"parameter_sizes": ["e2b", "e4b"],
"pulls": "670.3K",
"tags_count": 9,
"last_updated": "4 months ago",
"url": "https://ollama.com/library/gemma3n"
},
{
"name": "granite3.3",
"description": "IBM Granite 2B and 8B models are 128K context length language models that have been fine-tuned for improved reasoning and instruction-following capabilities",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["2b", "8b"],
"pulls": "660.7K",
"tags_count": 3,
"last_updated": "6 months ago",
"url": "https://ollama.com/library/granite3.3"
},
{
"name": "cogito",
"description": "Cogito v1 Preview is a family of hybrid reasoning models by Deep Cogito that outperform the best available open models of the same size",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["3b", "8b", "14b", "32b", "70b"],
"pulls": "659K",
"tags_count": 20,
"last_updated": "6 months ago",
"url": "https://ollama.com/library/cogito"
},
{
"name": "phi4-reasoning",
"description": "Phi 4 reasoning and reasoning plus are 14-billion parameter open-weight reasoning models that rival much larger models on complex reasoning tasks",
"capabilities": ["thinking"],
"parameter_sizes": ["14b"],
"pulls": "625.6K",
"tags_count": 9,
"last_updated": "6 months ago",
"url": "https://ollama.com/library/phi4-reasoning"
},
{
"name": "openthinker",
"description": "A fully open-source family of reasoning models built using a dataset derived by distilling DeepSeek-R1",
"capabilities": ["thinking"],
"parameter_sizes": ["7b", "32b"],
"pulls": "604.4K",
"tags_count": 15,
"last_updated": "6 months ago",
"url": "https://ollama.com/library/openthinker"
},
{
"name": "magistral",
"description": "Magistral is a small, efficient reasoning model with 24B parameters, excelling in domain-specific, transparent, and multilingual reasoning",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["24b"],
"pulls": "583.4K",
"tags_count": 5,
"last_updated": "4 months ago",
"url": "https://ollama.com/library/magistral"
},
{
"name": "deepscaler",
"description": "A fine-tuned version of Deepseek-R1-Distilled-Qwen-1.5B that surpasses the performance of OpenAI's o1-preview with just 1.5B parameters",
"capabilities": ["thinking"],
"parameter_sizes": ["1.5b"],
"pulls": "573.2K",
"tags_count": 5,
"last_updated": "8 months ago",
"url": "https://ollama.com/library/deepscaler"
},
{
"name": "codestral",
"description": "Codestral is Mistral AI's first-ever code model designed for code generation tasks",
"capabilities": ["tools"],
"parameter_sizes": ["22b"],
"pulls": "503.9K",
"tags_count": 17,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/codestral"
},
{
"name": "dolphin-phi",
"description": "2.7B uncensored Dolphin model by Eric Hartford, based on the Phi language model by Microsoft Research",
"capabilities": [],
"parameter_sizes": ["2.7b"],
"pulls": "569.2K",
"tags_count": 15,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/dolphin-phi"
},
{
"name": "dolphin-mistral",
"description": "The uncensored Dolphin model based on Mistral that excels at coding tasks. Updated to version 2.8",
"capabilities": ["tools"],
"parameter_sizes": ["7b"],
"pulls": "409.3K",
"tags_count": 120,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/dolphin-mistral"
},
{
"name": "wizard-vicuna-uncensored",
"description": "Wizard Vicuna Uncensored is a 7B, 13B, and 30B parameter model based on Llama 2 uncensored by Eric Hartford",
"capabilities": [],
"parameter_sizes": ["7b", "13b", "30b"],
"pulls": "255.1K",
"tags_count": 49,
"last_updated": "2 years ago",
"url": "https://ollama.com/library/wizard-vicuna-uncensored"
},
{
"name": "dolphin-starcoder2",
"description": "A 7B and 15B uncensored variant of the Dolphin model family that excels at coding, based on StarCoder2",
"capabilities": ["tools"],
"parameter_sizes": ["7b", "15b"],
"pulls": "113.1K",
"tags_count": 35,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/dolphin-starcoder2"
},
{
"name": "wizardlm-uncensored",
"description": "Uncensored version of Wizard LM model",
"capabilities": [],
"parameter_sizes": ["7b", "13b"],
"pulls": "89.5K",
"tags_count": 18,
"last_updated": "2 years ago",
"url": "https://ollama.com/library/wizardlm-uncensored"
},
{
"name": "llama-3.2-3b-instruct-abliterated",
"description": "An uncensored version of the original Llama-3.2-3B-Instruct, created via abliteration",
"capabilities": [],
"parameter_sizes": ["3b"],
"pulls": "508.1K",
"tags_count": 1,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama-3.2-3b-instruct-abliterated"
},
{
"name": "josiefied-qwen3-uncensored",
"description": "Qwen3, but Josiefied and uncensored",
"capabilities": [],
"parameter_sizes": ["7b"],
"pulls": "71.5K",
"tags_count": 47,
"last_updated": "4 months ago",
"url": "https://ollama.com/library/josiefied-qwen3-uncensored"
},
{
"name": "abliterated-llama3.1-8b",
"description": "Ablitered v3 llama-3.1 8b with uncensored prompt",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "31K",
"tags_count": 38,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/abliterated-llama3.1-8b"
},
{
"name": "dolphin-uncensored",
"description": "Dolphin is an uncensored multilingual chat tuned model by Eric Hartford, conformant to system prompt, good at coding and various tasks",
"capabilities": ["tools"],
"parameter_sizes": ["8b"],
"pulls": "25.5K",
"tags_count": 15,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/dolphin-uncensored"
},
{
"name": "specialized-uncensored-openai-20b",
"description": "Specialized uncensored/abliterated quants for new OpenAI 20B MOE - Mixture of Experts Model at 80+ T/S (quantized Q5_1)",
"capabilities": ["tools"],
"parameter_sizes": ["20b"],
"pulls": "22.9K",
"tags_count": 1,
"last_updated": "1 month ago",
"url": "https://ollama.com/library/specialized-uncensored-openai-20b"
},
{
"name": "lexi-uncensored",
"description": "Lexi is uncensored, which makes the model compliant. You are advised to implement your own alignment layer before exposing the model as a service",
"capabilities": [],
"parameter_sizes": ["7b"],
"pulls": "15.3K",
"tags_count": 9,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/lexi-uncensored"
},
{
"name": "llama3-uncensored",
"description": "Uncensored version of Llama3 8b",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "13.6K",
"tags_count": 1,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama3-uncensored"
},
{
"name": "llama3-uncensored-long",
"description": "This model was created with the goal for a good llama 3 uncencored model with long context",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "13.5K",
"tags_count": 4,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/llama3-uncensored-long"
},
{
"name": "deepseek-r1-distilled",
"description": "DeepSeek's first generation reasoning models with comparable performance to OpenAI-o1",
"capabilities": ["thinking"],
"parameter_sizes": ["7b"],
"pulls": "586.4K",
"tags_count": 55,
"last_updated": "5 months ago",
"url": "https://ollama.com/library/deepseek-r1-distilled"
},
{
"name": "llama-3.2-11b-vision-abliterated",
"description": "From huihui-ai/Llama-3.2-11B-Vision-Instruct-abliterated",
"capabilities": ["vision"],
"parameter_sizes": ["11b"],
"pulls": "69.8K",
"tags_count": 2,
"last_updated": "8 months ago",
"url": "https://ollama.com/library/llama-3.2-11b-vision-abliterated"
},
{
"name": "qwen2.5-1m",
"description": "Qwen2.5-1M is the long-context version of the Qwen2.5 series models, supporting a context length of up to 1M tokens",
"capabilities": [],
"parameter_sizes": ["7b"],
"pulls": "31K",
"tags_count": 11,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/qwen2.5-1m"
},
{
"name": "qwen3-abliterated",
"description": "This is the abliterated version of Qwen3, which is the latest generation of large language models in Qwen series",
"capabilities": [],
"parameter_sizes": ["7b"],
"pulls": "23.9K",
"tags_count": 6,
"last_updated": "5 months ago",
"url": "https://ollama.com/library/qwen3-abliterated"
},
{
"name": "dolphin-3.0-llama3.1-8b-abliterated",
"description": "Dolphin 3.0 Llama 3.1 8B is the next generation of the Dolphin series of instruct-tuned models designed to be the ultimate general purpose local model",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "21.7K",
"tags_count": 5,
"last_updated": "9 months ago",
"url": "https://ollama.com/library/dolphin-3.0-llama3.1-8b-abliterated"
},
{
"name": "gemma-3n-abliterated",
"description": "This is an uncensored version of google/gemma-3n created with abliteration",
"capabilities": [],
"parameter_sizes": ["9b"],
"pulls": "13.4K",
"tags_count": 2,
"last_updated": "3 months ago",
"url": "https://ollama.com/library/gemma-3n-abliterated"
},
{
"name": "neuraldaredevil-8b-abliterated",
"description": "Contains fp16 version of the excellent NeuralDaredevil-8B-abliterated",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "9,677",
"tags_count": 1,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/neuraldaredevil-8b-abliterated"
},
{
"name": "mlabonne-neuraldaredevil-8b-abliterated",
"description": "mlabonne/NeuralDaredevil-8B-abliterated",
"capabilities": [],
"parameter_sizes": ["8b"],
"pulls": "7,679",
"tags_count": 4,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/mlabonne-neuraldaredevil-8b-abliterated"
},
{
"name": "phi3-medium-abliterated",
"description": "This is a phi3 medium model that has had the prompt rejection neurons snipped which is better than fine tuning",
"capabilities": [],
"parameter_sizes": ["14b"],
"pulls": "6,591",
"tags_count": 1,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/phi3-medium-abliterated"
},
{
"name": "gemma2-9b-abliterated",
"description": "Abliterated version of Google's Gemma 2 9B",
"capabilities": [],
"parameter_sizes": ["9b"],
"pulls": "6,386",
"tags_count": 1,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/gemma2-9b-abliterated"
},
{
"name": "codeqwen1.5",
"description": "CodeQwen1.5 is a large language model pretrained on a large amount of code data",
"capabilities": ["tools"],
"parameter_sizes": ["7b"],
"pulls": "182K",
"tags_count": 30,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/codeqwen1.5"
},
{
"name": "deepcoder",
"description": "DeepCoder is a fully open-Source 14B coder model at O3-mini level, with a 1.5B version also available",
"capabilities": ["tools"],
"parameter_sizes": ["1.5b", "14b"],
"pulls": "352.4K",
"tags_count": 9,
"last_updated": "6 months ago",
"url": "https://ollama.com/library/deepcoder"
},
{
"name": "granite-code",
"description": "A family of open foundation models by IBM for Code Intelligence",
"capabilities": ["tools"],
"parameter_sizes": ["8b", "20b", "34b"],
"pulls": "295.1K",
"tags_count": 162,
"last_updated": "1 year ago",
"url": "https://ollama.com/library/granite-code"
},
{
"name": "mistral-large-2",
"description": "Mistral Large 2 is Mistral's new flagship model that is significantly more capable in code generation, mathematics, and reasoning",
"capabilities": ["tools", "thinking"],
"parameter_sizes": ["123b"],
"pulls": "265.9K",
"tags_count": 32,
"last_updated": "11 months ago",
"url": "https://ollama.com/library/mistral-large-2"
},
{
"name": "starcoder",
"description": "StarCoder is a code generation model trained on 80+ programming languages",
"capabilities": ["tools"],
"parameter_sizes": ["15b"],
"pulls": "228.8K",
"tags_count": 100,
"last_updated": "2 years ago",
"url": "https://ollama.com/library/starcoder"
},
{
"name": "opencoder",
"description": "OpenCoder is an open and reproducible code LLM family which includes 1.5B and 8B models, supporting chat in English and Chinese languages",
"capabilities": ["tools"],
"parameter_sizes": ["1.5b", "8b"],
"pulls": "194.3K",
"tags_count": 9,
"last_updated": "11 months ago",
"url": "https://ollama.com/library/opencoder"
},
{
"name": "llama-3.2-1b-abliterated",
"description": "Abliterated version for small reasoning tasks from Llama 3.2 1B",
"capabilities": ["thinking"],
"parameter_sizes": ["1b"],
"pulls": "39.9K",
"tags_count": 16,
"last_updated": "2 months ago",
"url": "https://ollama.com/library/llama-3.2-1b-abliterated"
}
],
"api_information": {
"local_api_endpoints": [
{
"endpoint": "/api/tags",
"method": "GET",
"description": "List all locally installed models"
}
],
"limitations": {
"remote_listing": "Não existe endpoint público para listar todos os modelos disponíveis no registry remoto do Ollama",
"user_models": "Modelos de usuários (uncensored/abliterated) são variantes comunitárias; busque via /search",
"reasoning_focus": "Modelos com 'thinking' são otimizados para raciocínio multi-step (ex: CoT, logical puzzles)"
}
},
"usage_instructions": {
"list_local_models": "curl http://localhost:11434/api/tags",
"pull_model": "ollama pull <model_name> (ex: ollama pull deepseek-r1 para reasoning)",
"test_uncensored": "ollama run llama2-uncensored --prompt 'Gere código Python sem restrições'",
"test_reasoning": "ollama run phi4-reasoning --prompt 'Resolva: 2+2=?'",
"browse_searches": "https://ollama.com/search?q=<termo> (ex: uncensored, code)"
},
"recommendations_for_devs": {
"coding": "Use qwen2.5-coder ou codestral para geração de código avançada [web:3]",
"reasoning": "deepseek-r1 ou phi4-reasoning para tarefas lógicas/math [web:77]",
"uncensored": "llama2-uncensored para experimentos sem alinhamento ético, mas implemente safeguards [attached_file:1]"
}
}
+96
View File
@@ -0,0 +1,96 @@
use actix_web::{get, post, web, HttpResponse, Responder};
use serde::Deserialize;
use crate::core::models::{pull_model, read_model_list};
use crate::crypto::CryptoState;
use crate::state::AppContext;
use crate::utils::log;
use crate::ws::{ws_route, BrokerAddr, WsEvent, WsEventKind};
use base64::{engine::general_purpose, Engine as _};
#[get("/health")]
pub async fn health() -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({"status":"ok"}))
}
#[get("/api/models")]
pub async fn models_list(state: web::Data<AppContext>) -> impl Responder {
match read_model_list(&state.model_list_path) {
Ok(list) => HttpResponse::Ok().json(list),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
}
}
#[derive(Deserialize)]
pub struct InstallReq {
pub name: String,
}
#[post("/api/models/install")]
pub async fn models_install(state: web::Data<AppContext>, payload: web::Json<InstallReq>) -> impl Responder {
let name = payload.name.clone();
let tx = state.broker.clone();
let base = state.ollama_base_url.clone();
let value = name.clone();
actix_rt::spawn(async move {
let _ = pull_model(&base, &value, &tx).await;
});
HttpResponse::Accepted().json(serde_json::json!({"status":"started","model":name}))
}
#[derive(Deserialize)]
pub struct SetKeyReq { pub hex_key: String }
#[post("/api/crypto/set-key")]
pub async fn crypto_set_key(state: web::Data<AppContext>, payload: web::Json<SetKeyReq>) -> impl Responder {
match state.crypto.set_key_hex(&payload.hex_key) {
Ok(()) => HttpResponse::Ok().json(serde_json::json!({"status":"ok"})),
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
}
}
#[post("/api/crypto/rotate-key")]
pub async fn crypto_rotate_key(state: web::Data<AppContext>) -> impl Responder {
match state.crypto.rotate_key() {
Ok(hex) => HttpResponse::Ok().json(serde_json::json!({"key": hex})),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
}
}
#[derive(Deserialize)]
pub struct CryptReq { pub data: String }
#[post("/api/crypto/encrypt")]
pub async fn crypto_encrypt(state: web::Data<AppContext>, payload: web::Json<CryptReq>) -> impl Responder {
match state.crypto.encrypt(payload.data.as_bytes()) {
Ok(ct) => HttpResponse::Ok().json(serde_json::json!({"ciphertext": general_purpose::STANDARD.encode(ct)})),
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
}
}
#[post("/api/crypto/decrypt")]
pub async fn crypto_decrypt(state: web::Data<AppContext>, payload: web::Json<CryptReq>) -> impl Responder {
match general_purpose::STANDARD.decode(&payload.data) {
Ok(bytes) => match state.crypto.decrypt(&bytes) {
Ok(pt) => HttpResponse::Ok().json(serde_json::json!({"plaintext": String::from_utf8_lossy(&pt)})),
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
},
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": format!("base64 decode: {}", e)})),
}
}
pub fn configure(cfg: &mut web::ServiceConfig, state: web::Data<AppContext>) {
cfg.app_data(state);
cfg.service(health);
cfg.service(models_list);
cfg.service(models_install);
cfg.service(crypto_set_key);
cfg.service(crypto_rotate_key);
cfg.service(crypto_encrypt);
cfg.service(crypto_decrypt);
}
pub fn ws_config(cfg: &mut web::ServiceConfig, broker: actix_web::web::Data<BrokerAddr>) {
cfg.app_data(broker);
cfg.service(ws_route);
}
+1
View File
@@ -0,0 +1 @@
pub mod user_controller;
+92
View File
@@ -0,0 +1,92 @@
use actix_web::{get, post, put, delete, web, HttpResponse, Responder};
use utoipa::{IntoParams, ToSchema};
use serde::Deserialize;
use validator::Validate;
use crate::middlewares::error::ApiError;
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
use crate::services::user_service::UserService;
#[derive(Deserialize, IntoParams, ToSchema)]
pub struct UserPath { pub id: String }
#[utoipa::path(
get,
path = "/api/v1/users",
responses(
(status = 200, description = "Lista de usuários", body = [User])
)
)]
#[get("/api/v1/users")]
pub async fn list_users(svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> impl Responder {
HttpResponse::Ok().json(svc.list())
}
#[utoipa::path(
get,
path = "/api/v1/users/{id}",
params(UserPath),
responses(
(status = 200, description = "Usuário encontrado", body = User),
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
)
)]
#[get("/api/v1/users/{id}")]
pub async fn get_user(path: web::Path<UserPath>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
match svc.get(path.id.clone()) {
Some(u) => Ok(HttpResponse::Ok().json(u)),
None => Err(ApiError::NotFound("Usuário não encontrado".into())),
}
}
#[utoipa::path(
post,
path = "/api/v1/users",
request_body = CreateUserRequest,
responses(
(status = 201, description = "Usuário criado", body = User),
(status = 400, description = "Erro de validação", body = crate::middlewares::error::ApiErrorBody)
)
)]
#[post("/api/v1/users")]
pub async fn create_user(payload: web::Json<CreateUserRequest>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
let mut req = payload.into_inner();
if let Err(e) = req.validate() { return Err(ApiError::BadRequest(format!("validação: {}", e))); }
let user = svc.create(req);
Ok(HttpResponse::Created().json(user))
}
#[utoipa::path(
put,
path = "/api/v1/users/{id}",
params(UserPath),
request_body = UpdateUserRequest,
responses(
(status = 200, description = "Usuário atualizado", body = User),
(status = 400, description = "Erro de validação", body = crate::middlewares::error::ApiErrorBody),
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
)
)]
#[put("/api/v1/users/{id}")]
pub async fn update_user(path: web::Path<UserPath>, payload: web::Json<UpdateUserRequest>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
let mut req = payload.into_inner();
if let Err(e) = req.validate() { return Err(ApiError::BadRequest(format!("validação: {}", e))); }
match svc.update(path.id.clone(), req) {
Some(u) => Ok(HttpResponse::Ok().json(u)),
None => Err(ApiError::NotFound("Usuário não encontrado".into())),
}
}
#[utoipa::path(
delete,
path = "/api/v1/users/{id}",
params(UserPath),
responses(
(status = 204, description = "Usuário deletado"),
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
)
)]
#[delete("/api/v1/users/{id}")]
pub async fn delete_user(path: web::Path<UserPath>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
if svc.delete(path.id.clone()) { Ok(HttpResponse::NoContent().finish()) } else { Err(ApiError::NotFound("Usuário não encontrado".into())) }
}
+1
View File
@@ -0,0 +1 @@
pub mod models;
+61
View File
@@ -0,0 +1,61 @@
use std::fs;
use std::path::Path;
use actix_web::http::header::USER_AGENT;
use awc::Client;
use serde::{Deserialize, Serialize};
use crate::utils::log;
use crate::ws::{WsEvent, WsEventKind};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelInfo {
pub name: String,
pub description: Option<String>,
}
pub fn read_model_list(path: &str) -> Result<Vec<ModelInfo>, String> {
let data = fs::read_to_string(path).map_err(|e| e.to_string())?;
let list: Vec<ModelInfo> = serde_json::from_str(&data).map_err(|e| e.to_string())?;
Ok(list)
}
pub async fn pull_model(
base_url: &str,
model: &str,
tx: &tokio::sync::broadcast::Sender<WsEvent>,
) -> Result<(), String> {
let client = Client::default();
let url = format!("{}/api/pull", base_url.trim_end_matches('/'));
let body = serde_json::json!({"name": model});
let mut resp = client
.post(url)
.insert_header((USER_AGENT, "OllaHubBackend/1.0"))
.send_json(&body)
.await
.map_err(|e| e.to_string())?;
let status = resp.status();
let bytes = resp
.body()
.await
.map_err(|e| format!("Erro corpo resposta: {}", e))?;
if !status.is_success() {
return Err(format!("Falha ao puxar modelo: {}", status));
}
let text = String::from_utf8_lossy(&bytes).to_string();
for line in text.lines() {
tx.send(WsEvent {
kind: WsEventKind::Log,
message: line.to_string(),
})
.ok();
}
tx.send(WsEvent {
kind: WsEventKind::Done,
message: format!("Download do modelo '{}' concluído", model),
})
.ok();
log::info(&format!("Modelo '{}' baixado com sucesso", model));
Ok(())
}
+116
View File
@@ -0,0 +1,116 @@
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use rand::RngCore;
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::utils::log;
#[derive(Clone)]
pub struct CryptoState {
key: Arc<Mutex<Option<[u8; 32]>>>,
key_path: PathBuf,
}
impl CryptoState {
pub fn new(key_path: PathBuf) -> Self {
let state = Self {
key: Arc::new(Mutex::new(None)),
key_path,
};
state.load_key_from_disk();
state
}
fn load_key_from_disk(&self) {
if self.key_path.exists() {
if let Ok(hex) = fs::read_to_string(&self.key_path) {
let hex = hex.trim();
match hex::decode(hex) {
Ok(bytes) if bytes.len() == 32 => {
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
*self.key.lock().unwrap() = Some(arr);
log::info("Chave carregada do disco");
}
_ => log::warn("Arquivo de chave inválido; ignorando"),
}
}
}
}
pub fn set_key_hex(&self, hex_key: &str) -> Result<(), String> {
let bytes = hex::decode(hex_key).map_err(|e| format!("hex inválido: {}", e))?;
if bytes.len() != 32 {
return Err("Chave deve ter 32 bytes".into());
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
*self.key.lock().unwrap() = Some(arr);
fs::create_dir_all(self.key_path.parent().unwrap_or_else(|| std::path::Path::new(".")))
.map_err(|e| e.to_string())?;
fs::write(&self.key_path, hex::encode(arr)).map_err(|e| e.to_string())?;
Ok(())
}
pub fn rotate_key(&self) -> Result<String, String> {
let mut new_key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut new_key);
*self.key.lock().unwrap() = Some(new_key);
fs::create_dir_all(self.key_path.parent().unwrap_or_else(|| std::path::Path::new(".")))
.map_err(|e| e.to_string())?;
let hex = hex::encode(new_key);
fs::write(&self.key_path, &hex).map_err(|e| e.to_string())?;
Ok(hex)
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, String> {
let key = self.key.lock().unwrap();
let key = key.ok_or("Chave não definida")?;
let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?;
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let mut out = Vec::from(nonce_bytes);
let ct = cipher
.encrypt(nonce, plaintext)
.map_err(|e| format!("Erro ao criptografar: {}", e))?;
out.extend_from_slice(&ct);
Ok(out)
}
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, String> {
if ciphertext.len() < 12 {
return Err("Ciphertext inválido".into());
}
let (nonce_bytes, ct) = ciphertext.split_at(12);
let key = self.key.lock().unwrap();
let key = key.ok_or("Chave não definida")?;
let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?;
let nonce = Nonce::from_slice(nonce_bytes);
let pt = cipher
.decrypt(nonce, ct)
.map_err(|e| format!("Erro ao descriptografar: {}", e))?;
Ok(pt)
}
}
#[cfg(test)]
mod tests {
use super::CryptoState;
use std::env;
use std::path::PathBuf;
#[test]
fn crypto_roundtrip() {
let tmp = env::temp_dir().join("crypto_key_test.hex");
let crypto = CryptoState::new(PathBuf::from(&tmp));
crypto.rotate_key().unwrap();
let msg = b"hello";
let ct = crypto.encrypt(msg).unwrap();
let pt = crypto.decrypt(&ct).unwrap();
assert_eq!(msg.to_vec(), pt);
let _ = std::fs::remove_file(tmp);
}
}
+1289
View File
File diff suppressed because it is too large. Load diff
+38
View File
@@ -0,0 +1,38 @@
use actix_web::{dev::{ServiceRequest, ServiceResponse}, Error};
use actix_web::{http::header, HttpResponse};
use actix_web::middleware::Next;
use actix_web::body::MessageBody;
use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::Deserialize;
use crate::state::AppContext;
#[derive(Debug, Deserialize)]
struct Claims { sub: String, exp: usize, role: Option<String> }
pub async fn jwt_auth<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse, Error>
where
B: MessageBody + 'static,
{
// Apenas protege rotas da API v1; demais passam direto
if !req.path().starts_with("/api/v1/") {
let res = next.call(req).await?;
return Ok(res.map_into_boxed_body());
}
let ctx = req.app_data::<actix_web::web::Data<AppContext>>().cloned();
let Some(state) = ctx else { return Ok(req.into_response(HttpResponse::InternalServerError().finish())); };
let Some(hdr) = req.headers().get(header::AUTHORIZATION) else {
return Ok(req.into_response(HttpResponse::Unauthorized().finish()));
};
let token = hdr.to_str().unwrap_or("");
let token = token.strip_prefix("Bearer ").unwrap_or(token);
let secret = &state.jwt_secret;
let res = decode::<Claims>(token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::default());
match res {
Ok(_data) => {
let res = next.call(req).await?;
Ok(res.map_into_boxed_body())
}
Err(_) => Ok(req.into_response(HttpResponse::Unauthorized().finish())),
}
}
+37
View File
@@ -0,0 +1,37 @@
use actix_web::{HttpResponse, ResponseError};
use serde::Serialize;
use utoipa::ToSchema;
use std::fmt;
#[derive(Debug, Serialize, ToSchema)]
pub struct ApiErrorBody { pub error: String }
#[derive(Debug)]
pub enum ApiError {
BadRequest(String),
Unauthorized(String),
NotFound(String),
Internal(String),
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
match self {
ApiError::BadRequest(msg) => HttpResponse::BadRequest().json(ApiErrorBody { error: msg.clone() }),
ApiError::Unauthorized(msg) => HttpResponse::Unauthorized().json(ApiErrorBody { error: msg.clone() }),
ApiError::NotFound(msg) => HttpResponse::NotFound().json(ApiErrorBody { error: msg.clone() }),
ApiError::Internal(msg) => HttpResponse::InternalServerError().json(ApiErrorBody { error: msg.clone() }),
}
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiError::BadRequest(msg) => write!(f, "BadRequest: {}", msg),
ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
ApiError::NotFound(msg) => write!(f, "NotFound: {}", msg),
ApiError::Internal(msg) => write!(f, "Internal: {}", msg),
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod auth;
pub mod error;
+1
View File
@@ -0,0 +1 @@
pub mod user;
+33
View File
@@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct User {
pub id: String,
pub name: String,
pub email: String,
}
impl User {
pub fn from_create(req: CreateUserRequest) -> Self {
Self { id: Uuid::new_v4().to_string(), name: req.name, email: req.email }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
pub struct CreateUserRequest {
#[validate(length(min = 2))]
pub name: String,
#[validate(email)]
pub email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
pub struct UpdateUserRequest {
#[validate(length(min = 2))]
pub name: Option<String>,
#[validate(email)]
pub email: Option<String>,
}
+1
View File
@@ -0,0 +1 @@
pub mod user_repository;
+53
View File
@@ -0,0 +1,53 @@
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
pub trait UserRepository: Send + Sync {
fn list(&self) -> Vec<User>;
fn get(&self, id: String) -> Option<User>;
fn create(&self, req: CreateUserRequest) -> User;
fn update(&self, id: String, req: UpdateUserRequest) -> Option<User>;
fn delete(&self, id: String) -> bool;
}
#[derive(Clone)]
pub struct InMemoryUserRepository {
inner: Arc<RwLock<HashMap<String, User>>>,
}
impl InMemoryUserRepository {
pub fn new() -> Self {
Self { inner: Arc::new(RwLock::new(HashMap::new())) }
}
}
impl UserRepository for InMemoryUserRepository {
fn list(&self) -> Vec<User> {
self.inner.read().unwrap().values().cloned().collect()
}
fn get(&self, id: String) -> Option<User> {
self.inner.read().unwrap().get(&id).cloned()
}
fn create(&self, req: CreateUserRequest) -> User {
let user = User::from_create(req);
self.inner.write().unwrap().insert(user.id.clone(), user.clone());
user
}
fn update(&self, id: String, req: UpdateUserRequest) -> Option<User> {
let mut map = self.inner.write().unwrap();
if let Some(existing) = map.get_mut(&id) {
if let Some(name) = req.name { existing.name = name; }
if let Some(email) = req.email { existing.email = email; }
return Some(existing.clone());
}
None
}
fn delete(&self, id: String) -> bool {
self.inner.write().unwrap().remove(&id).is_some()
}
}
+44
View File
@@ -0,0 +1,44 @@
use actix_web::web;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::controllers::user_controller::{list_users, get_user, create_user, update_user, delete_user};
use crate::repositories::user_repository::InMemoryUserRepository;
use crate::services::user_service::UserService;
use crate::state::AppContext;
#[derive(OpenApi)]
#[openapi(
paths(
crate::controllers::user_controller::list_users,
crate::controllers::user_controller::get_user,
crate::controllers::user_controller::create_user,
crate::controllers::user_controller::update_user,
crate::controllers::user_controller::delete_user,
),
components(schemas(
crate::models::user::User,
crate::models::user::CreateUserRequest,
crate::models::user::UpdateUserRequest,
crate::middlewares::error::ApiErrorBody,
)),
tags(
(name = "users", description = "Gerenciamento de usuários"),
)
)]
pub struct ApiDoc;
pub fn configure_v1(cfg: &mut web::ServiceConfig, _state: web::Data<AppContext>) {
let repo = InMemoryUserRepository::new();
let svc = UserService::new(repo);
cfg.app_data(web::Data::new(svc));
cfg.service(list_users);
cfg.service(get_user);
cfg.service(create_user);
cfg.service(update_user);
cfg.service(delete_user);
}
pub fn swagger_ui() -> SwaggerUi {
SwaggerUi::new("/docs").url("/api-doc/openapi.json", ApiDoc::openapi())
}
+3
View File
@@ -0,0 +1,3 @@
pub mod system;
pub mod ollama;
pub mod user_service;
+18
View File
@@ -0,0 +1,18 @@
use actix_web::http::header::USER_AGENT;
use awc::Client;
pub async fn check_api(base_url: &str) -> Result<(), String> {
let client = Client::default();
let url = format!("{}/api/tags", base_url.trim_end_matches('/'));
let resp = client
.get(url)
.insert_header((USER_AGENT, "OllaHubBackend/1.0"))
.send()
.await
.map_err(|e| e.to_string())?;
if resp.status().is_success() {
Ok(())
} else {
Err(format!("API ollama indisponível: {}", resp.status()))
}
}
+27
View File
@@ -0,0 +1,27 @@
use std::process::Command;
#[derive(Debug, Clone, Copy)]
pub enum Platform {
Windows,
Linux,
MacOS,
}
pub fn detect_platform() -> Platform {
if cfg!(target_os = "windows") {
Platform::Windows
} else if cfg!(target_os = "macos") {
Platform::MacOS
} else {
Platform::Linux
}
}
pub fn run_cmd(cmd: &str, args: &[&str]) -> Result<String, String> {
let output = Command::new(cmd).args(args).output().map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
+17
View File
@@ -0,0 +1,17 @@
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
use crate::repositories::user_repository::UserRepository;
#[derive(Clone)]
pub struct UserService<R: UserRepository> {
repo: R,
}
impl<R: UserRepository> UserService<R> {
pub fn new(repo: R) -> Self { Self { repo } }
pub fn list(&self) -> Vec<User> { self.repo.list() }
pub fn get(&self, id: String) -> Option<User> { self.repo.get(id) }
pub fn create(&self, req: CreateUserRequest) -> User { self.repo.create(req) }
pub fn update(&self, id: String, req: UpdateUserRequest) -> Option<User> { self.repo.update(id, req) }
pub fn delete(&self, id: String) -> bool { self.repo.delete(id) }
}
+31
View File
@@ -0,0 +1,31 @@
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::broadcast;
use crate::crypto::CryptoState;
use crate::ws::WsEvent;
#[derive(Clone)]
pub struct AppContext {
pub broker: broadcast::Sender<WsEvent>,
pub crypto: Arc<CryptoState>,
pub ollama_base_url: String,
pub model_list_path: String,
pub jwt_secret: String,
}
impl AppContext {
pub fn new(ollama_base_url: String, model_list_path: String, key_path: PathBuf) -> Self {
let broker = crate::ws::new_broker();
let crypto = Arc::new(CryptoState::new(key_path));
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "changeme-dev-secret".into());
Self {
broker,
crypto,
ollama_base_url,
model_list_path,
jwt_secret,
}
}
}
+20
View File
@@ -0,0 +1,20 @@
use std::time::{SystemTime, UNIX_EPOCH};
pub fn now_ts() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
}
pub fn info(message: &str) {
println!("[INFO] [{}] {}", now_ts(), message);
}
pub fn warn(message: &str) {
eprintln!("[WARN] [{}] {}", now_ts(), message);
}
pub fn error(message: &str) {
eprintln!("[ERROR] [{}] {}", now_ts(), message);
}
+1
View File
@@ -0,0 +1 @@
pub mod log;
+100
View File
@@ -0,0 +1,100 @@
use actix::{Actor, Context, Handler, ActorContext, Message};
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
use actix_web_actors::ws;
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use crate::utils::log;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WsEventKind {
Log,
Progress,
Done,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsEvent {
pub kind: WsEventKind,
pub message: String,
}
pub fn new_broker() -> broadcast::Sender<WsEvent> {
let (tx, _rx) = broadcast::channel(64);
tx
}
#[derive(Clone)]
pub struct BrokerAddr(pub broadcast::Sender<WsEvent>);
pub struct WsSession {
pub broker: broadcast::Sender<WsEvent>,
}
impl Actor for WsSession {
type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
let mut rx = self.broker.subscribe();
// Forward broker messages to the websocket client
actix_rt::spawn(async move {
while let Ok(evt) = rx.recv().await {
// We cannot access ctx here; use an internal channel.
// Instead, send events via a helper actor message.
// This is handled below using a Trampoline handler.
// For simplicity, we log; WS pushes are driven by incoming pings.
log::info(&format!("WS evt: {:?}: {}", evt.kind, evt.message));
}
});
}
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct PushEvent(pub WsEvent);
impl Handler<PushEvent> for WsSession {
type Result = ();
fn handle(&mut self, msg: PushEvent, ctx: &mut Self::Context) -> Self::Result {
let payload = serde_json::to_string(&msg.0).unwrap_or_else(|_| "{\"kind\":\"Error\",\"message\":\"serialize\"}".into());
ctx.text(payload);
}
}
impl actix::StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsSession {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => {
ctx.pong(&msg);
}
Ok(ws::Message::Text(text)) => {
// Echo and also subscribe client to stream by pushing recent event
ctx.text(text);
}
Ok(ws::Message::Binary(bin)) => {
ctx.binary(bin);
}
Ok(ws::Message::Close(reason)) => {
log::info(&format!("WS closed: {:?}", reason));
ctx.close(reason);
ctx.stop();
}
_ => {}
}
}
}
#[get("/ws")]
pub async fn ws_route(
req: HttpRequest,
stream: web::Payload,
broker: web::Data<BrokerAddr>,
) -> Result<HttpResponse, Error> {
let session = WsSession {
broker: broker.get_ref().0.clone(),
};
let resp = ws::start(session, &req, stream);
resp
}