Compare commits

...

4 Commits

9 changed files with 1366 additions and 59 deletions

8
.vscode/launch.json vendored
View File

@ -18,10 +18,10 @@
"cwd": "${workspaceFolder}/Biblioteca",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
// "serverReadyAction": {
// "action": "openExternally",
// "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
// },
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},

View File

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup>
</Project>

View File

@ -2,7 +2,7 @@
### Obtém uma lista de livros
GET {{url}}/livros
GET {{url}}/livros?pagina=3
Accept: application/json
### Cria um novo livro
@ -12,29 +12,28 @@ Accept: application/json
Content-Type: application/json
{
"isbn": "9780321741769",
"titulo": "The C# Programming Language",
"autor": "Anders Hejlsberg"
"isbn": "aabbccddee",
"titulo": "Livro teste",
"autor": "Autor"
}
### Edita um livro
PUT {{url}}/livros/9780321741769
PUT {{url}}/livros/1027
Accept: application/json
Content-Type: application/json
{
"isbn": "9780321741769",
"titulo": "The C# Programming Language",
"titulo": "teste4",
"autor": "Anders Hejlsberg, Mads Torgensen"
}
### Obtém um livro individual
GET {{url}}/livros/9780321741769
GET {{url}}/livros/1027
Accept: application/json
### Remove um livro
DELETE {{url}}/livros/9780321741769
DELETE {{url}}/livros/1027
Accept: application/json

View File

@ -0,0 +1,16 @@
namespace Biblioteca.Models;
public class Livro
{
public long Id { get; set; }
public string? Isbn { get; set; }
public string? Titulo { get; set; }
public string? Autor { get; set; }
public string? Genero { get; set; }
public string? Descricao { get; set; }
public string? Foto { get; set; }
public string? Keywords { get; set; }
public bool Ativo { get; set; }
public DateTime CriadoEm { get; set; }
public DateTime AtualizadoEm { get; set; }
}

View File

@ -1,71 +1,52 @@
using Microsoft.AspNetCore.Mvc;
using Biblioteca.Models;
using Biblioteca.Repositories;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
string[] saudacoes = ["Olá!", "こんにちは", "Привет", "Ողջույն"];
return saudacoes[Random.Shared.Next(saudacoes.Length)];
});
// Obtém uma lista com os livros registrados.
app.MapGet("/livros", () =>
app.MapGet("/livros", async (int pagina) =>
{
return new Livro[]
{
new()
{
Isbn = "9780262510875",
Titulo = "Structure and Interpretation of Computer Programs",
Autor = "Gerald Jay Sussman"
},
new()
{
Isbn = "9780131103627",
Titulo = "C Programming Language: ANSI C Version",
Autor = "Dennis Ritchie, Brian Kerningham"
},
new()
{
Isbn = "9780134190440",
Titulo = "The Go Programming Language",
Autor = "Brian Kerningham"
}
};
var repo = new LivroRepository();
var res = await repo.Obter(pagina: pagina);
return res;
});
// Cria um novo livro.
app.MapPost("/livros", (Livro livro) =>
app.MapPost("/livros", async (Livro livro) =>
{
return livro;
var repo = new LivroRepository();
var res = await repo.Criar(livro);
return res;
});
// Edita um livro.
app.MapPut("/livros/{isbn}", (string isbn, Livro livro) =>
app.MapPut("/livros/{id}", async (long id, Livro livro) =>
{
return new { editando = isbn, dados = livro };
var repo = new LivroRepository();
var res = await repo.Editar(id, livro);
return res;
});
// Obtém os dados de um livro individual.
app.MapGet("/livros/{isbn}", (string isbn) =>
app.MapGet("/livros/{id}", async (long id) =>
{
return new Livro() { Isbn = isbn };
var repo = new LivroRepository();
var res = await repo.Obter(id);
return res;
});
// Remove um livro.
app.MapDelete("/livros/{isbn}", (string isbn) =>
app.MapDelete("/livros/{id}", async (long id) =>
{
return Results.NoContent();
var repo = new LivroRepository();
await repo.Desativar(id);
});
app.Run();
public class Livro
{
public string? Isbn { get; set; }
public string? Titulo { get; set; }
public string? Autor { get; set; }
};
app.Run();

View File

@ -0,0 +1,198 @@
using Biblioteca.Models;
using MySqlConnector;
namespace Biblioteca.Repositories;
public class LivroRepository
{
private const string ConnString = "Server=gbrl.dev;Port=5306;User ID=sistemasdistribuidos.aluno;Password=eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di;Database=sistemasdistribuidos";
public async Task<IEnumerable<Livro>> Obter(int pagina)
{
using var conn = new MySqlConnection(ConnString);
using var cmd = conn.CreateCommand();
await conn.OpenAsync();
var take = 30;
var offset = take * Math.Max(pagina-1, 0);
var lista = new List<Livro>();
cmd.CommandText = "SELECT Id, Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro ORDER BY CriadoEm LIMIT @offset,@take";
cmd.Parameters.AddWithValue("offset", offset);
cmd.Parameters.AddWithValue("take", take);
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
lista.Add(new()
{
Id = reader.GetInt64(0),
Isbn = reader.GetString(1),
Titulo = reader.GetString(2),
Autor = reader.GetString(3),
Genero = reader.GetString(4),
Descricao = reader.GetString(5),
Foto = reader.GetString(6),
Keywords = reader.GetString(7),
Ativo = reader.GetBoolean(8),
CriadoEm = reader.GetDateTime(9),
AtualizadoEm = reader.GetDateTime(10),
});
}
return lista;
}
/// <summary>
/// Obtém um livro pelo seu ISBN.
/// </summary>
/// <param name="isbn"></param>
/// <returns></returns>
public async Task<Livro> Obter(long id)
{
using var conn = new MySqlConnection(ConnString);
using var cmd = conn.CreateCommand();
await conn.OpenAsync();
cmd.CommandText = "SELECT Id, Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Id=@id";
cmd.Parameters.AddWithValue("id", id);
using var reader = await cmd.ExecuteReaderAsync();
var existe = await reader.ReadAsync();
if (!existe)
{
throw new Exception($"Livro {id} não encontrado");
}
return new()
{
Id = reader.GetInt64(0),
Isbn = reader.GetString(1),
Titulo = reader.GetString(2),
Autor = reader.GetString(3),
Genero = reader.GetString(4),
Descricao = reader.GetString(5),
Foto = reader.GetString(6),
Keywords = reader.GetString(7),
Ativo = reader.GetBoolean(8),
CriadoEm = reader.GetDateTime(9),
AtualizadoEm = reader.GetDateTime(10),
};
}
public async Task<Livro> Criar(Livro dados)
{
using var conn = new MySqlConnection(ConnString);
using var cmd = conn.CreateCommand();
await conn.OpenAsync();
var livro = new Livro
{
Isbn = dados.Isbn?.Trim() ?? "",
Titulo = dados.Titulo?.Trim() ?? "",
Autor = dados.Autor?.Trim() ?? "",
Genero = dados.Genero?.Trim() ?? "",
Descricao = dados.Descricao?.Trim() ?? "",
Foto = dados.Foto?.Trim() ?? "",
Keywords = dados.Keywords?.Trim() ?? "",
Ativo = true,
CriadoEm = DateTime.Now,
AtualizadoEm = default,
};
if (livro.Titulo == "")
{
throw new Exception("O título do livro é obrigatório.");
}
cmd.CommandText =
@"
INSERT INTO Livro
(Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm)
VALUES
(@isbn, @titulo, @autor, @genero, @descricao, @foto, @keywords, @ativo, @criadoem, @atualizadoem)
";
cmd.Parameters.AddWithValue("isbn", livro.Isbn);
cmd.Parameters.AddWithValue("titulo", livro.Titulo);
cmd.Parameters.AddWithValue("autor", livro.Autor);
cmd.Parameters.AddWithValue("genero", livro.Genero);
cmd.Parameters.AddWithValue("descricao", livro.Descricao);
cmd.Parameters.AddWithValue("foto", livro.Foto);
cmd.Parameters.AddWithValue("keywords", livro.Keywords);
cmd.Parameters.AddWithValue("ativo", livro.Ativo);
cmd.Parameters.AddWithValue("criadoem", livro.CriadoEm);
cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
await cmd.ExecuteNonQueryAsync();
using var cmd2 = conn.CreateCommand();
cmd2.CommandText = "SELECT LAST_INSERT_ID()";
using var reader = await cmd2.ExecuteReaderAsync();
await reader.ReadAsync();
livro.Id = reader.GetInt64(0);
return livro;
}
public async Task<Livro> Editar(long id, Livro dados)
{
using var conn = new MySqlConnection(ConnString);
using var cmd = conn.CreateCommand();
await conn.OpenAsync();
var livro = await Obter(id);
livro.Titulo = dados.Titulo?.Trim() ?? "";
livro.Autor = dados.Autor?.Trim() ?? "";
livro.Genero = dados.Genero?.Trim() ?? "";
livro.Descricao = dados.Descricao?.Trim() ?? "";
livro.Foto = dados.Foto?.Trim() ?? "";
livro.Keywords = dados.Keywords?.Trim() ?? "";
livro.AtualizadoEm = DateTime.Now;
cmd.CommandText =
@"
UPDATE Livro SET
Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
WHERE Id=@id
";
cmd.Parameters.AddWithValue("id", id);
cmd.Parameters.AddWithValue("titulo", livro.Titulo);
cmd.Parameters.AddWithValue("autor", livro.Autor);
cmd.Parameters.AddWithValue("genero", livro.Genero);
cmd.Parameters.AddWithValue("descricao", livro.Descricao);
cmd.Parameters.AddWithValue("foto", livro.Foto);
cmd.Parameters.AddWithValue("keywords", livro.Keywords);
cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
await cmd.ExecuteNonQueryAsync();
return livro;
}
public async Task Desativar(long id)
{
using var conn = new MySqlConnection(ConnString);
using var cmd = conn.CreateCommand();
await conn.OpenAsync();
cmd.CommandText = "UPDATE Livro SET Ativo=false WHERE Id=@id";
cmd.Parameters.AddWithValue("id", id);
await cmd.ExecuteNonQueryAsync();
}
}

71
scripts/books.py Normal file
View File

@ -0,0 +1,71 @@
#
# books.py
# Gera um arquivo .sql com INSERTs de livros obtidos do Project Gutemberg
# https://gutendex.com/
#
import http.client
import json
import time
from urllib.parse import urlparse
API_URL = "https://gutendex.com/books/"
BOOKS_COUNT = 1000
FETCH_DELAY = 1#s
OUTPUT = "./books.sql"
def fetch(resource):
url = urlparse(resource)
client = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
conn = client(url.netloc)
conn.request("GET", f"{url.path}?{url.query}")
res = conn.getresponse()
if res.status < 200 or res.status > 299:
return {}
return json.loads(res.read())
def write_inserts(file, page):
data = fetch(f"{API_URL}?page={page}")
books = data["results"]
lines = []
esc = lambda str: str.replace('"', '\\"')
for book in books:
summaries = book["summaries"]
imgs = [book["formats"][f] for f in book["formats"] if f.startswith("image/")]
isbn = ""
titulo = book["title"]
autor = ",".join([a["name"] for a in book["authors"]])
genero = ",".join(book["subjects"])
descricao = summaries[0] if len(summaries) > 0 else ""
foto = imgs[0] if len(imgs) > 0 else ""
keywords = ",".join(book["bookshelves"])
lines.append(f'("{esc(isbn)}", "{esc(titulo)}", "{esc(autor)}", "{esc(genero)}", "{esc(descricao)}", "{esc(foto)}", "{esc(keywords)}", true, NOW(), NOW())')
values = ",\n".join(lines)
insert = f"INSERT INTO Livro (Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm) VALUES {values};\n"
file.write(insert)
return len(lines)
def collect(n, file, _page=1):
if n <= 0:
return
written = write_inserts(file, _page)
time.sleep(FETCH_DELAY)
collect(n-written, file, _page+1)
def run():
with open(OUTPUT, "w") as file:
collect(BOOKS_COUNT, file)
run()

1024
scripts/books.sql Normal file

File diff suppressed because it is too large Load Diff

17
scripts/schema.sql Normal file
View File

@ -0,0 +1,17 @@
CREATE TABLE Livro (
Id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
Isbn VARCHAR(255) NOT NULL,
Titulo VARCHAR(512) NOT NULL,
Autor TEXT NOT NULL,
Genero TEXT NOT NULL,
Descricao TEXT NOT NULL,
Foto TEXT NOT NULL,
Keywords TEXT NOT NULL,
Ativo BOOLEAN NOT NULL DEFAULT 0,
CriadoEm DATETIME NOT NULL,
AtualizadoEm DATETIME NOT NULL,
INDEX (Isbn),
INDEX (CriadoEm),
FULLTEXT (Titulo, Autor, Genero, Descricao, Keywords)
);