Implementa sistema de IDs curtos para pastes, substituindo hashes SHA-256. Atualiza a estrutura do banco de dados e a lógica de inserção para suportar a detecção de duplicatas. O README e a documentação de migração foram atualizados para refletir essas mudanças.
This commit is contained in:
64
MIGRATION.md
64
MIGRATION.md
@@ -1,24 +1,39 @@
|
||||
# Migração para SQLite3
|
||||
# Migração para SQLite3 com IDs Curtos
|
||||
|
||||
Este documento descreve como migrar o YASUC de BoltDB para SQLite3.
|
||||
Este documento descreve como migrar o YASUC de BoltDB para SQLite3 com o novo sistema de IDs curtos.
|
||||
|
||||
## Por que migrar para SQLite3?
|
||||
## Por que migrar para SQLite3 com IDs Curtos?
|
||||
|
||||
- **URLs mais amigáveis**: IDs de 7 caracteres em vez de hashes SHA-256 de 64 caracteres
|
||||
- **Melhor compatibilidade**: SQLite3 é mais amplamente suportado
|
||||
- **Ferramentas de administração**: Mais ferramentas disponíveis para gerenciar dados
|
||||
- **Consultas SQL**: Possibilidade de fazer consultas complexas
|
||||
- **Backup mais simples**: Arquivo único mais fácil de fazer backup
|
||||
- **Detecção de duplicatas**: Pastes idênticos recebem o mesmo ID curto
|
||||
|
||||
## Estrutura do Banco SQLite3
|
||||
|
||||
```sql
|
||||
CREATE TABLE pastes (
|
||||
hash TEXT PRIMARY KEY, -- Hash SHA-256 do conteúdo
|
||||
content TEXT NOT NULL, -- Conteúdo do paste
|
||||
created_at INTEGER NOT NULL -- Timestamp de criação
|
||||
short_id TEXT PRIMARY KEY, -- ID curto único (7 caracteres)
|
||||
content TEXT NOT NULL, -- Conteúdo do paste
|
||||
content_hash TEXT NOT NULL, -- Hash SHA-256 para detecção de duplicatas
|
||||
created_at INTEGER NOT NULL -- Timestamp de criação
|
||||
);
|
||||
```
|
||||
|
||||
## Sistema de IDs Curtos
|
||||
|
||||
### Características:
|
||||
- **7 caracteres**: Combinação de letras maiúsculas, minúsculas e números (a-z, A-Z, 0-9)
|
||||
- **62^7 possibilidades**: Aproximadamente 3.5 trilhões de combinações únicas
|
||||
- **Detecção de duplicatas**: Pastes idênticos recebem o mesmo ID
|
||||
- **Geração aleatória**: IDs são gerados aleatoriamente para evitar previsibilidade
|
||||
|
||||
### Exemplo de URLs:
|
||||
- **Antes**: `http://localhost:8080/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447`
|
||||
- **Agora**: `http://localhost:8080/Ly34kx3`
|
||||
|
||||
## Passos para Migração
|
||||
|
||||
### 1. Backup dos Dados Atuais
|
||||
@@ -45,6 +60,8 @@ Se você quiser migrar dados existentes do BoltDB para SQLite3:
|
||||
go run cmd/migrate/main.go -bolt data/yasuc.db -sqlite data/yasuc.sqlite3
|
||||
```
|
||||
|
||||
**Nota**: Durante a migração, cada paste do BoltDB receberá um novo ID curto. URLs antigas não funcionarão mais.
|
||||
|
||||
### 4. Testar a Aplicação
|
||||
|
||||
```bash
|
||||
@@ -73,6 +90,12 @@ sqlite3 data/yasuc.sqlite3 ".schema pastes"
|
||||
sqlite3 data/yasuc.sqlite3 "SELECT COUNT(*) FROM pastes;"
|
||||
```
|
||||
|
||||
### Verificar IDs curtos gerados:
|
||||
|
||||
```bash
|
||||
sqlite3 data/yasuc.sqlite3 "SELECT short_id, substr(content, 1, 50) FROM pastes LIMIT 5;"
|
||||
```
|
||||
|
||||
## Rollback (se necessário)
|
||||
|
||||
Se algo der errado, você pode voltar ao BoltDB:
|
||||
@@ -81,27 +104,31 @@ Se algo der errado, você pode voltar ao BoltDB:
|
||||
2. Reverter o código para a versão anterior
|
||||
3. Reconstruir o container
|
||||
|
||||
## Vantagens do SQLite3
|
||||
## Vantagens do SQLite3 com IDs Curtos
|
||||
|
||||
1. **Consultas SQL**: Você pode fazer consultas como:
|
||||
1. **URLs mais amigáveis**: Fáceis de compartilhar e digitar
|
||||
2. **Consultas SQL**: Você pode fazer consultas como:
|
||||
```sql
|
||||
-- Encontrar pastes criados hoje
|
||||
SELECT * FROM pastes WHERE date(created_at, 'unixepoch') = date('now');
|
||||
|
||||
-- Encontrar pastes maiores que 1KB
|
||||
SELECT hash, length(content) as size FROM pastes WHERE length(content) > 1024;
|
||||
SELECT short_id, length(content) as size FROM pastes WHERE length(content) > 1024;
|
||||
|
||||
-- Verificar duplicatas
|
||||
SELECT content_hash, COUNT(*) as count FROM pastes GROUP BY content_hash HAVING count > 1;
|
||||
```
|
||||
|
||||
2. **Backup mais simples**: Um único arquivo
|
||||
3. **Ferramentas de administração**: SQLite Browser, DBeaver, etc.
|
||||
4. **Melhor performance**: Para consultas complexas
|
||||
3. **Backup mais simples**: Um único arquivo
|
||||
4. **Ferramentas de administração**: SQLite Browser, DBeaver, etc.
|
||||
5. **Melhor performance**: Para consultas complexas
|
||||
|
||||
## Notas Importantes
|
||||
|
||||
- O hash SHA-256 continua sendo usado como identificador único
|
||||
- A funcionalidade da API permanece a mesma
|
||||
- URLs dos pastes existentes continuam funcionando
|
||||
- O tamanho máximo de paste (4MB) permanece inalterado
|
||||
- **URLs antigas não funcionam**: Pastes migrados terão novos IDs curtos
|
||||
- **Detecção de duplicatas**: Pastes idênticos recebem o mesmo ID
|
||||
- **Tamanho máximo**: O tamanho máximo de paste (4MB) permanece inalterado
|
||||
- **Compatibilidade**: A API permanece a mesma, apenas os IDs mudaram
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -113,6 +140,11 @@ Se algo der errado, você pode voltar ao BoltDB:
|
||||
- Execute o script de migração novamente
|
||||
- Verifique se o arquivo SQLite3 foi criado corretamente
|
||||
|
||||
### Erro: "não foi possível gerar um ID único"
|
||||
- Extremamente raro, mas pode acontecer se houver muitas colisões
|
||||
- O sistema tenta 100 vezes antes de falhar
|
||||
- Considere aumentar o `maxAttempts` no código se necessário
|
||||
|
||||
### Performance lenta
|
||||
- Considere adicionar índices se necessário
|
||||
- Verifique se o volume Docker está configurado corretamente
|
||||
25
README.md
25
README.md
@@ -6,7 +6,7 @@ yet another [sprunge.us](http://sprunge.us/) clone
|
||||
|
||||
## Sobre
|
||||
|
||||
YASUC é um pastebin de linha de comando que armazena pastes usando SQLite3. Cada paste é identificado pelo hash SHA-256 do seu conteúdo, garantindo que pastes idênticos tenham a mesma URL.
|
||||
YASUC é um pastebin de linha de comando que armazena pastes usando SQLite3. Cada paste é identificado por um ID curto único de 7 caracteres, tornando as URLs mais amigáveis e fáceis de compartilhar.
|
||||
|
||||
## Uso do Servidor
|
||||
|
||||
@@ -39,13 +39,20 @@ docker run -d -p 8080:8080 -v /path/to/data:/data yasuc
|
||||
|
||||
# Exemplo
|
||||
echo 'hello world' | curl -F 'sprunge=<-' http://localhost:8080/
|
||||
# Retorna: http://localhost:8080/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
|
||||
# Retorna: http://localhost:8080/Ly34kx3
|
||||
|
||||
# Acessar um paste
|
||||
curl http://localhost:8080/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
|
||||
curl http://localhost:8080/Ly34kx3
|
||||
# Retorna: hello world
|
||||
```
|
||||
|
||||
## Características dos IDs Curtos
|
||||
|
||||
- **7 caracteres**: Combinação de letras maiúsculas, minúsculas e números
|
||||
- **Únicos**: Cada paste tem um ID único
|
||||
- **Detecção de duplicatas**: Pastes idênticos recebem o mesmo ID
|
||||
- **Amigáveis**: URLs mais curtas e fáceis de compartilhar
|
||||
|
||||
## Migração de BoltDB para SQLite3
|
||||
|
||||
Este projeto foi migrado de BoltDB para SQLite3. Para migrar dados existentes:
|
||||
@@ -58,9 +65,10 @@ Este projeto foi migrado de BoltDB para SQLite3. Para migrar dados existentes:
|
||||
|
||||
```sql
|
||||
CREATE TABLE pastes (
|
||||
hash TEXT PRIMARY KEY, -- Hash SHA-256 do conteúdo
|
||||
content TEXT NOT NULL, -- Conteúdo do paste
|
||||
created_at INTEGER NOT NULL -- Timestamp de criação
|
||||
short_id TEXT PRIMARY KEY, -- ID curto único (7 caracteres)
|
||||
content TEXT NOT NULL, -- Conteúdo do paste
|
||||
content_hash TEXT NOT NULL, -- Hash SHA-256 para detecção de duplicatas
|
||||
created_at INTEGER NOT NULL -- Timestamp de criação
|
||||
);
|
||||
```
|
||||
|
||||
@@ -81,7 +89,10 @@ SELECT COUNT(*) FROM pastes;
|
||||
SELECT * FROM pastes WHERE date(created_at, 'unixepoch') = date('now');
|
||||
|
||||
-- Pastes maiores que 1KB
|
||||
SELECT hash, length(content) as size FROM pastes WHERE length(content) > 1024;
|
||||
SELECT short_id, length(content) as size FROM pastes WHERE length(content) > 1024;
|
||||
|
||||
-- Encontrar duplicatas (mesmo conteúdo, IDs diferentes)
|
||||
SELECT content_hash, COUNT(*) as count FROM pastes GROUP BY content_hash HAVING count > 1;
|
||||
```
|
||||
|
||||
## Copyright
|
||||
|
||||
114
SHORT_IDS_SUMMARY.md
Normal file
114
SHORT_IDS_SUMMARY.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Resumo: Implementação de IDs Curtos
|
||||
|
||||
## ✅ Implementação Concluída com Sucesso!
|
||||
|
||||
O sistema YASUC foi atualizado para usar IDs curtos de 7 caracteres em vez de hashes SHA-256 completos.
|
||||
|
||||
## 🔄 Mudanças Principais
|
||||
|
||||
### 1. **Sistema de IDs Curtos**
|
||||
- ✅ **7 caracteres**: Combinação de letras maiúsculas, minúsculas e números
|
||||
- ✅ **62^7 possibilidades**: Aproximadamente 3.5 trilhões de combinações únicas
|
||||
- ✅ **Geração aleatória**: IDs são gerados aleatoriamente
|
||||
- ✅ **Detecção de duplicatas**: Pastes idênticos recebem o mesmo ID
|
||||
|
||||
### 2. **Estrutura do Banco Atualizada**
|
||||
```sql
|
||||
CREATE TABLE pastes (
|
||||
short_id TEXT PRIMARY KEY, -- ID curto único (7 caracteres)
|
||||
content TEXT NOT NULL, -- Conteúdo do paste
|
||||
content_hash TEXT NOT NULL, -- Hash SHA-256 para detecção de duplicatas
|
||||
created_at INTEGER NOT NULL -- Timestamp de criação
|
||||
);
|
||||
```
|
||||
|
||||
### 3. **Índices de Performance**
|
||||
- ✅ `idx_pastes_content_hash` - Para detecção rápida de duplicatas
|
||||
- ✅ `idx_pastes_created_at` - Para consultas por data
|
||||
|
||||
### 4. **Funcionalidades Implementadas**
|
||||
- ✅ Geração de IDs únicos com verificação de colisões
|
||||
- ✅ Detecção de duplicatas baseada no hash SHA-256 do conteúdo
|
||||
- ✅ Retorno do mesmo ID para pastes idênticos
|
||||
- ✅ Tratamento de erros para colisões de IDs
|
||||
|
||||
## 🧪 Testes Realizados
|
||||
|
||||
### ✅ Funcionalidade Básica
|
||||
- ✅ Geração de IDs curtos: `LV86NjG`, `dMZWovL`
|
||||
- ✅ URLs mais amigáveis: `http://localhost:8080/LV86NjG`
|
||||
- ✅ Recuperação de pastes funciona corretamente
|
||||
- ✅ Detecção de duplicatas funciona
|
||||
|
||||
### ✅ Testes de Duplicatas
|
||||
- ✅ Paste idêntico retorna o mesmo ID
|
||||
- ✅ Paste diferente gera novo ID
|
||||
- ✅ Sistema mantém integridade dos dados
|
||||
|
||||
## 📊 Comparação: Antes vs Agora
|
||||
|
||||
| Aspecto | Antes | Agora |
|
||||
|---------|-------|-------|
|
||||
| **URL** | `http://localhost:8080/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447` | `http://localhost:8080/Ly34kx3` |
|
||||
| **Tamanho** | 64 caracteres | 7 caracteres |
|
||||
| **Legibilidade** | Difícil de ler/digitar | Fácil de ler/digitar |
|
||||
| **Compartilhamento** | URLs longas e complexas | URLs curtas e amigáveis |
|
||||
| **Duplicatas** | Mesmo hash = mesma URL | Mesmo conteúdo = mesmo ID |
|
||||
|
||||
## 🔧 Vantagens dos IDs Curtos
|
||||
|
||||
1. **Usabilidade**: URLs mais fáceis de compartilhar e digitar
|
||||
2. **Legibilidade**: IDs curtos são mais humanos
|
||||
3. **Compatibilidade**: Funciona melhor em sistemas com limitações de URL
|
||||
4. **UX**: Melhor experiência do usuário
|
||||
5. **Manutenção**: Mais fácil de gerenciar e debugar
|
||||
|
||||
## 📁 Arquivos Modificados
|
||||
|
||||
### Código Principal:
|
||||
- `yasuc.go` - Sistema de IDs curtos implementado
|
||||
- `cmd/migrate/main.go` - Script de migração atualizado
|
||||
|
||||
### Documentação:
|
||||
- `README.md` - Documentação atualizada
|
||||
- `MIGRATION.md` - Instruções de migração atualizadas
|
||||
- `init.sql` - Script SQL atualizado
|
||||
|
||||
## 🚀 Como Usar
|
||||
|
||||
### Execução Local:
|
||||
```bash
|
||||
go build -o yasuc.exe .
|
||||
./yasuc.exe -db data/yasuc.sqlite3 -port 8080 -addr 127.0.0.1
|
||||
```
|
||||
|
||||
### Teste:
|
||||
```bash
|
||||
# Criar paste
|
||||
echo "hello world" | curl -F "sprunge=<-" http://localhost:8080/
|
||||
# Retorna: http://localhost:8080/Ly34kx3
|
||||
|
||||
# Acessar paste
|
||||
curl http://localhost:8080/Ly34kx3
|
||||
# Retorna: hello world
|
||||
```
|
||||
|
||||
## ⚠️ Notas Importantes
|
||||
|
||||
1. **URLs antigas não funcionam**: Pastes migrados terão novos IDs
|
||||
2. **Migração necessária**: Dados existentes precisam ser migrados
|
||||
3. **Detecção de duplicatas**: Funciona baseada no conteúdo, não no ID
|
||||
4. **Colisões**: Extremamente raras, mas tratadas automaticamente
|
||||
|
||||
## 🎯 Próximos Passos (Opcionais)
|
||||
|
||||
1. **Migrar dados existentes**: Use o script de migração
|
||||
2. **Personalizar charset**: Modificar caracteres usados nos IDs
|
||||
3. **Ajustar tamanho**: Alterar comprimento dos IDs se necessário
|
||||
4. **Adicionar prefixos**: Para diferentes tipos de paste
|
||||
|
||||
## ✅ Status Final
|
||||
|
||||
**IMPLEMENTAÇÃO CONCLUÍDA COM SUCESSO!**
|
||||
|
||||
O sistema agora gera URLs curtas e amigáveis mantendo toda a funcionalidade anterior, incluindo detecção de duplicatas e integridade dos dados.
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +17,41 @@ import (
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
const shortIDLength = 7
|
||||
|
||||
// generateShortID gera um ID único de 7 caracteres
|
||||
func generateShortID() string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, shortIDLength)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// generateUniqueShortID gera um ID único que não existe no banco
|
||||
func generateUniqueShortID(db *sql.DB) (string, error) {
|
||||
maxAttempts := 100
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
id := generateShortID()
|
||||
|
||||
// Verificar se o ID já existe
|
||||
var exists int
|
||||
err := db.QueryRow("SELECT 1 FROM pastes WHERE short_id = ? LIMIT 1", id).Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
return id, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("não foi possível gerar um ID único após %d tentativas", maxAttempts)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize random seed
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
var boltPath, sqlitePath string
|
||||
flag.StringVar(&boltPath, "bolt", "", "caminho para o arquivo BoltDB")
|
||||
flag.StringVar(&sqlitePath, "sqlite", "", "caminho para o arquivo SQLite3 de destino")
|
||||
@@ -44,8 +79,9 @@ func main() {
|
||||
// Criar tabela no SQLite3
|
||||
_, err = sqliteDB.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS pastes (
|
||||
hash TEXT PRIMARY KEY,
|
||||
short_id TEXT PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
content_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
`)
|
||||
@@ -53,6 +89,17 @@ func main() {
|
||||
log.Fatal("Erro ao criar tabela:", err)
|
||||
}
|
||||
|
||||
// Criar índices
|
||||
_, err = sqliteDB.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_content_hash ON pastes(content_hash)`)
|
||||
if err != nil {
|
||||
log.Fatal("Erro ao criar índice content_hash:", err)
|
||||
}
|
||||
|
||||
_, err = sqliteDB.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_created_at ON pastes(created_at)`)
|
||||
if err != nil {
|
||||
log.Fatal("Erro ao criar índice created_at:", err)
|
||||
}
|
||||
|
||||
// Iniciar transação no SQLite3
|
||||
tx, err := sqliteDB.Begin()
|
||||
if err != nil {
|
||||
@@ -60,7 +107,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Preparar statement de inserção
|
||||
stmt, err := tx.Prepare("INSERT OR IGNORE INTO pastes (hash, content, created_at) VALUES (?, ?, ?)")
|
||||
stmt, err := tx.Prepare("INSERT OR IGNORE INTO pastes (short_id, content, content_hash, created_at) VALUES (?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
log.Fatal("Erro ao preparar statement:", err)
|
||||
}
|
||||
@@ -68,6 +115,7 @@ func main() {
|
||||
|
||||
// Migrar dados do BoltDB para SQLite3
|
||||
count := 0
|
||||
duplicates := 0
|
||||
err = boltDB.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("pastes"))
|
||||
if b == nil {
|
||||
@@ -79,10 +127,27 @@ func main() {
|
||||
hash := sha256.Sum256(v)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
|
||||
// Inserir no SQLite3
|
||||
_, err := stmt.Exec(hashStr, string(v), time.Now().Unix())
|
||||
// Verificar se já existe um paste com o mesmo conteúdo
|
||||
var existingID string
|
||||
err := sqliteDB.QueryRow("SELECT short_id FROM pastes WHERE content_hash = ? LIMIT 1", hashStr).Scan(&existingID)
|
||||
if err == nil {
|
||||
// Paste já existe, pular
|
||||
duplicates++
|
||||
return nil
|
||||
} else if err != sql.ErrNoRows {
|
||||
return fmt.Errorf("erro ao verificar duplicata: %v", err)
|
||||
}
|
||||
|
||||
// Gerar ID curto único
|
||||
shortID, err := generateUniqueShortID(sqliteDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erro ao inserir paste %s: %v", hashStr, err)
|
||||
return fmt.Errorf("erro ao gerar ID único: %v", err)
|
||||
}
|
||||
|
||||
// Inserir no SQLite3
|
||||
_, err = stmt.Exec(shortID, string(v), hashStr, time.Now().Unix())
|
||||
if err != nil {
|
||||
return fmt.Errorf("erro ao inserir paste %s: %v", shortID, err)
|
||||
}
|
||||
|
||||
count++
|
||||
@@ -105,5 +170,5 @@ func main() {
|
||||
log.Fatal("Erro ao fazer commit:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Migração concluída! %d pastes migrados com sucesso.\n", count)
|
||||
fmt.Printf("Migração concluída! %d pastes migrados com sucesso, %d duplicatas ignoradas.\n", count, duplicates)
|
||||
}
|
||||
|
||||
Binary file not shown.
10
init.sql
10
init.sql
@@ -2,14 +2,16 @@
|
||||
-- Execute este script para criar a estrutura do banco
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pastes (
|
||||
hash TEXT PRIMARY KEY,
|
||||
short_id TEXT PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
content_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Criar índice para melhorar performance de consultas por data
|
||||
-- Criar índices para melhorar performance
|
||||
CREATE INDEX IF NOT EXISTS idx_pastes_content_hash ON pastes(content_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_pastes_created_at ON pastes(created_at);
|
||||
|
||||
-- Inserir alguns dados de exemplo (opcional)
|
||||
-- INSERT OR IGNORE INTO pastes (hash, content, created_at) VALUES
|
||||
-- ('a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447', 'hello world', strftime('%s', 'now'));
|
||||
-- INSERT OR IGNORE INTO pastes (short_id, content, content_hash, created_at) VALUES
|
||||
-- ('Ly34kx3', 'hello world', 'a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447', strftime('%s', 'now'));
|
||||
91
yasuc.go
91
yasuc.go
@@ -25,6 +25,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@@ -33,7 +34,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxPasteSize = 4 * 1024 * 1024
|
||||
maxPasteSize = 4 * 1024 * 1024
|
||||
shortIDLength = 7
|
||||
)
|
||||
|
||||
const usageText = `
|
||||
@@ -53,13 +55,12 @@ SYNOPSIS
|
||||
|
||||
DESCRIPTION
|
||||
A command line pastebin. Pastes are immutable and created with simple HTTP
|
||||
POST requests. The path of a paste's URL is the SHA-256 digest of the
|
||||
paste's contents.
|
||||
POST requests. The path of a paste's URL is a short unique identifier.
|
||||
|
||||
EXAMPLE
|
||||
$ echo 'hello world' | curl -F 'sprunge=<-' {{.BaseURL}}
|
||||
{{.BaseURL}}/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
|
||||
$ firefox {{.BaseURL}}/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
|
||||
{{.BaseURL}}/Ly34kx3
|
||||
$ firefox {{.BaseURL}}/Ly34kx3
|
||||
|
||||
COPYRIGHT
|
||||
Copyright © Tom Jakubowski. License AGPLv3: GNU Affero GPL Version 3
|
||||
@@ -82,23 +83,70 @@ func (e pasteTooLarge) Error() string {
|
||||
return fmt.Sprintf("paste too large (maximum size %d bytes)", maxPasteSize)
|
||||
}
|
||||
|
||||
// generateShortID gera um ID único de 7 caracteres
|
||||
func generateShortID() string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, shortIDLength)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// generateUniqueShortID gera um ID único que não existe no banco
|
||||
func generateUniqueShortID(db *sql.DB) (string, error) {
|
||||
maxAttempts := 100
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
id := generateShortID()
|
||||
|
||||
// Verificar se o ID já existe
|
||||
var exists int
|
||||
err := db.QueryRow("SELECT 1 FROM pastes WHERE short_id = ? LIMIT 1", id).Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
return id, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("não foi possível gerar um ID único após %d tentativas", maxAttempts)
|
||||
}
|
||||
|
||||
func stashPaste(db *sql.DB, pasteStr string) (key string, err error) {
|
||||
if len(pasteStr) > maxPasteSize {
|
||||
err = pasteTooLarge{}
|
||||
return
|
||||
}
|
||||
paste := []byte(pasteStr)
|
||||
rawKey := sha256.Sum256(paste)
|
||||
key = hex.EncodeToString(rawKey[:])
|
||||
|
||||
// Insert or ignore (SQLite's UPSERT equivalent)
|
||||
_, err = db.Exec("INSERT OR IGNORE INTO pastes (hash, content, created_at) VALUES (?, ?, ?)",
|
||||
key, pasteStr, time.Now().Unix())
|
||||
// Gerar ID curto único
|
||||
shortID, err := generateUniqueShortID(db)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
// Calcular hash SHA-256 para verificação de duplicatas
|
||||
paste := []byte(pasteStr)
|
||||
hash := sha256.Sum256(paste)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
|
||||
// Verificar se já existe um paste com o mesmo conteúdo
|
||||
var existingID string
|
||||
err = db.QueryRow("SELECT short_id FROM pastes WHERE content_hash = ? LIMIT 1", hashStr).Scan(&existingID)
|
||||
if err == nil {
|
||||
// Paste já existe, retornar o ID existente
|
||||
return existingID, nil
|
||||
} else if err != sql.ErrNoRows {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Inserir novo paste
|
||||
_, err = db.Exec("INSERT INTO pastes (short_id, content, content_hash, created_at) VALUES (?, ?, ?, ?)",
|
||||
shortID, pasteStr, hashStr, time.Now().Unix())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return shortID, nil
|
||||
}
|
||||
|
||||
type pasteNotFound struct{}
|
||||
@@ -109,7 +157,7 @@ func (e pasteNotFound) Error() string {
|
||||
|
||||
func fetchPaste(db *sql.DB, key string) (paste string, err error) {
|
||||
var content string
|
||||
err = db.QueryRow("SELECT content FROM pastes WHERE hash = ?", key).Scan(&content)
|
||||
err = db.QueryRow("SELECT content FROM pastes WHERE short_id = ?", key).Scan(&content)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", pasteNotFound{}
|
||||
@@ -177,15 +225,30 @@ func initDatabase(db *sql.DB) error {
|
||||
// Create the pastes table if it doesn't exist
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS pastes (
|
||||
hash TEXT PRIMARY KEY,
|
||||
short_id TEXT PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
content_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes for better performance
|
||||
_, err = db.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_content_hash ON pastes(content_hash)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE INDEX IF NOT EXISTS idx_pastes_created_at ON pastes(created_at)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize random seed
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
var dbPath, addr string
|
||||
var port int
|
||||
flag.StringVar(&dbPath, "db", "", "location of database file")
|
||||
|
||||
Reference in New Issue
Block a user