Meu primeiro projeto backend, uma api de mangas! pt-2
Continuação
Ola, meu nome e Caio tenho 16 anos e este texto é a continuação de um projeto que iniciei ontem, link pro primeiro texto:aqui.
Atualização
Criei o projeto usando o Rider da JetBrains como IDE, escolhi o template WebApi e habilitei o docker(quero aprender a usar ele no futuro).
Até agora a estrutura de pastas esta bem simples, assim:
Application | Controllers | Domain |
---|---|---|
Repositories, ViewModels | Animes, Mangas, Users, Mangakas | Data, Models |
Repositories> MangaRepo> PagesRepo ViewModels> MangasViewModel> PagesViewModel | Mangas> MangasController.cs | Data> AppDbContext.cs Models> Animes, Mangakas, Tags, USers |
Nao achei uma forma mais clara de explicar a estrutura se nao com tabelas, enfim aqui vai a definição de cada pasta:
- Application: Tudo que vou applicar nas controllers, ex: repositorios, dtos, serviços etc
- Controllers: Minhas Controllers
- Domain: Camada mais profunda da aplicação, onde eu guardo minhas models q dificilmente acabo mudando.
Alem dessas, as pastas dentro delas:
- Repositories: Aonde vou manusear tudo que esta relacionado as entidades, desde a criação até a listagem delas, também e aqui onde vou mapear as mesmas e consultar ao database.
- ViewModels: Aonde vou criar minhas ViewModels.
- Animes, Mangas, Users, Mangakas: Sao as pastas pra cada controller, pra cada controller eu crio uma interface e a classe da controller em sí.
- Data: Pasta onde armazeno a classe AppDbContext que organiza as entidades no banco de dados, formando as relações necessarias.
- Models: Pasta onde eu guardo todas as minhas entidades.
Agora vamos para o que importa, o que eu fiz ate agora.
Comecei defindo o que eu queria que cada manga tivesse como propriedade, olhei e revirei alguns sites pensando em como organizaria a relação entre as pages e os mangas em sí.
Acabei deixando a entidade Mangas assim:
public class Mangas
{
[Key]
public string? Id { get; set; }
public string? Title { get; set; }
public string? TagsModelId { get; set; }
public TagsModel? TagsModel { get; set; }
public string? Author { get; set; }
public string? Group { get; set; }
public string? Translation { get; set; }
public CollectionPage? CollectionPage { get; set; }
public string? Description { get; set; }
public int? Popularity { get; set; }
}
Tambem criei um arquivo Page.cs onde armazeno as seguintes entidades:
public class CollectionPage
{
[Key]
public string? CollectionId { get; set; }
[ForeignKey("Id")]
public string? MangaId { get; set; }
public List<PageModel>? PageModels = [];
}
public class PageModel
{
[Key]
public int? PageNumber { get; set; }
[ForeignKey("CollectionId")]
public string? CollectionId { get; set; }
public string? PageName { get; set; }
public string? MangaUrl { get; set; }
}
E tambem as Tags:
public class TagsModel
{
[Key]
public string? TagsId { get; set; }
public string? MangaId { get; set; }
public Dictionary<string, bool>? Tags { get; set; } = new Dictionary<string, bool>()
{
{"Ação", false},
{"Comédia", false},
{"Shounen", false},
{"Seinen", false}
};
public Mangas.Mangas Manga { get; set; }
}
- CollectionPage: Uma entidade que tem relaçao unica com Mangas, mangas podem ter uma CollectionPage e CollectionPages podem ter um Manga linkado pela prop MangaId.
- PageModel: Uma entidade que tem multiplas relações com CollectionPage, uma CollectionPage pode ter muitas Pages.
- Tags: Tags pra filtragem dos mangas, elas tambem vao ser implementadas no animes, por enquanto so deixei algumas pra simplificar, depois vou adicionar bem mais
Depois disso, nao criei mais nenhuma entidade pq vi o quao complexo e criar estas entidades e definir sua relações no EF Core, então terminei por ai nas entidades, depois crio as outras que sao ou iguais ou menos complexas que essa.
Sobre as controllers, nao fiz nada ainda porque to trabalhando nos repositorios.
Agora sobre meu context, está assim:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TagsModel>()
.OwnsOne(x => x.Tags)
.WithOwner();
modelBuilder.Entity<Mangas>()
.HasOne(x => x.TagsModel)
.WithOne(e => e.Manga)
.HasForeignKey<Mangas>(m => m.TagsModelId)
.IsRequired();
}
public DbSet<Mangas> Mangas { get; set; }
public DbSet<CollectionPage> CollectionPages { get; set; }
public DbSet<PageModel> PageModels { get; set; }
public DbSet<TagsModel> Tags { get; set; }
}
Eu começo definindo as relações sobreescrevendo o metodo OnModelCreating, confesso que levei um tempo ate pegar este codigo, li a documentação mas no fim tive que apelar pro GPT.Depois vou estudar mais estas relações com o EF Core porque achei muito complicado.
Depois rodei as migrations e boom:
CultureInfo only the invariant culture is supported in globalization-invariant mode.
Esse erro ja me ocorreu mas eu não lembrava como resolver porque provavelmente na época so joguei o erro no GPT infinitas vezes ate ele me dar um solução, e dessa vez nao funcionou.Ai veio o stress máximo, depois disso fui pesquisar fora do GPT e achei a solução em 2 minutos no stackoverflow.
Conseguir rodar as migrations e o database update.
Depois fui pros repositories, ali eu dei uma bugada.
Fiquei pensando em como fazer pra gerar um manga, ate que achei um jeito.Pensei assim:
Vou criar um MangaRepository onde vou gerar o Manga sozinho e suas props, depois ou gerar suas Pages em outro repositorio chamado PagesRepository. entao os repostories ficaram assim:
MangaRepository
public interface IMangaRepository
{
Task<List<Mangas>> GetMangas();
Task<Mangas> GetMangaById(string id);
Mangas Generate(MangasViewModel model);
}
public class MangaRepository : IMangaRepository
{
private readonly AppDbContext _context;
public MangaRepository(AppDbContext context)
{
_context = context;
}
public async Task<List<Mangas>> GetMangas()
{
return await _context.Mangas.ToListAsync();
}
public async Task<Mangas> GetMangaById(string id)
{
return await _context.Mangas.FirstAsync(x => x.Id == id);
}
public Mangas Generate(MangasViewModel model)
{
var newManga = new Mangas()
{
Id = Guid.NewGuid().ToString(),
Title = model.Title,
Author = model.Author,
Description = model.Description,
Group = model.Group,
Translation = model.Translation
};
return newManga;
}
}
PagesRepository
public interface IPageRepository
{
Task<CollectionPage> GetPages(string id);
Task<PageModel> GetPageById(string id);
Task<Mangas> GenerateFullManga(CollectionPagesViewModel model, string id, List<PageModel> pageModels);
}
public class PageRepository : IPageRepository
{
private readonly AppDbContext _context;
public PageRepository(AppDbContext context)
{
_context = context;
}
public async Task<CollectionPage> GetPages(string id)
{
return await _context.CollectionPages.FirstAsync(x => x.MangaId == id);
}
public async Task<PageModel> GetPageById(string id)
{
return await _context.PageModels.FirstAsync(x => x.CollectionId == id);
}
public async Task<Mangas> GenerateFullManga(CollectionPagesViewModel? model, string? id, List<PageModel>? pageModels)
{
var newCollectionPage = new CollectionPage
{
CollectionId = Guid.NewGuid().ToString(),
MangaId = id,
PageModels = pageModels
};
var manga = await _context.Mangas.FirstAsync(x => x.Id == id);
manga.TagsModel = new TagsModel()
{
Tags = model?.Tags,
MangaId = id,
Manga = manga,
TagsId = Guid.NewGuid().ToString()
};
manga.CollectionPage = newCollectionPage;
return manga;
}
}
E pronto!
Definido alguns dos metodos de cada repositorio. Finalizei o primeiro passo desse projeto.
Agora vou começar a modificar as controllers, todas elas vao ter autenticação autorização em JWT, mas vou externalizar isso em um serviço separado de JWT.
Quero sugestões e críticas ao projeto.
Link do Repositorio do projeto: aqui
Desculpa se o texto ta difícil de entender, tentei me esforçar pra deixar ele bonitinho.
É isso tchau!