From 91880dc61f72f2eca0443a5b6134eb47775e1aa6 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 26 Nov 2024 21:04:30 -0300 Subject: [PATCH] feat: company_created, company_deleted, event_created logs --- .../request/RecoveryRequestUseCase.java | 2 +- .../app/company/CompanyServiceFactory.java | 7 +- .../company/create/CreateCompanyUseCase.java | 12 +- .../company/delete/DeleteCompanyUseCase.java | 9 +- .../company/update/UpdateCompanyUseCase.java | 2 +- .../core/create/CreateEnrollmentUseCase.java | 2 +- .../CreateUpsertUpsertEnrollmentUseCase.java | 9 +- .../app/event/EventServiceFactory.java | 5 +- .../app/event/create/CreateEventUseCase.java | 16 +- .../sale/create/CreateTicket2SellUseCase.java | 2 +- database/inserir_padroes.sql | 2 + database/tabelas.sql | 338 ++++++++++++------ .../tickets/domain/shared/IDomainEvent.java | 2 + .../domain/shared/event/CompanyCreated.java | 62 ++++ .../domain/shared/event/CompanyDeleted.java | 62 ++++ .../domain/shared/event/EventCreated.java | 62 ++++ ...ketError.java => TicketConsumedError.java} | 18 +- ...uccess.java => TicketConsumedSuccess.java} | 18 +- .../ifsp/tickets/domain/ticket/Ticket.java | 18 +- .../com/ifsp/tickets/infra/api/EventAPI.java | 2 +- .../api/controllers/ExceptionController.java | 9 +- .../infra/config/app/CompanyConfig.java | 4 +- .../tickets/infra/config/app/EventConfig.java | 4 +- .../infra/shared/event/InfraAppEvent.java | 14 +- observability/docker-compose.yml | 3 + 25 files changed, 525 insertions(+), 159 deletions(-) create mode 100644 domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyCreated.java create mode 100644 domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyDeleted.java create mode 100644 domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/EventCreated.java rename domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/{ConsumeTicketError.java => TicketConsumedError.java} (68%) rename domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/{ConsumeTicketSuccess.java => TicketConsumedSuccess.java} (66%) diff --git a/application/src/main/java/br/com/ifsp/tickets/app/auth/recovery/request/RecoveryRequestUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/auth/recovery/request/RecoveryRequestUseCase.java index c3cf923..e738ffd 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/auth/recovery/request/RecoveryRequestUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/auth/recovery/request/RecoveryRequestUseCase.java @@ -44,7 +44,7 @@ public void execute(RecoveryRequestInput aCommand) { } final PasswordRecovery passwordRecovery = PasswordRecovery.create(user, ipAddress, userAgent); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the password recovery request"); passwordRecovery.validate(notification); notification.throwPossibleErrors(); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/company/CompanyServiceFactory.java b/application/src/main/java/br/com/ifsp/tickets/app/company/CompanyServiceFactory.java index a3d9e67..cc999da 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/company/CompanyServiceFactory.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/company/CompanyServiceFactory.java @@ -11,17 +11,18 @@ import br.com.ifsp.tickets.app.company.update.IUpdateCompanyUseCase; import br.com.ifsp.tickets.app.company.update.UpdateCompanyUseCase; import br.com.ifsp.tickets.domain.company.ICompanyGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; import br.com.ifsp.tickets.domain.user.IUserGateway; public class CompanyServiceFactory { private static CompanyService companyService; - public static CompanyService create(ICompanyGateway companyGateway, IUserGateway userGateway) { + public static CompanyService create(ICompanyGateway companyGateway, IUserGateway userGateway, IDomainEventPublisher eventPublisher) { if (companyService == null) { - final ICreateCompanyUseCase companyUseCase = new CreateCompanyUseCase(userGateway, companyGateway); + final ICreateCompanyUseCase companyUseCase = new CreateCompanyUseCase(userGateway, companyGateway, eventPublisher); final IGetCompanyByIdUseCase getCompanyByIdUseCase = new GetCompanyByIdUseCase(companyGateway); - final IDeleteCompanyUseCase deleteCompanyUseCase = new DeleteCompanyUseCase(companyGateway); + final IDeleteCompanyUseCase deleteCompanyUseCase = new DeleteCompanyUseCase(companyGateway, eventPublisher); final IUpdateCompanyUseCase updateCompanyUseCase = new UpdateCompanyUseCase(companyGateway); final ISearchCompanyUseCase searchCompanyUseCase = new SearchCompanyUseCase(companyGateway); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/company/create/CreateCompanyUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/company/create/CreateCompanyUseCase.java index 3ac423e..d3b69c2 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/company/create/CreateCompanyUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/company/create/CreateCompanyUseCase.java @@ -3,6 +3,8 @@ import br.com.ifsp.tickets.domain.company.Company; import br.com.ifsp.tickets.domain.company.ICompanyGateway; import br.com.ifsp.tickets.domain.company.vo.CNPJ; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; +import br.com.ifsp.tickets.domain.shared.event.CompanyCreated; import br.com.ifsp.tickets.domain.shared.exceptions.AlreadyJoinedACompany; import br.com.ifsp.tickets.domain.shared.exceptions.IllegalResourceAccessException; import br.com.ifsp.tickets.domain.shared.exceptions.NotFoundException; @@ -16,10 +18,12 @@ public class CreateCompanyUseCase implements ICreateCompanyUseCase { private final IUserGateway userGateway; private final ICompanyGateway companyGateway; + private final IDomainEventPublisher eventPublisher; - public CreateCompanyUseCase(IUserGateway userGateway, ICompanyGateway companyGateway) { + public CreateCompanyUseCase(IUserGateway userGateway, ICompanyGateway companyGateway, IDomainEventPublisher eventPublisher) { this.userGateway = userGateway; this.companyGateway = companyGateway; + this.eventPublisher = eventPublisher; } @Override @@ -56,12 +60,16 @@ public CreateCompanyOutput execute(CreateCompanyInput anIn) { address ); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the company"); company.validate(notification); notification.throwPossibleErrors(); company = this.companyGateway.create(company); owner.joinCompany(company.getId()); this.userGateway.update(owner); + + company.registerEvent(new CompanyCreated(company, creator)); + company.publishDomainEvents(this.eventPublisher); + return CreateCompanyOutput.from(company); } } diff --git a/application/src/main/java/br/com/ifsp/tickets/app/company/delete/DeleteCompanyUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/company/delete/DeleteCompanyUseCase.java index 4a0eb4d..e04cf0d 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/company/delete/DeleteCompanyUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/company/delete/DeleteCompanyUseCase.java @@ -3,6 +3,8 @@ import br.com.ifsp.tickets.domain.company.Company; import br.com.ifsp.tickets.domain.company.CompanyID; import br.com.ifsp.tickets.domain.company.ICompanyGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; +import br.com.ifsp.tickets.domain.shared.event.CompanyDeleted; import br.com.ifsp.tickets.domain.shared.exceptions.IllegalResourceAccessException; import br.com.ifsp.tickets.domain.shared.exceptions.NotFoundException; import br.com.ifsp.tickets.domain.user.User; @@ -10,9 +12,11 @@ public class DeleteCompanyUseCase implements IDeleteCompanyUseCase { private final ICompanyGateway companyGateway; + private final IDomainEventPublisher eventPublisher; - public DeleteCompanyUseCase(ICompanyGateway companyGateway) { + public DeleteCompanyUseCase(ICompanyGateway companyGateway, IDomainEventPublisher eventPublisher) { this.companyGateway = companyGateway; + this.eventPublisher = eventPublisher; } @Override @@ -25,5 +29,8 @@ public void execute(DeleteCompanyInput anIn) { // TODO - lidar com os tickets, eventos e usuários associados a essa empresa this.companyGateway.delete(company); + + company.registerEvent(new CompanyDeleted(company, authenticatedUser)); + company.publishDomainEvents(this.eventPublisher); } } diff --git a/application/src/main/java/br/com/ifsp/tickets/app/company/update/UpdateCompanyUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/company/update/UpdateCompanyUseCase.java index 69ebd1a..d555ef5 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/company/update/UpdateCompanyUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/company/update/UpdateCompanyUseCase.java @@ -33,7 +33,7 @@ public UpdateCompanyOutput execute(UpdateCompanyInput anIn) { final Company company = this.companyGateway.findById(companyID).orElseThrow(() -> NotFoundException.with(Company.class, companyID)); company.updateCompanyInfo(name, description, cnpj); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the company"); company.validate(notification); notification.throwPossibleErrors(); return UpdateCompanyOutput.from(this.companyGateway.update(company)); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/enrollment/core/create/CreateEnrollmentUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/enrollment/core/create/CreateEnrollmentUseCase.java index 0873172..8df159d 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/enrollment/core/create/CreateEnrollmentUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/enrollment/core/create/CreateEnrollmentUseCase.java @@ -83,7 +83,7 @@ public CreateEnrollmentOutput execute(CreateEnrollmentInput anIn) { final LocalDate expiredIn = event.getEndDate().plusDays(1); final Ticket ticket = Ticket.newTicketWithId(ticketID, userID, document, event, ticketSale, ticketSale.getDescription(), event.getInitDate(), expiredIn); final Message message = this.messageGateway.findBySubjectAndType(MessageSubject.EVENT_TICKET, MessageType.HTML).orElseThrow(() -> NotFoundException.with("Email template not found")); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the enrollment"); enrollment.validate(notification); ticket.validate(notification); notification.throwPossibleErrors(); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/enrollment/upsert/create/CreateUpsertUpsertEnrollmentUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/enrollment/upsert/create/CreateUpsertUpsertEnrollmentUseCase.java index 80d9c67..1428687 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/enrollment/upsert/create/CreateUpsertUpsertEnrollmentUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/enrollment/upsert/create/CreateUpsertUpsertEnrollmentUseCase.java @@ -1,16 +1,10 @@ package br.com.ifsp.tickets.app.enrollment.upsert.create; import br.com.ifsp.tickets.app.enrollment.ITicketQRGenerator; -import br.com.ifsp.tickets.app.enrollment.core.create.CreateEnrollmentOutput; -import br.com.ifsp.tickets.domain.communication.email.Email; import br.com.ifsp.tickets.domain.communication.email.IEmailGateway; import br.com.ifsp.tickets.domain.communication.message.IMessageGateway; -import br.com.ifsp.tickets.domain.communication.message.Message; -import br.com.ifsp.tickets.domain.communication.message.type.MessageSubject; -import br.com.ifsp.tickets.domain.communication.message.type.MessageType; import br.com.ifsp.tickets.domain.company.Company; import br.com.ifsp.tickets.domain.company.ICompanyGateway; -import br.com.ifsp.tickets.domain.enrollment.Enrollment; import br.com.ifsp.tickets.domain.enrollment.IEnrollmentGateway; import br.com.ifsp.tickets.domain.enrollment.upsert.IUpsertEnrollmentGateway; import br.com.ifsp.tickets.domain.enrollment.upsert.UpsertEnrollment; @@ -25,7 +19,6 @@ import br.com.ifsp.tickets.domain.shared.file.IFileStorage; import br.com.ifsp.tickets.domain.shared.validation.handler.Notification; import br.com.ifsp.tickets.domain.ticket.ITicketGateway; -import br.com.ifsp.tickets.domain.ticket.Ticket; import br.com.ifsp.tickets.domain.ticket.TicketID; import br.com.ifsp.tickets.domain.user.User; import br.com.ifsp.tickets.domain.user.UserID; @@ -93,7 +86,7 @@ public String execute(CreateUpsertEnrollmentInput anIn) { final UpsertEnrollment enrollment = UpsertEnrollment .newUpsertEnrollment(name, emailString, document, birthDate, userID, eventID, ticketSaleID, ticketID); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the enrollment"); enrollment.validate(notification); notification.throwPossibleErrors(); final UpsertEnrollment createdEnrollment = this.upsertEnrollmentGateway.create(enrollment); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/event/EventServiceFactory.java b/application/src/main/java/br/com/ifsp/tickets/app/event/EventServiceFactory.java index 4568b36..def501b 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/event/EventServiceFactory.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/event/EventServiceFactory.java @@ -23,15 +23,16 @@ import br.com.ifsp.tickets.domain.company.ICompanyGateway; import br.com.ifsp.tickets.domain.event.IEventGateway; import br.com.ifsp.tickets.domain.event.sale.ITicketSaleGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; import br.com.ifsp.tickets.domain.shared.file.IFileStorage; public class EventServiceFactory { private static EventService eventService; - public static EventService create(ICompanyGateway companyGateway, IEventGateway eventGateway, ITicketSaleGateway ticketSaleGateway, IFileStorage fileStorage) { + public static EventService create(ICompanyGateway companyGateway, IEventGateway eventGateway, ITicketSaleGateway ticketSaleGateway, IFileStorage fileStorage, IDomainEventPublisher eventPublisher) { if (eventService == null) { - final ICreateEventUseCase createEventUseCase = new CreateEventUseCase(companyGateway, eventGateway); + final ICreateEventUseCase createEventUseCase = new CreateEventUseCase(companyGateway, eventGateway, eventPublisher); final IGetEventUseCase getEventUseCase = new GetEventUseCase(eventGateway); final ISearchEventUseCase searchEventUseCase = new SearchEventUseCase(eventGateway); final IDownloadThumbnailUseCase downloadThumbnailUseCase = new DownloadThumbnailUseCase(fileStorage, eventGateway); diff --git a/application/src/main/java/br/com/ifsp/tickets/app/event/create/CreateEventUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/event/create/CreateEventUseCase.java index bfa1983..0942ae5 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/event/create/CreateEventUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/event/create/CreateEventUseCase.java @@ -7,6 +7,8 @@ import br.com.ifsp.tickets.domain.event.EventConfig; import br.com.ifsp.tickets.domain.event.EventConfigKey; import br.com.ifsp.tickets.domain.event.IEventGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; +import br.com.ifsp.tickets.domain.shared.event.EventCreated; import br.com.ifsp.tickets.domain.shared.exceptions.IllegalResourceAccessException; import br.com.ifsp.tickets.domain.shared.exceptions.NoCompanyException; import br.com.ifsp.tickets.domain.shared.exceptions.NotFoundException; @@ -21,22 +23,29 @@ public class CreateEventUseCase implements ICreateEventUseCase { private final ICompanyGateway companyGateway; private final IEventGateway eventGateway; + private final IDomainEventPublisher eventPublisher; - public CreateEventUseCase(ICompanyGateway companyGateway, IEventGateway eventGateway) { + public CreateEventUseCase(ICompanyGateway companyGateway, IEventGateway eventGateway, IDomainEventPublisher eventPublisher) { this.companyGateway = companyGateway; this.eventGateway = eventGateway; + this.eventPublisher = eventPublisher; } @Override public CreateEventOutput execute(CreateEventInput anIn) { final User user = anIn.user(); final CompanyID companyID = CompanyID.with(anIn.companyId()); + if (!user.canManageEvents() && !user.canManageAnyEvent()) throw new IllegalResourceAccessException("User does not have permission to create events"); + if (!user.hasCompany() && !user.canManageAnyEvent()) throw new NoCompanyException(); + final CompanyID userCompanyID = user.getCompanyID(); + if (!user.canManageAnyEvent() && !userCompanyID.equals(companyID)) throw new IllegalResourceAccessException("User does not have permission to create events for this company"); + final Company company = this.companyGateway.findById(companyID).orElseThrow(() -> NotFoundException.with(Company.class, companyID)); final String name = anIn.name(); final String description = anIn.description(); @@ -55,11 +64,14 @@ public CreateEventOutput execute(CreateEventInput anIn) { config ); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating the event"); event.validate(notification); notification.throwPossibleErrors(); final Event eventCreated = this.eventGateway.create(event); + eventCreated.registerEvent(new EventCreated(eventCreated, user)); + eventCreated.publishDomainEvents(this.eventPublisher); + return CreateEventOutput.from(eventCreated); } diff --git a/application/src/main/java/br/com/ifsp/tickets/app/event/sale/create/CreateTicket2SellUseCase.java b/application/src/main/java/br/com/ifsp/tickets/app/event/sale/create/CreateTicket2SellUseCase.java index dc5f46a..879fe3f 100644 --- a/application/src/main/java/br/com/ifsp/tickets/app/event/sale/create/CreateTicket2SellUseCase.java +++ b/application/src/main/java/br/com/ifsp/tickets/app/event/sale/create/CreateTicket2SellUseCase.java @@ -41,7 +41,7 @@ public CreateTicket2SellOutput execute(CreateTicket2SellInput anIn) { final BigDecimal price = anIn.price(); final int entries = anIn.entries(); final TicketSale ticketSale = TicketSale.newTicketSale(event, name, description, price, entries); - final Notification notification = Notification.create(); + final Notification notification = Notification.create("An error occurred while validating a ticket sale"); ticketSale.validate(notification); notification.throwPossibleErrors(); return CreateTicket2SellOutput.from(this.ticketSaleGateway.create(ticketSale)); diff --git a/database/inserir_padroes.sql b/database/inserir_padroes.sql index e74e7b5..0f49c5f 100644 --- a/database/inserir_padroes.sql +++ b/database/inserir_padroes.sql @@ -1,3 +1,5 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + CREATE OR REPLACE PROCEDURE CriarEntidadesPadroes() AS $$ diff --git a/database/tabelas.sql b/database/tabelas.sql index 44f88c9..05c0ed1 100644 --- a/database/tabelas.sql +++ b/database/tabelas.sql @@ -1,7 +1,6 @@ create table addresses ( - id uuid not null - primary key, + id uuid not null, city varchar(255) not null, complement varchar(255), country varchar(255) not null, @@ -12,118 +11,209 @@ create table addresses zip_code varchar(255) not null ); +alter table addresses + add constraint addresses_pkey + primary key (id); + create table companies ( - address_id uuid - unique - constraint fk8w70yf6urddd0ky7ev90okenf - references addresses, - id uuid not null - primary key, + address_id uuid, + id uuid not null, owner_id uuid not null, - cnpj varchar(255) not null - unique, + cnpj varchar(255) not null, description varchar(255), name varchar(255) not null ); +alter table companies + add constraint companies_pkey + primary key (id); + +alter table companies + add constraint companies_address_id_key + unique (address_id); + +alter table companies + add constraint companies_cnpj_key + unique (cnpj); + +alter table companies + add constraint fk8w70yf6urddd0ky7ev90okenf + foreign key (address_id) references addresses; + +create table email_attachments +( + email_id bigint not null, + attachments varchar(255) +); + create table emails ( failed_attempts integer not null, sent boolean not null, created_at timestamp(6) not null, - id bigserial - primary key, + id bigserial, sent_at timestamp(6), body text not null, subject varchar(255) not null, target varchar(255) not null ); -create table email_attachments -( - email_id bigint not null - constraint fk753q149o0k6fjxh2fji1616ua - references emails, - attachments varchar(255) -); +alter table emails + add constraint emails_pkey + primary key (id); + +alter table email_attachments + add constraint fk753q149o0k6fjxh2fji1616ua + foreign key (email_id) references emails; -CREATE TABLE enrollments +create table enrollments ( - id uuid NOT NULL PRIMARY KEY, - event_id uuid NOT NULL, - name varchar(255) NOT NULL, - email varchar(255) NOT NULL, - birth_date date NOT NULL, - document varchar(255) NOT NULL, - status varchar(255) NOT NULL, - created_at timestamp(6) NOT NULL, + birth_date date not null, + created_at timestamp(6) not null, updated_at timestamp(6), - user_id uuid + event_id uuid not null, + id uuid not null, + user_id uuid, + document varchar(255) not null, + email varchar(255) not null, + name varchar(255) not null, + status varchar(255) not null ); -ALTER TABLE enrollments - ADD COLUMN name varchar(255), - ADD COLUMN email varchar(255), - ADD COLUMN birth_date date, - ADD COLUMN document varchar(255); +alter table enrollments + add constraint enrollments_pkey + primary key (id); +create table event_attachments +( + event_id uuid not null, + attachment_paths varchar(255) +); -create table events_thumbnails +create table event_configurations ( - id uuid - constraint events_thumbnails_pk - primary key, - filename varchar not null, - uploaded_at timestamp not null + event_id uuid not null, + key varchar(255) not null, + value varchar(255) ); +alter table event_configurations + add constraint event_configurations_pkey + primary key (event_id, key); + create table events ( end_date date not null, init_date date not null, status integer not null, - address_id uuid - unique - constraint fkquc7xx27bo60lupj2rf7e0hn2 - references addresses, - event_thumbnail_id uuid - constraint events_event_thumbnail_id_fk - references events_thumbnails (id), + address_id uuid, company_id uuid not null, - id uuid not null - primary key, + event_thumbnail_id uuid, + id uuid not null, description varchar(1000), name varchar(255) not null ); -create table event_attachments -( - event_id uuid not null - constraint fko49p20umx9s78ww9w2h91dcao - references events, - attachment_paths varchar(255) -); +alter table events + add constraint events_pkey + primary key (id); -create table event_configurations +alter table event_attachments + add constraint fko49p20umx9s78ww9w2h91dcao + foreign key (event_id) references events; + +alter table event_configurations + add constraint fk33jpq0j7g5hn9x03pi9y8qh5q + foreign key (event_id) references events; + +alter table events + add constraint events_address_id_key + unique (address_id); + +alter table events + add constraint events_event_thumbnail_id_key + unique (event_thumbnail_id); + +alter table events + add constraint fkquc7xx27bo60lupj2rf7e0hn2 + foreign key (address_id) references addresses; + +create table events_thumbnails ( - event_id uuid not null - constraint fk33jpq0j7g5hn9x03pi9y8qh5q - references events, - key varchar(255) not null, - value varchar(255), - primary key (event_id, key) + uploaded_at timestamp(6) not null, + id uuid not null, + filename varchar(255) not null ); +alter table events_thumbnails + add constraint events_thumbnails_pkey + primary key (id); + +alter table events + add constraint fk32u9t33qldisvv10fq4kffp7w + foreign key (event_thumbnail_id) references events_thumbnails; + create table messages ( - id serial - primary key, + id serial, subject integer not null, type char not null, template text not null ); +alter table messages + add constraint messages_pkey + primary key (id); + +create table password_recovery +( + used boolean not null, + created_at timestamp(6) not null, + expires_at timestamp(6) not null, + used_at timestamp(6), + id uuid not null, + user_id uuid not null, + agent varchar(255) not null, + ip_address varchar(255) not null, + token varchar(255) not null +); + +alter table password_recovery + add constraint password_recovery_pkey + primary key (id); + +alter table password_recovery + add constraint password_recovery_token_key + unique (token); + +create table payments +( + id bigint not null, + payment_date timestamp(6) not null, + external_reference varchar(255) not null, + status varchar(255) not null +); + +alter table payments + add constraint payments_pkey + primary key (id); + +create table ticket_sale +( + active boolean not null, + entries integer not null, + price numeric(38, 2) not null, + event_id uuid not null, + id uuid not null, + description varchar(255) not null, + name varchar(255) not null +); + +alter table ticket_sale + add constraint ticket_sale_pkey + primary key (id); + create table tickets ( expired_in date not null, @@ -131,16 +221,61 @@ create table tickets created_at timestamp(6) not null, last_time_consumed timestamp(6), event_id uuid not null, - id uuid not null - primary key, - user_id uuid not null, + id uuid not null, + ticket_sale_id uuid not null, + user_id uuid, code varchar(255) not null, description varchar(255) not null, + document varchar(255) not null, status varchar(255) not null ); -ALTER TABLE tickets - ADD COLUMN document varchar(255); +alter table tickets + add constraint tickets_pkey + primary key (id); + +create table upsert_emails +( + last_notification_date timestamp(6), + request_date timestamp(6) not null, + id uuid not null, + user_id uuid not null, + email varchar(255) not null, + token varchar(255) not null +); + +alter table upsert_emails + add constraint upsert_emails_pkey + primary key (id); + +alter table upsert_emails + add constraint upsert_emails_user_id_key + unique (user_id); + +alter table upsert_emails + add constraint upsert_emails_email_key + unique (email); + +alter table upsert_emails + add constraint upsert_emails_token_key + unique (token); + +create table upsert_enrollments +( + birth_date date not null, + event_id uuid not null, + id uuid not null, + ticket_id uuid not null, + ticket_sale_id uuid not null, + user_id uuid, + document varchar(255) not null, + email varchar(255) not null, + name varchar(255) not null +); + +alter table upsert_enrollments + add constraint upsert_enrollments_pkey + primary key (id); create table users ( @@ -149,49 +284,36 @@ create table users password_date date, role_id integer not null, company_id uuid, - id uuid not null - primary key, + id uuid not null, bio varchar(255), - document varchar(255) - unique, - email varchar(255) - unique, + document varchar(255) not null, + email varchar(255), name varchar(255) not null, password varchar(255), phone varchar(255), username varchar(255) not null - unique ); -create table password_recovery -( - used boolean not null, - created_at timestamp(6) not null, - expires_at timestamp(6) not null, - used_at timestamp(6), - id uuid not null - primary key, - user_id uuid not null - constraint fke8rvirgchpmurh9y9sq1rkxsd - references users, - agent varchar(255) not null, - ip_address varchar(255) not null, - token varchar(255) not null - unique -); +alter table users + add constraint users_pkey + primary key (id); -create table upsert_emails -( - last_notification_date timestamp(6), - request_date timestamp(6) not null, - id uuid not null - primary key, - user_id uuid not null - unique - constraint fk4r2qn2aoel26lppx4p60a09sy - references users on DELETE cascade, - email varchar(255) not null - unique, - token varchar(255) not null - unique -); +alter table password_recovery + add constraint fke8rvirgchpmurh9y9sq1rkxsd + foreign key (user_id) references users; + +alter table upsert_emails + add constraint fk4r2qn2aoel26lppx4p60a09sy + foreign key (user_id) references users; + +alter table users + add constraint users_document_key + unique (document); + +alter table users + add constraint users_email_key + unique (email); + +alter table users + add constraint users_username_key + unique (username); \ No newline at end of file diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/IDomainEvent.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/IDomainEvent.java index 1256d78..d51379f 100644 --- a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/IDomainEvent.java +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/IDomainEvent.java @@ -17,6 +17,8 @@ public interface IDomainEvent extends Serializable { DomainEventType type(); + String targetId(); + String id(); diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyCreated.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyCreated.java new file mode 100644 index 0000000..998ccbb --- /dev/null +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyCreated.java @@ -0,0 +1,62 @@ +package br.com.ifsp.tickets.domain.shared.event; + +import br.com.ifsp.tickets.domain.company.Company; +import br.com.ifsp.tickets.domain.shared.DomainEventType; +import br.com.ifsp.tickets.domain.shared.IDomainEvent; +import br.com.ifsp.tickets.domain.user.User; + +import java.time.Instant; +import java.util.UUID; + +public class CompanyCreated implements IDomainEvent { + + private final String targetId; + private final String name; + private final String authorId; + + public CompanyCreated(Company company, User author) { + this.targetId = company.getId().getValue().toString(); + this.name = company.getName(); + this.authorId = author.getId().getValue().toString(); + } + + @Override + public Instant occurredOn() { + return Instant.now(); + } + + @Override + public String subject() { + return "CompanyCreated"; + } + + @Override + public String message() { + return "Company has been created"; + } + + @Override + public String reason() { + return "User " + authorId + " created the company '" + name + "'"; + } + + @Override + public String source() { + return Company.class.getName(); + } + + @Override + public DomainEventType type() { + return DomainEventType.INFO; + } + + @Override + public String targetId() { + return this.targetId; + } + + @Override + public String id() { + return UUID.randomUUID().toString(); + } +} diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyDeleted.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyDeleted.java new file mode 100644 index 0000000..154df5c --- /dev/null +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/CompanyDeleted.java @@ -0,0 +1,62 @@ +package br.com.ifsp.tickets.domain.shared.event; + +import br.com.ifsp.tickets.domain.company.Company; +import br.com.ifsp.tickets.domain.shared.DomainEventType; +import br.com.ifsp.tickets.domain.shared.IDomainEvent; +import br.com.ifsp.tickets.domain.user.User; + +import java.time.Instant; +import java.util.UUID; + +public class CompanyDeleted implements IDomainEvent { + + private final String targetId; + private final String name; + private final String authorId; + + public CompanyDeleted(Company company, User author) { + this.targetId = company.getId().getValue().toString(); + this.name = company.getName(); + this.authorId = author.getId().getValue().toString(); + } + + @Override + public Instant occurredOn() { + return Instant.now(); + } + + @Override + public String subject() { + return "CompanyDeleted"; + } + + @Override + public String message() { + return "Company has been deleted"; + } + + @Override + public String reason() { + return "User " + authorId + " deleted the company '" + name + "'"; + } + + @Override + public String source() { + return Company.class.getName(); + } + + @Override + public DomainEventType type() { + return DomainEventType.INFO; + } + + @Override + public String targetId() { + return this.targetId; + } + + @Override + public String id() { + return UUID.randomUUID().toString(); + } +} diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/EventCreated.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/EventCreated.java new file mode 100644 index 0000000..148a5f8 --- /dev/null +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/EventCreated.java @@ -0,0 +1,62 @@ +package br.com.ifsp.tickets.domain.shared.event; + +import br.com.ifsp.tickets.domain.event.Event; +import br.com.ifsp.tickets.domain.shared.DomainEventType; +import br.com.ifsp.tickets.domain.shared.IDomainEvent; +import br.com.ifsp.tickets.domain.user.User; + +import java.time.Instant; +import java.util.UUID; + +public class EventCreated implements IDomainEvent { + + private final String targetId; + private final String name; + private final String authorId; + + public EventCreated(Event event, User author) { + this.targetId = event.getId().getValue().toString(); + this.name = event.getName(); + this.authorId = author.getId().getValue().toString(); + } + + @Override + public Instant occurredOn() { + return Instant.now(); + } + + @Override + public String subject() { + return "EventCreated"; + } + + @Override + public String message() { + return "Event has been created"; + } + + @Override + public String reason() { + return "User " + authorId + " created the event '" + name + "'"; + } + + @Override + public String source() { + return Event.class.getName(); + } + + @Override + public DomainEventType type() { + return DomainEventType.INFO; + } + + @Override + public String targetId() { + return this.targetId; + } + + @Override + public String id() { + return UUID.randomUUID().toString(); + } +} diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketError.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedError.java similarity index 68% rename from domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketError.java rename to domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedError.java index be253d6..2dfeb8f 100644 --- a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketError.java +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedError.java @@ -5,14 +5,15 @@ import br.com.ifsp.tickets.domain.ticket.Ticket; import java.time.Instant; +import java.util.UUID; -public class ConsumeTicketError implements IDomainEvent { +public class TicketConsumedError implements IDomainEvent { - private final String id; + private final String targetId; private final String reason; - public ConsumeTicketError(Ticket ticket, String reason) { - this.id = ticket.getId().getValue().toString(); + public TicketConsumedError(Ticket ticket, String reason) { + this.targetId = ticket.getId().getValue().toString(); this.reason = reason; } @@ -23,7 +24,7 @@ public Instant occurredOn() { @Override public String subject() { - return "ConsumeTicket"; + return "TicketConsumed"; } @Override @@ -41,9 +42,14 @@ public DomainEventType type() { return DomainEventType.ERROR; } + @Override + public String targetId() { + return this.targetId; + } + @Override public String id() { - return this.id; + return UUID.randomUUID().toString(); } @Override diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketSuccess.java b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedSuccess.java similarity index 66% rename from domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketSuccess.java rename to domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedSuccess.java index da653ec..dc67f47 100644 --- a/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/ConsumeTicketSuccess.java +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/shared/event/TicketConsumedSuccess.java @@ -5,13 +5,14 @@ import br.com.ifsp.tickets.domain.ticket.Ticket; import java.time.Instant; +import java.util.UUID; -public class ConsumeTicketSuccess implements IDomainEvent { +public class TicketConsumedSuccess implements IDomainEvent { - private final String id; + private final String targetId; - public ConsumeTicketSuccess(Ticket ticket) { - this.id = ticket.getId().getValue().toString(); + public TicketConsumedSuccess(Ticket ticket) { + this.targetId = ticket.getId().getValue().toString(); } @Override @@ -21,7 +22,7 @@ public Instant occurredOn() { @Override public String subject() { - return "ConsumeTicket"; + return "TicketConsumed"; } @Override @@ -44,8 +45,13 @@ public DomainEventType type() { return DomainEventType.INFO; } + @Override + public String targetId() { + return this.targetId; + } + @Override public String id() { - return this.id; + return UUID.randomUUID().toString(); } } diff --git a/domain/src/main/java/br/com/ifsp/tickets/domain/ticket/Ticket.java b/domain/src/main/java/br/com/ifsp/tickets/domain/ticket/Ticket.java index f14106e..b57b3fd 100644 --- a/domain/src/main/java/br/com/ifsp/tickets/domain/ticket/Ticket.java +++ b/domain/src/main/java/br/com/ifsp/tickets/domain/ticket/Ticket.java @@ -5,8 +5,8 @@ import br.com.ifsp.tickets.domain.event.sale.TicketSale; import br.com.ifsp.tickets.domain.event.sale.TicketSaleID; import br.com.ifsp.tickets.domain.shared.Entity; -import br.com.ifsp.tickets.domain.shared.event.ConsumeTicketError; -import br.com.ifsp.tickets.domain.shared.event.ConsumeTicketSuccess; +import br.com.ifsp.tickets.domain.shared.event.TicketConsumedError; +import br.com.ifsp.tickets.domain.shared.event.TicketConsumedSuccess; import br.com.ifsp.tickets.domain.shared.exceptions.ChangeTicketStatusException; import br.com.ifsp.tickets.domain.shared.exceptions.DomainException; import br.com.ifsp.tickets.domain.shared.exceptions.TicketConsumeException; @@ -88,43 +88,43 @@ public void cancel() { public Optional consume(Event event) { if (!event.getStatus().isOpened() && !event.getStatus().isInProgress()) { final TicketConsumeException exception = new TicketConsumeException("Event is not open and is not in progress"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } if (!event.getId().equals(this.eventID)) { final TicketConsumeException exception = new TicketConsumeException("Ticket does not belong to this event"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } if (this.status.isCanceled()) { final TicketConsumeException exception = new TicketConsumeException("Ticket is canceled"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } if (this.status.isConsumed()) { final TicketConsumeException exception = new TicketConsumeException("Ticket is already consumed"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } if (this.status.isExpired() || this.expire()) { final TicketConsumeException exception = new TicketConsumeException("Ticket is expired"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } if (this.isValidToConsume()) { final TicketConsumeException exception = new TicketConsumeException("Ticket is not valid yet"); - this.registerEvent(new ConsumeTicketError(this, exception.getMessage())); + this.registerEvent(new TicketConsumedError(this, exception.getMessage())); return Optional.of(exception); } this.status = TicketStatus.CONSUMED; this.lastTimeConsumed = LocalDateTime.now(); - this.registerEvent(new ConsumeTicketSuccess(this)); + this.registerEvent(new TicketConsumedSuccess(this)); return Optional.empty(); } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java index ba89000..f44ad88 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/EventAPI.java @@ -23,7 +23,7 @@ @Tag(name = "Event", description = "Event API - manage events from a company") public interface EventAPI { - @PostMapping(consumes = "application/json") + @PostMapping(value = "/", consumes = "application/json") @Operation( summary = "Create event", description = "Create a new event", diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java index ebc14d7..60f5564 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/api/controllers/ExceptionController.java @@ -26,6 +26,7 @@ public ResponseEntity handleNotificationException(ValidationEx HttpStatus.BAD_REQUEST.value(), ex.getErrors().stream().map(APIError::from).toList() ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.badRequest().body(APIErrorResponse); } @@ -37,6 +38,7 @@ public ResponseEntity handleIllegalArgumentException(IllegalAr HttpStatus.BAD_REQUEST.value(), Stream.of(new Error(ex.getMessage())).map(APIError::from).toList() ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.badRequest().body(APIErrorResponse); } @@ -48,6 +50,7 @@ public ResponseEntity handleIllegalCommandField(IllegalCommand HttpStatus.BAD_REQUEST.value(), ex.getErrors().stream().map(APIError::from).toList() ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.badRequest().body(APIErrorResponse); } @@ -59,6 +62,7 @@ public ResponseEntity handleNotFoundException(NotFoundExceptio HttpStatus.NOT_FOUND.value(), null ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(APIErrorResponse); } @@ -70,6 +74,7 @@ public ResponseEntity handleAuthenticationException(Authentica HttpStatus.UNAUTHORIZED.value(), null ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(APIErrorResponse); } @@ -81,6 +86,7 @@ public ResponseEntity handleIllegalResourceAccess(IllegalResou HttpStatus.FORBIDDEN.value(), null ); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.status(HttpStatus.FORBIDDEN).body(APIErrorResponse); } @@ -92,12 +98,11 @@ public ResponseEntity handleDomainException(DomainException ex HttpStatus.BAD_REQUEST.value(), null ); - log.warn(ex.getMessage(), ex.getCause()); + log.error(ex.getMessage(), ex.getCause()); return ResponseEntity.badRequest().body(APIErrorResponse); } - public record APIError( @JsonProperty("message") String message diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/CompanyConfig.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/CompanyConfig.java index 01946f5..abc40d0 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/CompanyConfig.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/CompanyConfig.java @@ -3,6 +3,7 @@ import br.com.ifsp.tickets.app.company.CompanyService; import br.com.ifsp.tickets.app.company.CompanyServiceFactory; import br.com.ifsp.tickets.domain.company.ICompanyGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; import br.com.ifsp.tickets.domain.user.IUserGateway; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -15,10 +16,11 @@ public class CompanyConfig { private final ICompanyGateway companyGateway; private final IUserGateway userGateway; + private final IDomainEventPublisher eventPublisher; @Bean public CompanyService companyService() { - return CompanyServiceFactory.create(companyGateway, userGateway); + return CompanyServiceFactory.create(companyGateway, userGateway, eventPublisher); } } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/EventConfig.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/EventConfig.java index 6f7f079..f01d396 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/EventConfig.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/config/app/EventConfig.java @@ -5,6 +5,7 @@ import br.com.ifsp.tickets.domain.company.ICompanyGateway; import br.com.ifsp.tickets.domain.event.IEventGateway; import br.com.ifsp.tickets.domain.event.sale.ITicketSaleGateway; +import br.com.ifsp.tickets.domain.shared.IDomainEventPublisher; import br.com.ifsp.tickets.domain.shared.file.IFileStorage; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -19,9 +20,10 @@ public class EventConfig { private final IEventGateway eventGateway; private final ITicketSaleGateway ticketSaleGateway; private final IFileStorage fileStorage; + private final IDomainEventPublisher eventPublisher; @Bean public EventService eventService() { - return EventServiceFactory.create(companyGateway, eventGateway, ticketSaleGateway, fileStorage); + return EventServiceFactory.create(companyGateway, eventGateway, ticketSaleGateway, fileStorage, eventPublisher); } } diff --git a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/event/InfraAppEvent.java b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/event/InfraAppEvent.java index b224174..c980a20 100644 --- a/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/event/InfraAppEvent.java +++ b/infrastructure/src/main/java/br/com/ifsp/tickets/infra/shared/event/InfraAppEvent.java @@ -2,17 +2,19 @@ import br.com.ifsp.tickets.domain.shared.DomainEventType; import br.com.ifsp.tickets.domain.shared.IDomainEvent; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.*; import org.springframework.context.ApplicationEvent; import java.time.Instant; @JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(value = {"timestamp"}) public class InfraAppEvent extends ApplicationEvent implements IDomainEvent { @JsonProperty("id") private final String id; + @JsonProperty("target_id") + private final String targetId; @JsonProperty("subject") private final String subject; @JsonProperty("message") @@ -28,7 +30,8 @@ public class InfraAppEvent extends ApplicationEvent implements IDomainEvent { public InfraAppEvent(Object objectSource, IDomainEvent domainEvent) { super(objectSource); - this.id = domainEvent.id(); + this.id = domainEvent.targetId(); + this.targetId = domainEvent.targetId(); this.subject = domainEvent.subject(); this.message = domainEvent.message(); this.reason = domainEvent.reason(); @@ -67,6 +70,11 @@ public DomainEventType type() { return this.type; } + @Override + public String targetId() { + return this.targetId; + } + @Override public String id() { return this.id; diff --git a/observability/docker-compose.yml b/observability/docker-compose.yml index 2ca9c26..0265b07 100644 --- a/observability/docker-compose.yml +++ b/observability/docker-compose.yml @@ -35,12 +35,14 @@ services: loki: image: grafana/loki:latest command: -config.file=/etc/loki/local-config.yaml + restart: always ports: - "3100:3100" tempo: image: grafana/tempo:latest command: [ "-config.file=/etc/tempo.yml" ] + restart: always volumes: - ./docker/tempo/tempo.yml:/etc/tempo.yml ports: @@ -50,6 +52,7 @@ services: grafana: container_name: grafana image: grafana/grafana + restart: always volumes: - ./docker/grafana/grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml environment: