Skip to content

Commit

Permalink
Merge branch 'release/0.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Sep 16, 2024
2 parents 5c81146 + a0d2235 commit 6300922
Show file tree
Hide file tree
Showing 15 changed files with 102 additions and 54 deletions.
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
org.gradle.jvmargs=-Xmx1024M
quarkusPlatformVersion=3.14.2
quarkusPluginVersion=3.14.2
quarkusPlatformVersion=3.14.4
quarkusPluginVersion=3.14.4
sentryVersion=7.14.0
twelveMonkeysVersion=3.11.0
spotlessPluginVersion=6.25.0
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/app/fyreplace/api/data/Color.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.fyreplace.api.data;

import org.eclipse.microprofile.openapi.annotations.media.Schema;

public record Color(
@Schema(required = true, minimum = "0", maximum = "255", format = "uint8") int r,
@Schema(required = true, minimum = "0", maximum = "255", format = "uint8") int g,
@Schema(required = true, minimum = "0", maximum = "255", format = "uint8") int b) {}
2 changes: 1 addition & 1 deletion src/main/java/app/fyreplace/api/data/TokenCreation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

public record TokenCreation(
@NotBlank @Length(max = Email.EMAIL_MAX_LENGTH) @Schema(maxLength = Email.EMAIL_MAX_LENGTH) String identifier,
@NotBlank String secret) {}
@NotBlank @Schema(format = "password") String secret) {}
36 changes: 24 additions & 12 deletions src/main/java/app/fyreplace/api/data/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
import jakarta.persistence.Table;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.core.SecurityContext;
import java.awt.Color;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import lombok.SneakyThrows;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
Expand Down Expand Up @@ -154,16 +154,17 @@ public boolean getBlocked() {
return Block.count("source = ?1 and target = ?2", currentUser, this) > 0;
}

@Schema(required = true, pattern = "^#[A-F0-9]{6}$")
public String getTint() {
final var crc = new CRC32();
crc.update(username.getBytes());
final var hue = (float) (crc.getValue() / Math.pow(2, 32));
@SneakyThrows
@Schema(required = true)
public Color getTint() {
final var md5 = MessageDigest.getInstance("MD5");
final var digest = md5.digest(username.getBytes());
final var hue = bytesToFloat(digest);
final var h = hue * 6;
final var variance = (h - (float) Math.floor(h)) * 0.25f;
final var brightness = (int) h % 2 == 0 ? 1f - variance : 0.75f + variance;
final var color = Color.HSBtoRGB(hue, 0.5f, brightness);
return "#%02X%02X%02X".formatted((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
final var variance = Math.abs(h - (float) Math.round(h)) * 0.15f;
final var brightness = Math.round(h) % 2 == 0 ? 0.75f - variance : 0.6f + variance;
final var color = java.awt.Color.HSBtoRGB(hue, 0.5f, brightness);
return new Color((color >> 2 * Byte.SIZE) & 0xFF, (color >> Byte.SIZE) & 0xFF, color & 0xFF);
}

@Override
Expand Down Expand Up @@ -261,6 +262,17 @@ public static User getFromSecurityContext(final SecurityContext context, @Nullab
return user;
}

private static float bytesToFloat(byte[] bytes) {
final var bytesToUse = Math.min(bytes.length, Long.BYTES);
var result = 0L;

for (var i = 0; i < bytesToUse; i++) {
result = (result << Byte.SIZE) | (bytes[i] & 0xFF);
}

return (float) Math.abs((double) result / Long.MAX_VALUE);
}

public enum Rank {
CITIZEN,
MODERATOR,
Expand All @@ -277,5 +289,5 @@ public record Profile(
@Schema(required = true) UUID id,
@Schema(required = true) String username,
@Schema(required = true) String avatar,
@Schema(required = true) String tint) {}
@Schema(required = true) Color tint) {}
}
20 changes: 17 additions & 3 deletions src/main/java/app/fyreplace/api/data/dev/DataSeeder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.fyreplace.api.data.dev;

import static java.util.Objects.requireNonNull;
import static java.util.stream.IntStream.range;

import app.fyreplace.api.data.Block;
Expand All @@ -19,6 +20,7 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.transaction.Transactional;
import java.io.IOException;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@SuppressWarnings({"UnusedReturnValue", "unused"})
Expand All @@ -44,8 +46,8 @@ public void onShutdown(@Observes final ShutdownEvent event) {

@Transactional
public void insertData() {
range(0, 20).forEach(i -> createUser("user_" + i, true));
range(0, 10).forEach(i -> createUser("user_inactive_" + i, false));
range(0, 20).forEach(i -> createUser("user_" + i, true, i % 2 == 0));
range(0, 10).forEach(i -> createUser("user_inactive_" + i, false, false));
final var user = User.findByUsername("user_0");
range(0, 20).forEach(i -> createPost(user, "Post " + i, true, false));
range(0, 20).forEach(i -> createPost(user, "Draft " + i, false, false));
Expand Down Expand Up @@ -74,12 +76,24 @@ public void deleteData() {
}

@Transactional(Transactional.TxType.REQUIRES_NEW)
public User createUser(final String username, final boolean active) {
public User createUser(final String username, final boolean active, final boolean hasAvatar) {
final var user = new User();
user.username = username;
user.active = active;
user.persist();

if (hasAvatar) {
try (final var stream = getClass().getResourceAsStream("/META-INF/resources/images/logo-maskable.png")) {
final var storedFile = new StoredFile(
"avatars", user.username, requireNonNull(stream).readAllBytes());
storedFile.persist();
user.avatar = storedFile;
user.persist();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

final var email = new Email();
email.user = user;
email.email = username + "@example.org";
Expand Down
19 changes: 14 additions & 5 deletions src/main/java/app/fyreplace/api/emails/EmailBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import io.quarkus.mailer.Mailer;
import io.quarkus.qute.TemplateInstance;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
import java.net.URI;
import java.time.Instant;
import java.time.ZoneId;
Expand All @@ -24,9 +26,6 @@ public abstract class EmailBase extends Mail {
@ConfigProperty(name = "app.name")
String appName;

@ConfigProperty(name = "app.url")
URI appUrl;

@ConfigProperty(name = "app.front.url")
URI appFrontUrl;

Expand All @@ -45,6 +44,9 @@ public abstract class EmailBase extends Mail {
@Inject
LocaleService localeService;

@Context
UriInfo uriInfo;

private boolean customDeepLinks;

private String randomCodeClearText;
Expand Down Expand Up @@ -85,8 +87,9 @@ protected String getRandomCode() {
protected String getLink() {
return UriBuilder.fromUri(appFrontUrl.toString())
.scheme(customDeepLinks ? appFrontCustomScheme : appFrontUrl.getScheme())
.path(email.user.active ? "/login" : "/register")
.queryParam("action", action())
.fragment(email.email + ':' + getRandomCode())
.fragment(getRandomCode())
.build()
.toString();
}
Expand All @@ -96,9 +99,11 @@ protected ResourceBundle getResourceBundle() {
}

protected TemplateCommonData getTemplateCommonData() {
return new TemplateCommonData(getResourceBundle(), appName, appUrl, appWebsiteUrl, getRandomCode(), getLink());
return new TemplateCommonData(
getResourceBundle(), appName, uriInfo.getBaseUri(), appWebsiteUrl, getRandomCode(), getLink());
}

@SuppressWarnings("unused")
public static final class TemplateCommonData {
public final ResourceBundle res;
public final String appName;
Expand All @@ -117,5 +122,9 @@ private TemplateCommonData(
this.code = code;
this.link = link;
}

public URI makeAppUrl(String path) {
return UriBuilder.fromUri(appUrl).path(path).build();
}
}
}
3 changes: 0 additions & 3 deletions src/main/java/app/fyreplace/api/endpoints/DevEndpoint.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package app.fyreplace.api.endpoints;

import app.fyreplace.api.cache.DuplicateRequestKeyGenerator;
import app.fyreplace.api.data.User;
import app.fyreplace.api.services.JwtService;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.cache.CacheResult;
import io.quarkus.elytron.security.common.BcryptUtil;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
Expand All @@ -20,7 +18,6 @@ public final class DevEndpoint {

@GET
@Path("users/{username}/token")
@CacheResult(cacheName = "requests", keyGenerator = DuplicateRequestKeyGenerator.class)
public String getUserToken(@PathParam("username") final String username) {
final var user = User.findByUsername(username);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,33 @@
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.RedirectionException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;

@Path("stored-files")
public final class StoredFilesEndpoint {
@ConfigProperty(name = "app.url")
URI appUrl;

@Inject
StorageService storageService;

@Inject
MimeTypeService mimeTypeService;

@Context
UriInfo uriInfo;

@GET
@Path("{path:.*}")
@Operation(hidden = true)
public Response getStoredFile(@PathParam("path") final String path) throws URISyntaxException {
final var requestUri = storageService.getUri(path);
final var pathUri = storageService.getUri(path);

if (!appUrl.getHost().equals(requestUri.getHost())) {
throw new RedirectionException(Status.SEE_OTHER, requestUri);
if (!uriInfo.getBaseUri().getHost().equals(pathUri.getHost())) {
throw new RedirectionException(Status.SEE_OTHER, pathUri);
}

try {
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/app/fyreplace/api/endpoints/TokensEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public final class TokensEndpoint {
@APIResponse(
responseCode = "201",
description = "Created",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(implementation = String.class)))
content =
@Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(implementation = String.class, format = "password")))
@APIResponse(responseCode = "400", description = "Bad Request")
@APIResponse(responseCode = "404", description = "Not Found")
@CacheResult(cacheName = "requests", keyGenerator = DuplicateRequestKeyGenerator.class)
Expand Down Expand Up @@ -85,7 +88,13 @@ public Response createToken(@NotNull @Valid final TokenCreation input) {
@GET
@Path("new")
@Authenticated
@APIResponse(responseCode = "200", description = "OK")
@APIResponse(
responseCode = "200",
description = "OK",
content =
@Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(implementation = String.class, format = "password")))
public String getNewToken() {
return jwtService.makeJwt(User.getFromSecurityContext(context));
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/app/fyreplace/api/services/MimeTypeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public String getMimeType(final byte[] data) throws IOException {

while (readers.hasNext()) {
final var reader = readers.next();
final var format = reader.getFormatName().toLowerCase();
final var format = reader.getFormatName().toUpperCase();

try {
return Arrays.stream(KnownFileType.values())
.filter(m -> m.name.equals(format))
.filter(m -> m.name().equals(format))
.findFirst()
.orElseThrow()
.mime;
Expand All @@ -39,7 +39,8 @@ public String getExtension(final byte[] data) {
.filter(m -> m.mime.equals(mime))
.findFirst()
.orElseThrow()
.name();
.name()
.toLowerCase();
} catch (final IOException | NoSuchElementException e) {
return "unknown";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package app.fyreplace.api.services.mimetype;

public enum KnownFileType {
JPEG("jpeg", "image/jpeg"),
PNG("png", "image/png"),
WEBP("webp", "image/webp");
JPEG("image/jpeg"),
PNG("image/png"),
WEBP("image/webp");

public final String name;
public final String mime;

KnownFileType(String name, String mime) {
this.name = name;
KnownFileType(String mime) {
this.mime = mime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
import app.fyreplace.api.endpoints.StoredFilesEndpoint;
import app.fyreplace.api.services.StorageService;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;
import java.net.URI;
import org.eclipse.microprofile.config.inject.ConfigProperty;

public abstract class LocalStorageServiceBase implements StorageService {
@ConfigProperty(name = "app.url")
URI appUrl;
@Context
UriInfo uriInfo;

@Override
public URI getUri(final String path) {
final var pathBase = StoredFilesEndpoint.class.getAnnotation(Path.class).value();
return UriBuilder.fromUri(appUrl).path(pathBase).path(path).build();
return uriInfo.getBaseUriBuilder().path(pathBase).path(path).build();
}
}
2 changes: 1 addition & 1 deletion src/main/resources/templates/_logo.mjml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<mj-divider border-color="#FF8243" padding="0 0 40px 0" />
<mj-image src="{d.appUrl}/images/logo.png" width="80px" padding-bottom="40px" />
<mj-image src="{d.makeAppUrl('/images/logo.png')}" width="80px" padding-bottom="40px" />
2 changes: 1 addition & 1 deletion src/main/resources/templates/_schema.mjml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@context": "https://schema.org",
"@type": "Organization",
"url": "{d.websiteUrl}",
"logo": "{d.appUrl}/images/logo-maskable.png",
"logo": "{d.makeAppUrl('/images/logo-maskable.png')}",
"name": "{d.appName}"
},
"potentialAction": {
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/app/fyreplace/api/testing/UserTestsBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class UserTestsBase extends TransactionalTestsBase {
@Override
public void beforeEach() {
super.beforeEach();
range(0, getActiveUserCount()).forEach(i -> dataSeeder.createUser("user_" + i, true));
range(0, getInactiveUserCount()).forEach(i -> dataSeeder.createUser("user_inactive_" + i, false));
range(0, getActiveUserCount()).forEach(i -> dataSeeder.createUser("user_" + i, true, false));
range(0, getInactiveUserCount()).forEach(i -> dataSeeder.createUser("user_inactive_" + i, false, false));
}

public int getActiveUserCount() {
Expand Down

0 comments on commit 6300922

Please sign in to comment.