diff --git a/backend/src/main/java/de/strichliste/dto/CompanyDto.java b/backend/src/main/java/de/strichliste/dto/CompanyDto.java index 6190e56..90c7740 100644 --- a/backend/src/main/java/de/strichliste/dto/CompanyDto.java +++ b/backend/src/main/java/de/strichliste/dto/CompanyDto.java @@ -1,3 +1,3 @@ package de.strichliste.dto; -public record CompanyDto(Long id, String name, boolean active) {} +public record CompanyDto(Long id, String name, boolean active, boolean hasLogo) {} diff --git a/backend/src/main/java/de/strichliste/entity/Company.java b/backend/src/main/java/de/strichliste/entity/Company.java index 059d408..16aa63c 100644 --- a/backend/src/main/java/de/strichliste/entity/Company.java +++ b/backend/src/main/java/de/strichliste/entity/Company.java @@ -22,6 +22,12 @@ public class Company extends PanacheEntityBase { @Column(name = "created_at", nullable = false, updatable = false) public LocalDateTime createdAt = LocalDateTime.now(); + @Column(name = "logo") + public byte[] logo; + + @Column(name = "logo_content_type", length = 50) + public String logoContentType; + @OneToMany(mappedBy = "company", fetch = FetchType.LAZY) public List employees; diff --git a/backend/src/main/java/de/strichliste/resource/CompanyAdminResource.java b/backend/src/main/java/de/strichliste/resource/CompanyAdminResource.java index c61337f..14666db 100644 --- a/backend/src/main/java/de/strichliste/resource/CompanyAdminResource.java +++ b/backend/src/main/java/de/strichliste/resource/CompanyAdminResource.java @@ -5,10 +5,12 @@ import de.strichliste.entity.*; import de.strichliste.filter.AuthFilter.Secured; import jakarta.transaction.Transactional; import jakarta.ws.rs.*; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.multipart.FileUpload; +import java.io.IOException; +import java.nio.file.Files; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -96,6 +98,47 @@ public class CompanyAdminResource { return Response.ok(new EmployeeDto(employee.id, employee.company.id, employee.firstName, employee.lastName, employee.active)).build(); } + @POST + @Path("/logo") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Transactional + public Response uploadLogo(@QueryParam("token") String token, @RestForm("file") FileUpload file) throws IOException { + AccessLink link = AccessLink.findByToken(token).orElse(null); + if (link == null || link.company == null) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + Company company = Company.findById(link.company.id); + if (company == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + company.logo = Files.readAllBytes(file.uploadedFile()); + company.logoContentType = file.contentType(); + + return Response.ok().build(); + } + + @DELETE + @Path("/logo") + @Transactional + public Response deleteLogo(@QueryParam("token") String token) { + AccessLink link = AccessLink.findByToken(token).orElse(null); + if (link == null || link.company == null) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + Company company = Company.findById(link.company.id); + if (company == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + company.logo = null; + company.logoContentType = null; + + return Response.ok().build(); + } + @GET @Path("/report") public Response getMonthlyReport(@QueryParam("token") String token, @QueryParam("month") String month) { diff --git a/backend/src/main/java/de/strichliste/resource/ProviderAdminResource.java b/backend/src/main/java/de/strichliste/resource/ProviderAdminResource.java index 0cf7faf..930177d 100644 --- a/backend/src/main/java/de/strichliste/resource/ProviderAdminResource.java +++ b/backend/src/main/java/de/strichliste/resource/ProviderAdminResource.java @@ -29,7 +29,7 @@ public class ProviderAdminResource { return Company.findAll().list().stream() .map(obj -> { Company c = (Company) obj; - return new CompanyDto(c.id, c.name, c.active); + return new CompanyDto(c.id, c.name, c.active, c.logo != null); }) .toList(); } @@ -42,7 +42,7 @@ public class ProviderAdminResource { company.name = request.name(); company.persist(); return Response.status(Response.Status.CREATED) - .entity(new CompanyDto(company.id, company.name, company.active)) + .entity(new CompanyDto(company.id, company.name, company.active, company.logo != null)) .build(); } @@ -55,7 +55,7 @@ public class ProviderAdminResource { return Response.status(Response.Status.NOT_FOUND).build(); } company.name = request.name(); - return Response.ok(new CompanyDto(company.id, company.name, company.active)).build(); + return Response.ok(new CompanyDto(company.id, company.name, company.active, company.logo != null)).build(); } @PUT @@ -67,7 +67,7 @@ public class ProviderAdminResource { return Response.status(Response.Status.NOT_FOUND).build(); } company.active = !company.active; - return Response.ok(new CompanyDto(company.id, company.name, company.active)).build(); + return Response.ok(new CompanyDto(company.id, company.name, company.active, company.logo != null)).build(); } // --- Products --- diff --git a/backend/src/main/java/de/strichliste/resource/PublicResource.java b/backend/src/main/java/de/strichliste/resource/PublicResource.java index 21f49d6..a337f85 100644 --- a/backend/src/main/java/de/strichliste/resource/PublicResource.java +++ b/backend/src/main/java/de/strichliste/resource/PublicResource.java @@ -21,7 +21,7 @@ public class PublicResource { @Path("/companies") public List getActiveCompanies() { return Company.findAllActive().stream() - .map(c -> new CompanyDto(c.id, c.name, c.active)) + .map(c -> new CompanyDto(c.id, c.name, c.active, c.logo != null)) .toList(); } @@ -62,6 +62,18 @@ public class PublicResource { return Response.status(Response.Status.CREATED).build(); } + @GET + @Path("/companies/{id}/logo") + @Produces("*/*") + public Response getCompanyLogo(@PathParam("id") Long companyId) { + Company company = Company.findById(companyId); + if (company == null || company.logo == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + String contentType = company.logoContentType != null ? company.logoContentType : "image/png"; + return Response.ok(company.logo, contentType).build(); + } + @GET @Path("/tally/monthly/{employeeId}") public List getMonthlyTally( diff --git a/backend/src/main/resources/db/migration/V3__company_logo.sql b/backend/src/main/resources/db/migration/V3__company_logo.sql new file mode 100644 index 0000000..b35f674 --- /dev/null +++ b/backend/src/main/resources/db/migration/V3__company_logo.sql @@ -0,0 +1,2 @@ +ALTER TABLE company ADD COLUMN logo MEDIUMBLOB; +ALTER TABLE company ADD COLUMN logo_content_type VARCHAR(50); diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts index 8d5a476..93cd5ba 100644 --- a/frontend/src/lib/api/client.ts +++ b/frontend/src/lib/api/client.ts @@ -18,6 +18,7 @@ export interface Company { id: number; name: string; active: boolean; + hasLogo: boolean; } export interface Employee { @@ -90,6 +91,17 @@ export const api = { // --- Company Admin --- companyAdmin: { + uploadLogo: async (token: string, file: File): Promise => { + const form = new FormData(); + form.append('file', file); + const res = await fetch(`${API_BASE}/admin/company/logo?token=${token}`, { + method: 'POST', + body: form + }); + if (!res.ok) throw new Error(`API error: ${res.status}`); + }, + deleteLogo: (token: string) => + request(`/admin/company/logo?token=${token}`, { method: 'DELETE' }), getEmployees: (token: string) => request(`/admin/company/employees?token=${token}`), createEmployee: (token: string, firstName: string, lastName: string) => diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 796fc88..47c7c33 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -28,7 +28,15 @@
{#each companies as company} -
🏢
+ {#if company.hasLogo} + {company.name} + {:else} +
🏢
+ {/if}

{company.name}

{/each} diff --git a/frontend/src/routes/admin/company/+page.svelte b/frontend/src/routes/admin/company/+page.svelte index 4ac4181..6f9b2e9 100644 --- a/frontend/src/routes/admin/company/+page.svelte +++ b/frontend/src/routes/admin/company/+page.svelte @@ -1,7 +1,7 @@