Basic setup of MinIO with containers and appropriate services/components for image/file storage.

This commit is contained in:
Jesse Brault 2024-07-22 10:09:46 -05:00
parent 2c897d6d50
commit 4f46fce70a
17 changed files with 2202 additions and 0 deletions

View File

@ -61,10 +61,15 @@ dependencies {
implementation 'org.commonmark:commonmark:0.22.0'
implementation 'org.jsoup:jsoup:1.17.2'
implementation 'io.minio:minio:8.5.11'
compileOnly 'org.jetbrains:annotations:24.1.0'
// Custom testing
testRuntimeOnly 'com.h2database:h2'
testImplementation 'org.testcontainers:testcontainers:1.20.0'
testImplementation 'org.testcontainers:junit-jupiter:1.20.0'
testImplementation "org.testcontainers:minio:1.20.0"
testFixturesImplementation 'org.hamcrest:hamcrest:2.2'
}

View File

@ -0,0 +1,88 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserCreateException;
import app.mealsmadeeasy.api.user.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MinIOContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
import java.io.InputStream;
import static app.mealsmadeeasy.api.matchers.Matchers.isUser;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.notNullValue;
@Testcontainers
@SpringBootTest
public class S3ImageServiceTests {
@Container
private static final MinIOContainer container = new MinIOContainer(
DockerImageName.parse("minio/minio:latest")
);
@DynamicPropertySource
public static void minioProperties(DynamicPropertyRegistry registry) {
registry.add("app.mealsmadeeasy.api.minio.bucketName", () -> "test-bucket");
registry.add("app.mealsmadeeasy.api.minio.endpoint", container::getS3URL);
registry.add("app.mealsmadeeasy.api.minio.accessKey", container::getUserName);
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
}
@Autowired
private UserService userService;
@Autowired
private ImageService imageService;
private User createTestUser(String username) {
try {
return this.userService.createUser(username, username + "@test.com", "test");
} catch (UserCreateException e) {
throw new RuntimeException(e);
}
}
@Test
public void smokeScreen() {}
@Test
@DirtiesContext
public void simpleCreate() {
try (final InputStream hal9000 = S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg")) {
final User owner = this.createTestUser("imageOwner");
final Image image = this.imageService.create(
owner,
"HAL9000.svg",
hal9000,
"image/svg+xml",
27881L
);
assertThat(image.getOwner(), isUser(owner));
assertThat(image.getCreated(), is(notNullValue()));
assertThat(image.getModified(), is(nullValue()));
assertThat(image.getUserFilename(), is("HAL9000.svg"));
assertThat(image.getMimeType(), is("image/svg+xml"));
assertThat(image.getAlt(), is(nullValue()));
assertThat(image.getCaption(), is(nullValue()));
assertThat(image.getInternalUrl(), is(notNullValue()));
assertThat(image.isPublic(), is(false));
assertThat(image.getViewers(), is(empty()));
} catch (IOException | ImageException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,68 @@
package app.mealsmadeeasy.api.s3;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MinIOContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Testcontainers
public class MinioS3ManagerTests {
@Container
private static final MinIOContainer container = new MinIOContainer(
DockerImageName.parse("minio/minio:latest")
);
private MinioS3Manager s3Manager;
@BeforeEach
public void beforeEach() {
this.s3Manager = new MinioS3Manager();
this.s3Manager.setEndpoint(container.getS3URL());
this.s3Manager.setAccessKey(container.getUserName());
this.s3Manager.setSecretKey(container.getPassword());
}
@Test
public void smokeScreen() {}
@Test
public void simpleStore() {
try (final InputStream hal9000 = MinioS3ManagerTests.class.getResourceAsStream("HAL9000.svg")) {
final String result = this.s3Manager.store(
"test-images",
"HAL9000.svg",
"image/svg+xml",
hal9000,
27881L
);
assertEquals("HAL9000.svg", result);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
}
}
@Test
public void simpleDelete() {
try (final InputStream hal9000 = MinioS3ManagerTests.class.getResourceAsStream("HAL9000.svg")) {
final String objectName = this.s3Manager.store(
"test-images",
"HAL9000.svg",
"image/svg+xml",
hal9000,
27881L
);
this.s3Manager.delete("test-images", objectName);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
}
}
}

View File

@ -0,0 +1,741 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="256"
height="256"
id="svg2"
inkscape:version="0.48.2 r9819"
sodipodi:docname="HAL9000.svg">
<title
id="title3116">HAL9000</title>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1292"
id="namedview139"
showgrid="false"
inkscape:zoom="2.6074563"
inkscape:cx="94.488987"
inkscape:cy="146.92353"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
id="linearGradient4716">
<stop
id="stop4718"
style="stop-color:#0a1314;stop-opacity:1"
offset="0" />
<stop
id="stop4720"
style="stop-color:#0a1314;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4698">
<stop
id="stop4700"
style="stop-color:#424a4b;stop-opacity:1"
offset="0" />
<stop
id="stop4702"
style="stop-color:#424a4b;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4680">
<stop
id="stop4682"
style="stop-color:#0e191c;stop-opacity:1"
offset="0" />
<stop
id="stop4684"
style="stop-color:#0e191c;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4662">
<stop
id="stop4664"
style="stop-color:#f5f4f1;stop-opacity:1"
offset="0" />
<stop
id="stop4666"
style="stop-color:#f5f4f1;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4632">
<stop
id="stop4634"
style="stop-color:#626463;stop-opacity:1"
offset="0" />
<stop
id="stop4636"
style="stop-color:#626463;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4622">
<stop
id="stop4624"
style="stop-color:#b9b5b4;stop-opacity:1"
offset="0" />
<stop
id="stop4626"
style="stop-color:#9d9290;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4594">
<stop
id="stop4596"
style="stop-color:#473e3e;stop-opacity:1"
offset="0" />
<stop
id="stop4598"
style="stop-color:#473e3e;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4584">
<stop
id="stop4586"
style="stop-color:#b1aba9;stop-opacity:1"
offset="0" />
<stop
id="stop4588"
style="stop-color:#b1aba9;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4572">
<stop
id="stop4574"
style="stop-color:#e0d8d4;stop-opacity:1"
offset="0" />
<stop
id="stop4580"
style="stop-color:#e0d8d4;stop-opacity:0.45490196"
offset="0.67647064" />
<stop
id="stop4576"
style="stop-color:#e0d8d4;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4562">
<stop
id="stop4564"
style="stop-color:#cfcdc7;stop-opacity:1"
offset="0" />
<stop
id="stop4566"
style="stop-color:#cfcdc7;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4546">
<stop
id="stop4548"
style="stop-color:#444544;stop-opacity:1"
offset="0" />
<stop
id="stop4550"
style="stop-color:#706062;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4454">
<stop
id="stop4456"
style="stop-color:#bebbb6;stop-opacity:1"
offset="0" />
<stop
id="stop4458"
style="stop-color:#bebbb6;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4446">
<stop
id="stop4448"
style="stop-color:#8f908a;stop-opacity:1"
offset="0" />
<stop
id="stop4450"
style="stop-color:#8f908a;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4438">
<stop
id="stop4440"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4442"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4430">
<stop
id="stop4432"
style="stop-color:#ea3231;stop-opacity:1"
offset="0" />
<stop
id="stop4434"
style="stop-color:#ea3231;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3971">
<stop
id="stop3973"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3975"
style="stop-color:#d3070e;stop-opacity:1"
offset="0.36951563" />
<stop
id="stop3977"
style="stop-color:#c10914;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3862">
<stop
id="stop3864"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3866"
style="stop-color:#ea1117;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3798">
<stop
id="stop3800"
style="stop-color:#f8ee46;stop-opacity:1"
offset="0" />
<stop
id="stop3802"
style="stop-color:#d3321c;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3774">
<stop
id="stop3776"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3782"
style="stop-color:#cd0d14;stop-opacity:1"
offset="0.36951563" />
<stop
id="stop3778"
style="stop-color:#c10914;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
color-interpolation-filters="sRGB"
id="filter4426">
<feGaussianBlur
id="feGaussianBlur4428"
stdDeviation="0.79644532" />
</filter>
<filter
x="-0.014859335"
y="-0.10673607"
width="1.0297188"
height="1.2134721"
color-interpolation-filters="sRGB"
id="filter4487">
<feGaussianBlur
id="feGaussianBlur4489"
stdDeviation="0.49337636" />
</filter>
<filter
color-interpolation-filters="sRGB"
id="filter4511">
<feGaussianBlur
id="feGaussianBlur4513"
stdDeviation="0.36832783" />
</filter>
<radialGradient
cx="355.6181"
cy="263.47437"
r="214.64285"
fx="355.6181"
fy="263.47437"
id="radialGradient4735"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.49590542,0.02361451,-0.04756507,0.99886814,191.79733,-8.0995301)" />
<radialGradient
cx="688.20172"
cy="322.61343"
r="214.64285"
fx="688.20172"
fy="322.61343"
id="radialGradient4737"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.18891652,0.20859515,-0.74120407,0.67127976,623.54686,-26.528142)" />
<radialGradient
cx="-50.826534"
cy="568.55469"
r="214.64285"
fx="-50.826534"
fy="568.55469"
id="radialGradient4739"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24198071,-0.24251704,0.72109578,0.71950102,-205.95295,-86.121137)" />
<radialGradient
cx="143.2925"
cy="560.57587"
r="214.64285"
fx="143.2925"
fy="560.57587"
id="radialGradient4741"
xlink:href="#linearGradient4680"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.55737084,1.1227092,-1.0153425,0.5040684,651.47245,105.56632)" />
<radialGradient
cx="359.53653"
cy="680.74078"
r="214.64285"
fx="359.53653"
fy="680.74078"
id="radialGradient4743"
xlink:href="#linearGradient4698"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.59429926,-0.0196788,0.03309447,0.99945223,124.48576,7.5040537)" />
<radialGradient
cx="549.07318"
cy="531.27026"
r="214.64285"
fx="549.07318"
fy="531.27026"
id="radialGradient4745"
xlink:href="#linearGradient4716"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.28951144,0.61897854,-1.0261698,-0.47996685,1253.2096,446.39787)" />
<radialGradient
cx="137.3555"
cy="130.31177"
r="87.547356"
fx="137.3555"
fy="130.31177"
id="radialGradient4965"
xlink:href="#linearGradient3774"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="137.3555"
cy="130.31177"
r="87.547356"
fx="137.3555"
fy="130.31177"
id="radialGradient4967"
xlink:href="#linearGradient3971"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="126.875"
cy="125.125"
r="44.125"
fx="126.875"
fy="125.125"
id="radialGradient4969"
xlink:href="#linearGradient3862"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="493.20697"
cy="120.2355"
r="20.152544"
fx="493.20697"
fy="120.2355"
id="radialGradient4971"
xlink:href="#linearGradient3798"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.75621888,0,0,0.75621888,120.23455,29.311144)" />
<linearGradient
x1="59.75"
y1="853.86218"
x2="63.5"
y2="848.86218"
id="linearGradient4973"
xlink:href="#linearGradient4454"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="65.5"
y1="878.36218"
x2="89"
y2="879.86218"
id="linearGradient4975"
xlink:href="#linearGradient4438"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="159.09903"
y1="895.73804"
x2="155.03316"
y2="895.2077"
id="linearGradient4977"
xlink:href="#linearGradient4430"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="201.5"
y1="859.36218"
x2="192.5"
y2="865.86218"
id="linearGradient4979"
xlink:href="#linearGradient4446"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="565.67145"
cy="446.36511"
r="214.64285"
fx="565.67145"
fy="446.36511"
id="radialGradient4981"
xlink:href="#linearGradient4546"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.01141222,0.7200172,-0.98807046,-0.01566569,1003.895,64.801116)" />
<radialGradient
cx="525.8512"
cy="583.23352"
r="214.64285"
fx="525.8512"
fy="583.23352"
id="radialGradient4983"
xlink:href="#linearGradient4562"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.13019845,0.14665728,-0.25941753,-0.23030421,735.49435,657.5781)" />
<radialGradient
cx="338.53104"
cy="703.86841"
r="214.64285"
fx="338.53104"
fy="703.86841"
id="radialGradient4985"
xlink:href="#linearGradient4572"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.27995439,0.0091529,-0.01235873,-0.37800452,464.47395,950.87477)" />
<radialGradient
cx="241.97748"
cy="591.31604"
r="214.64285"
fx="241.97748"
fy="591.31604"
id="radialGradient4987"
xlink:href="#linearGradient4584"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.0653018,0.07876991,-0.51364189,0.42581899,480.05196,325.10195)" />
<radialGradient
cx="148.8054"
cy="479.24811"
r="214.64285"
fx="148.8054"
fy="479.24811"
id="radialGradient4989"
xlink:href="#linearGradient4594"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.03414964,0.69195514,-1.3162287,0.06495088,774.52389,345.15386)" />
<radialGradient
cx="361.88593"
cy="270.58835"
r="214.64285"
fx="361.88593"
fy="270.58835"
id="radialGradient4991"
xlink:href="#linearGradient4622"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.50253939,0.0288342,-0.05728614,0.9983578,195.52495,-9.9903306)" />
<radialGradient
cx="113.19639"
cy="362.84845"
r="214.64285"
fx="113.19639"
fy="362.84845"
id="radialGradient4993"
xlink:href="#linearGradient4632"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.08238352,0.11533689,-0.81373347,-0.58123819,490.52721,552.42839)" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>HAL9000</dc:title>
<dc:date></dc:date>
<dc:creator>
<cc:Agent>
<dc:title>MorningLemon</dc:title>
</cc:Agent>
</dc:creator>
<dc:language>German</dc:language>
<dc:subject>
<rdf:Bag>
<rdf:li>HAL</rdf:li>
<rdf:li>9000</rdf:li>
<rdf:li>HAL9000</rdf:li>
<rdf:li>robot</rdf:li>
<rdf:li>space</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>The famous red eye of HAL 9000 from Stanley Kubricks Film &quot;2001: A Space Odyssey&quot;.</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
transform="translate(0,-796.36218)"
id="layer1">
<g
transform="translate(-727,-21)"
id="g4726">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4724"
style="fill:#5d5f5f;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4642"
style="fill:url(#radialGradient4735);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4670"
style="fill:url(#radialGradient4737);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4674"
style="fill:url(#radialGradient4739);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4678"
style="fill:url(#radialGradient4741);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4688"
style="fill:url(#radialGradient4743);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4706"
style="fill:url(#radialGradient4745);fill-opacity:1;stroke:none" />
</g>
<g
id="g4924">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path3766"
style="fill:#706062;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4556"
style="fill:#767676;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4544"
style="fill:url(#radialGradient4981);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4560"
style="fill:url(#radialGradient4983);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4570"
style="fill:url(#radialGradient4985);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4582"
style="fill:url(#radialGradient4987);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4592"
style="fill:url(#radialGradient4989);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4602"
style="fill:url(#radialGradient4991);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4630"
style="fill:url(#radialGradient4993);fill-opacity:1;stroke:none" />
</g>
<g
transform="translate(-2.2167876e-7,-3.5831251e-6)"
id="g4879">
<g
id="g4800">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.51198087,0,0,0.51198087,-53.204651,683.07034)"
id="path3768"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
<g
id="g4785">
<path
d="m 224.15285,130.31177 a 86.797356,86.797356 0 1 1 -173.594706,0 86.797356,86.797356 0 1 1 173.594706,0 z"
transform="matrix(0.74941633,0,0,0.74941633,25.063546,826.70441)"
id="path3874"
style="opacity:0.38050316;fill:url(#radialGradient4965);fill-opacity:1;stroke:none" />
<path
d="m 224.15285,130.31177 a 86.797356,86.797356 0 1 1 -173.594706,0 86.797356,86.797356 0 1 1 173.594706,0 z"
transform="translate(-9.3554993,794.05041)"
id="path3772"
style="fill:url(#radialGradient4967);fill-opacity:1;stroke:none" />
<path
d="m 170.25,125.125 a 43.375,43.375 0 1 1 -86.75,0 43.375,43.375 0 1 1 86.75,0 z"
transform="translate(1.125,801.23718)"
id="path3860"
style="fill:url(#radialGradient4969);fill-opacity:1;stroke:#ef1d00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none;stroke-dashoffset:0" />
<path
d="m 513.35951,120.2355 a 20.152544,20.152544 0 1 1 -40.30508,0 20.152544,20.152544 0 1 1 40.30508,0 z"
transform="matrix(1.6843175,0,0,1.6843175,-702.71713,721.84743)"
id="path3796"
style="fill:url(#radialGradient4971);fill-opacity:1;stroke:none" />
<path
d="m 498.51029,117.9374 a 4.0658641,4.0658641 0 1 1 -8.13173,0 4.0658641,4.0658641 0 1 1 8.13173,0 z"
transform="matrix(0.86956522,0,0,0.86956522,-301.95168,821.80792)"
id="path3818"
style="fill:#f4f846;fill-opacity:1;stroke:none" />
</g>
</g>
<g
id="g4859">
<g
id="g4393"
style="filter:url(#filter4426)">
<path
d="m 98.20252,833.52304 2.12132,8.3085 c 15.36068,-5.88046 45.97986,-3.83259 61.87184,3.71231 l 1.59099,-8.3085 C 143.78528,828.55429 112.399,829.1646 98.20252,833.52304 z"
id="path4007"
style="fill:#c9c9bf;fill-opacity:1;stroke:none" />
<path
d="m 58.60454,849.25616 c -13.04121,17.05062 -17.38118,27.71714 -19.09189,37.29989 3.26274,-1.56268 4.80687,-4.56751 6.34116,-7.26083 6.11733,-9.48606 12.36426,-19.07676 21.05923,-25.44286 z"
id="path4009"
style="fill:url(#linearGradient4973);fill-opacity:1;stroke:none" />
<path
d="m 58.60454,877.71721 11.3137,-8.3085 9.54595,9.36916 -10.78338,4.5962 z"
id="path4011"
style="opacity:0.53773588;fill:url(#linearGradient4975);fill-opacity:1;stroke:none" />
<path
d="m 111.10722,853.49881 c 9.39243,-1.29788 19.13002,-0.86996 28.99137,0.17677 0.56932,2.02341 0.78297,4.40249 0.88389,6.89429 -9.25131,-0.68705 -18.50263,-1.334 -27.75394,-0.17677 -0.425,-2.01599 -1.22929,-4.41128 -2.12132,-6.89429 z"
id="path4013"
style="opacity:0.53773588;fill:#ffffff;fill-opacity:1;stroke:none" />
<path
d="m 178.81269,871.17648 -5.12652,6.54073 c 2.70117,0.95519 6.19764,2.10921 10.78337,3.53554 1.18577,-1.42148 2.82121,-3.29262 4.94975,-5.65686 -2.93649,-1.67282 -6.26255,-3.21578 -10.6066,-4.41941 z"
id="path4015"
style="fill:#816461;fill-opacity:1;stroke:none" />
<path
d="m 91.66178,896.98587 5.12652,-3.88909 2.2981,3.53554 -3.88909,3.18198 z"
id="path4017"
style="fill:#f15e4f;fill-opacity:1;stroke:none" />
<path
d="m 118.88539,882.31341 c 5.81338,-0.94098 11.07456,-0.22537 16.44023,0.17677 l -0.35355,3.18198 c -5.24438,-0.1614 -10.48875,-0.5541 -15.73313,0.17678 z"
id="path4019"
style="fill:#ec4e3e;fill-opacity:1;stroke:none" />
<path
d="m 119.76927,895.21811 c 7.32697,-1.53523 13.82111,-1.00484 19.26866,2.12132 l 1.41422,-2.82843 c -7.72213,-3.49102 -16.3628,-3.26623 -21.38998,-1.59099 z"
id="path4021"
style="fill:#e66044;fill-opacity:1;stroke:none" />
<path
d="m 160.25114,898.75364 2.12132,-3.0052 -4.94975,-2.2981 -2.65165,2.2981 z"
id="path4023"
style="fill:url(#linearGradient4977);fill-opacity:1;stroke:none" />
<path
d="m 155.30139,906.70859 c -2.27582,-3.53407 -2.72679,-5.37411 -4.77297,-7.07107 l -1.59099,1.06066 c 2.46956,2.24316 4.48363,4.82793 6.01041,7.77818 z"
id="path4025"
style="fill:#f74639;fill-opacity:1;stroke:none" />
<path
d="m 132.4972,901.40529 c -2.88735,-0.75674 -5.77471,-0.58908 -8.66206,-0.17678 l 0,-1.94454 c 2.70228,-0.8158 5.54755,-0.77368 8.48528,-0.17678 z"
id="path4027"
style="fill:#ef4d2b;fill-opacity:1;stroke:none" />
<path
d="m 108.63234,900.52141 c -3.03319,2.75901 -5.03926,5.90318 -6.18718,9.36916 l -1.06066,-1.06066 c 1.17722,-3.76435 2.99712,-7.35399 6.0104,-10.42982 z"
id="path4029"
style="fill:#eb5241;fill-opacity:1;stroke:none" />
<path
d="m 110.57688,908.47636 1.06067,-2.47488 -2.65166,-1.06066 -1.23743,2.47488 z"
id="path4031"
style="fill:#f7432e;fill-opacity:1;stroke:none" />
<path
d="m 146.99288,908.29958 1.23744,-1.94454 -2.12132,-1.23744 -1.76777,1.41421 z"
id="path4033"
style="fill:#f7432e;fill-opacity:1;stroke:none" />
<path
d="m 191.57094,855.65046 4.77297,-4.06587 c 8.44599,4.49145 14.50078,13.36678 20.15255,22.98097 l -2.47488,2.47488 c -7.3081,-7.48088 -14.67726,-14.83965 -22.45064,-21.38998 z"
id="path4035"
style="fill:url(#linearGradient4979);fill-opacity:1;stroke:none" />
</g>
<path
d="m 128,26.25 c -12.15497,0 -23.81896,2.153186 -34.625,6.0625 l 2.34375,0.15625 C 105.86477,29.075845 116.71287,28.25 128,28.25 c 15.02248,0 29.28979,2.249441 42.125,8.09375 L 173.0625,36.75 C 159.47932,30.02936 144.18114,26.25 128,26.25 z"
transform="translate(0,796.36218)"
id="path4462"
style="opacity:0.93081761;fill:#35373c;fill-opacity:1;stroke:none;filter:url(#filter4487)" />
<path
d="M 49.71875,63 C 46.008058,67.46453 42.663152,72.251252 39.75,77.3125 l 0.1875,0.03125 c 1.24315,-2.050869 3.554075,-4.055585 4.931758,-6.00967 1.73058,-2.454631 3.566499,-5.829368 5.505742,-8.11533 z"
transform="translate(0,796.36218)"
id="path4469"
style="opacity:0.39308178;fill:#bab7b2;fill-opacity:1;stroke:none;filter:url(#filter4511)" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,741 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="256"
height="256"
id="svg2"
inkscape:version="0.48.2 r9819"
sodipodi:docname="HAL9000.svg">
<title
id="title3116">HAL9000</title>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1292"
id="namedview139"
showgrid="false"
inkscape:zoom="2.6074563"
inkscape:cx="94.488987"
inkscape:cy="146.92353"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
id="linearGradient4716">
<stop
id="stop4718"
style="stop-color:#0a1314;stop-opacity:1"
offset="0" />
<stop
id="stop4720"
style="stop-color:#0a1314;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4698">
<stop
id="stop4700"
style="stop-color:#424a4b;stop-opacity:1"
offset="0" />
<stop
id="stop4702"
style="stop-color:#424a4b;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4680">
<stop
id="stop4682"
style="stop-color:#0e191c;stop-opacity:1"
offset="0" />
<stop
id="stop4684"
style="stop-color:#0e191c;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4662">
<stop
id="stop4664"
style="stop-color:#f5f4f1;stop-opacity:1"
offset="0" />
<stop
id="stop4666"
style="stop-color:#f5f4f1;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4632">
<stop
id="stop4634"
style="stop-color:#626463;stop-opacity:1"
offset="0" />
<stop
id="stop4636"
style="stop-color:#626463;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4622">
<stop
id="stop4624"
style="stop-color:#b9b5b4;stop-opacity:1"
offset="0" />
<stop
id="stop4626"
style="stop-color:#9d9290;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4594">
<stop
id="stop4596"
style="stop-color:#473e3e;stop-opacity:1"
offset="0" />
<stop
id="stop4598"
style="stop-color:#473e3e;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4584">
<stop
id="stop4586"
style="stop-color:#b1aba9;stop-opacity:1"
offset="0" />
<stop
id="stop4588"
style="stop-color:#b1aba9;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4572">
<stop
id="stop4574"
style="stop-color:#e0d8d4;stop-opacity:1"
offset="0" />
<stop
id="stop4580"
style="stop-color:#e0d8d4;stop-opacity:0.45490196"
offset="0.67647064" />
<stop
id="stop4576"
style="stop-color:#e0d8d4;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4562">
<stop
id="stop4564"
style="stop-color:#cfcdc7;stop-opacity:1"
offset="0" />
<stop
id="stop4566"
style="stop-color:#cfcdc7;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4546">
<stop
id="stop4548"
style="stop-color:#444544;stop-opacity:1"
offset="0" />
<stop
id="stop4550"
style="stop-color:#706062;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4454">
<stop
id="stop4456"
style="stop-color:#bebbb6;stop-opacity:1"
offset="0" />
<stop
id="stop4458"
style="stop-color:#bebbb6;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4446">
<stop
id="stop4448"
style="stop-color:#8f908a;stop-opacity:1"
offset="0" />
<stop
id="stop4450"
style="stop-color:#8f908a;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4438">
<stop
id="stop4440"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4442"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4430">
<stop
id="stop4432"
style="stop-color:#ea3231;stop-opacity:1"
offset="0" />
<stop
id="stop4434"
style="stop-color:#ea3231;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3971">
<stop
id="stop3973"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3975"
style="stop-color:#d3070e;stop-opacity:1"
offset="0.36951563" />
<stop
id="stop3977"
style="stop-color:#c10914;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3862">
<stop
id="stop3864"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3866"
style="stop-color:#ea1117;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3798">
<stop
id="stop3800"
style="stop-color:#f8ee46;stop-opacity:1"
offset="0" />
<stop
id="stop3802"
style="stop-color:#d3321c;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3774">
<stop
id="stop3776"
style="stop-color:#ea1117;stop-opacity:1"
offset="0" />
<stop
id="stop3782"
style="stop-color:#cd0d14;stop-opacity:1"
offset="0.36951563" />
<stop
id="stop3778"
style="stop-color:#c10914;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
color-interpolation-filters="sRGB"
id="filter4426">
<feGaussianBlur
id="feGaussianBlur4428"
stdDeviation="0.79644532" />
</filter>
<filter
x="-0.014859335"
y="-0.10673607"
width="1.0297188"
height="1.2134721"
color-interpolation-filters="sRGB"
id="filter4487">
<feGaussianBlur
id="feGaussianBlur4489"
stdDeviation="0.49337636" />
</filter>
<filter
color-interpolation-filters="sRGB"
id="filter4511">
<feGaussianBlur
id="feGaussianBlur4513"
stdDeviation="0.36832783" />
</filter>
<radialGradient
cx="355.6181"
cy="263.47437"
r="214.64285"
fx="355.6181"
fy="263.47437"
id="radialGradient4735"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.49590542,0.02361451,-0.04756507,0.99886814,191.79733,-8.0995301)" />
<radialGradient
cx="688.20172"
cy="322.61343"
r="214.64285"
fx="688.20172"
fy="322.61343"
id="radialGradient4737"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.18891652,0.20859515,-0.74120407,0.67127976,623.54686,-26.528142)" />
<radialGradient
cx="-50.826534"
cy="568.55469"
r="214.64285"
fx="-50.826534"
fy="568.55469"
id="radialGradient4739"
xlink:href="#linearGradient4662"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24198071,-0.24251704,0.72109578,0.71950102,-205.95295,-86.121137)" />
<radialGradient
cx="143.2925"
cy="560.57587"
r="214.64285"
fx="143.2925"
fy="560.57587"
id="radialGradient4741"
xlink:href="#linearGradient4680"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.55737084,1.1227092,-1.0153425,0.5040684,651.47245,105.56632)" />
<radialGradient
cx="359.53653"
cy="680.74078"
r="214.64285"
fx="359.53653"
fy="680.74078"
id="radialGradient4743"
xlink:href="#linearGradient4698"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.59429926,-0.0196788,0.03309447,0.99945223,124.48576,7.5040537)" />
<radialGradient
cx="549.07318"
cy="531.27026"
r="214.64285"
fx="549.07318"
fy="531.27026"
id="radialGradient4745"
xlink:href="#linearGradient4716"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.28951144,0.61897854,-1.0261698,-0.47996685,1253.2096,446.39787)" />
<radialGradient
cx="137.3555"
cy="130.31177"
r="87.547356"
fx="137.3555"
fy="130.31177"
id="radialGradient4965"
xlink:href="#linearGradient3774"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="137.3555"
cy="130.31177"
r="87.547356"
fx="137.3555"
fy="130.31177"
id="radialGradient4967"
xlink:href="#linearGradient3971"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="126.875"
cy="125.125"
r="44.125"
fx="126.875"
fy="125.125"
id="radialGradient4969"
xlink:href="#linearGradient3862"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="493.20697"
cy="120.2355"
r="20.152544"
fx="493.20697"
fy="120.2355"
id="radialGradient4971"
xlink:href="#linearGradient3798"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.75621888,0,0,0.75621888,120.23455,29.311144)" />
<linearGradient
x1="59.75"
y1="853.86218"
x2="63.5"
y2="848.86218"
id="linearGradient4973"
xlink:href="#linearGradient4454"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="65.5"
y1="878.36218"
x2="89"
y2="879.86218"
id="linearGradient4975"
xlink:href="#linearGradient4438"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="159.09903"
y1="895.73804"
x2="155.03316"
y2="895.2077"
id="linearGradient4977"
xlink:href="#linearGradient4430"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="201.5"
y1="859.36218"
x2="192.5"
y2="865.86218"
id="linearGradient4979"
xlink:href="#linearGradient4446"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="565.67145"
cy="446.36511"
r="214.64285"
fx="565.67145"
fy="446.36511"
id="radialGradient4981"
xlink:href="#linearGradient4546"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.01141222,0.7200172,-0.98807046,-0.01566569,1003.895,64.801116)" />
<radialGradient
cx="525.8512"
cy="583.23352"
r="214.64285"
fx="525.8512"
fy="583.23352"
id="radialGradient4983"
xlink:href="#linearGradient4562"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.13019845,0.14665728,-0.25941753,-0.23030421,735.49435,657.5781)" />
<radialGradient
cx="338.53104"
cy="703.86841"
r="214.64285"
fx="338.53104"
fy="703.86841"
id="radialGradient4985"
xlink:href="#linearGradient4572"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.27995439,0.0091529,-0.01235873,-0.37800452,464.47395,950.87477)" />
<radialGradient
cx="241.97748"
cy="591.31604"
r="214.64285"
fx="241.97748"
fy="591.31604"
id="radialGradient4987"
xlink:href="#linearGradient4584"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.0653018,0.07876991,-0.51364189,0.42581899,480.05196,325.10195)" />
<radialGradient
cx="148.8054"
cy="479.24811"
r="214.64285"
fx="148.8054"
fy="479.24811"
id="radialGradient4989"
xlink:href="#linearGradient4594"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.03414964,0.69195514,-1.3162287,0.06495088,774.52389,345.15386)" />
<radialGradient
cx="361.88593"
cy="270.58835"
r="214.64285"
fx="361.88593"
fy="270.58835"
id="radialGradient4991"
xlink:href="#linearGradient4622"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.50253939,0.0288342,-0.05728614,0.9983578,195.52495,-9.9903306)" />
<radialGradient
cx="113.19639"
cy="362.84845"
r="214.64285"
fx="113.19639"
fy="362.84845"
id="radialGradient4993"
xlink:href="#linearGradient4632"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.08238352,0.11533689,-0.81373347,-0.58123819,490.52721,552.42839)" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>HAL9000</dc:title>
<dc:date></dc:date>
<dc:creator>
<cc:Agent>
<dc:title>MorningLemon</dc:title>
</cc:Agent>
</dc:creator>
<dc:language>German</dc:language>
<dc:subject>
<rdf:Bag>
<rdf:li>HAL</rdf:li>
<rdf:li>9000</rdf:li>
<rdf:li>HAL9000</rdf:li>
<rdf:li>robot</rdf:li>
<rdf:li>space</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>The famous red eye of HAL 9000 from Stanley Kubricks Film &quot;2001: A Space Odyssey&quot;.</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
transform="translate(0,-796.36218)"
id="layer1">
<g
transform="translate(-727,-21)"
id="g4726">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4724"
style="fill:#5d5f5f;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4642"
style="fill:url(#radialGradient4735);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4670"
style="fill:url(#radialGradient4737);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4674"
style="fill:url(#radialGradient4739);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4678"
style="fill:url(#radialGradient4741);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4688"
style="fill:url(#radialGradient4743);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.59186864,0,0,0.59186864,645.52079,666.41997)"
id="path4706"
style="fill:url(#radialGradient4745);fill-opacity:1;stroke:none" />
</g>
<g
id="g4924">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path3766"
style="fill:#706062;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4556"
style="fill:#767676;fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4544"
style="fill:url(#radialGradient4981);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4560"
style="fill:url(#radialGradient4983);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4570"
style="fill:url(#radialGradient4985);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4582"
style="fill:url(#radialGradient4987);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4592"
style="fill:url(#radialGradient4989);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4602"
style="fill:url(#radialGradient4991);fill-opacity:1;stroke:none" />
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.56551391,0,0,0.56551391,-72.151523,657.84071)"
id="path4630"
style="fill:url(#radialGradient4993);fill-opacity:1;stroke:none" />
</g>
<g
transform="translate(-2.2167876e-7,-3.5831251e-6)"
id="g4879">
<g
id="g4800">
<path
d="m 568.57141,471.29074 a 214.64285,214.64285 0 1 1 -429.2857,0 214.64285,214.64285 0 1 1 429.2857,0 z"
transform="matrix(0.51198087,0,0,0.51198087,-53.204651,683.07034)"
id="path3768"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
<g
id="g4785">
<path
d="m 224.15285,130.31177 a 86.797356,86.797356 0 1 1 -173.594706,0 86.797356,86.797356 0 1 1 173.594706,0 z"
transform="matrix(0.74941633,0,0,0.74941633,25.063546,826.70441)"
id="path3874"
style="opacity:0.38050316;fill:url(#radialGradient4965);fill-opacity:1;stroke:none" />
<path
d="m 224.15285,130.31177 a 86.797356,86.797356 0 1 1 -173.594706,0 86.797356,86.797356 0 1 1 173.594706,0 z"
transform="translate(-9.3554993,794.05041)"
id="path3772"
style="fill:url(#radialGradient4967);fill-opacity:1;stroke:none" />
<path
d="m 170.25,125.125 a 43.375,43.375 0 1 1 -86.75,0 43.375,43.375 0 1 1 86.75,0 z"
transform="translate(1.125,801.23718)"
id="path3860"
style="fill:url(#radialGradient4969);fill-opacity:1;stroke:#ef1d00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none;stroke-dashoffset:0" />
<path
d="m 513.35951,120.2355 a 20.152544,20.152544 0 1 1 -40.30508,0 20.152544,20.152544 0 1 1 40.30508,0 z"
transform="matrix(1.6843175,0,0,1.6843175,-702.71713,721.84743)"
id="path3796"
style="fill:url(#radialGradient4971);fill-opacity:1;stroke:none" />
<path
d="m 498.51029,117.9374 a 4.0658641,4.0658641 0 1 1 -8.13173,0 4.0658641,4.0658641 0 1 1 8.13173,0 z"
transform="matrix(0.86956522,0,0,0.86956522,-301.95168,821.80792)"
id="path3818"
style="fill:#f4f846;fill-opacity:1;stroke:none" />
</g>
</g>
<g
id="g4859">
<g
id="g4393"
style="filter:url(#filter4426)">
<path
d="m 98.20252,833.52304 2.12132,8.3085 c 15.36068,-5.88046 45.97986,-3.83259 61.87184,3.71231 l 1.59099,-8.3085 C 143.78528,828.55429 112.399,829.1646 98.20252,833.52304 z"
id="path4007"
style="fill:#c9c9bf;fill-opacity:1;stroke:none" />
<path
d="m 58.60454,849.25616 c -13.04121,17.05062 -17.38118,27.71714 -19.09189,37.29989 3.26274,-1.56268 4.80687,-4.56751 6.34116,-7.26083 6.11733,-9.48606 12.36426,-19.07676 21.05923,-25.44286 z"
id="path4009"
style="fill:url(#linearGradient4973);fill-opacity:1;stroke:none" />
<path
d="m 58.60454,877.71721 11.3137,-8.3085 9.54595,9.36916 -10.78338,4.5962 z"
id="path4011"
style="opacity:0.53773588;fill:url(#linearGradient4975);fill-opacity:1;stroke:none" />
<path
d="m 111.10722,853.49881 c 9.39243,-1.29788 19.13002,-0.86996 28.99137,0.17677 0.56932,2.02341 0.78297,4.40249 0.88389,6.89429 -9.25131,-0.68705 -18.50263,-1.334 -27.75394,-0.17677 -0.425,-2.01599 -1.22929,-4.41128 -2.12132,-6.89429 z"
id="path4013"
style="opacity:0.53773588;fill:#ffffff;fill-opacity:1;stroke:none" />
<path
d="m 178.81269,871.17648 -5.12652,6.54073 c 2.70117,0.95519 6.19764,2.10921 10.78337,3.53554 1.18577,-1.42148 2.82121,-3.29262 4.94975,-5.65686 -2.93649,-1.67282 -6.26255,-3.21578 -10.6066,-4.41941 z"
id="path4015"
style="fill:#816461;fill-opacity:1;stroke:none" />
<path
d="m 91.66178,896.98587 5.12652,-3.88909 2.2981,3.53554 -3.88909,3.18198 z"
id="path4017"
style="fill:#f15e4f;fill-opacity:1;stroke:none" />
<path
d="m 118.88539,882.31341 c 5.81338,-0.94098 11.07456,-0.22537 16.44023,0.17677 l -0.35355,3.18198 c -5.24438,-0.1614 -10.48875,-0.5541 -15.73313,0.17678 z"
id="path4019"
style="fill:#ec4e3e;fill-opacity:1;stroke:none" />
<path
d="m 119.76927,895.21811 c 7.32697,-1.53523 13.82111,-1.00484 19.26866,2.12132 l 1.41422,-2.82843 c -7.72213,-3.49102 -16.3628,-3.26623 -21.38998,-1.59099 z"
id="path4021"
style="fill:#e66044;fill-opacity:1;stroke:none" />
<path
d="m 160.25114,898.75364 2.12132,-3.0052 -4.94975,-2.2981 -2.65165,2.2981 z"
id="path4023"
style="fill:url(#linearGradient4977);fill-opacity:1;stroke:none" />
<path
d="m 155.30139,906.70859 c -2.27582,-3.53407 -2.72679,-5.37411 -4.77297,-7.07107 l -1.59099,1.06066 c 2.46956,2.24316 4.48363,4.82793 6.01041,7.77818 z"
id="path4025"
style="fill:#f74639;fill-opacity:1;stroke:none" />
<path
d="m 132.4972,901.40529 c -2.88735,-0.75674 -5.77471,-0.58908 -8.66206,-0.17678 l 0,-1.94454 c 2.70228,-0.8158 5.54755,-0.77368 8.48528,-0.17678 z"
id="path4027"
style="fill:#ef4d2b;fill-opacity:1;stroke:none" />
<path
d="m 108.63234,900.52141 c -3.03319,2.75901 -5.03926,5.90318 -6.18718,9.36916 l -1.06066,-1.06066 c 1.17722,-3.76435 2.99712,-7.35399 6.0104,-10.42982 z"
id="path4029"
style="fill:#eb5241;fill-opacity:1;stroke:none" />
<path
d="m 110.57688,908.47636 1.06067,-2.47488 -2.65166,-1.06066 -1.23743,2.47488 z"
id="path4031"
style="fill:#f7432e;fill-opacity:1;stroke:none" />
<path
d="m 146.99288,908.29958 1.23744,-1.94454 -2.12132,-1.23744 -1.76777,1.41421 z"
id="path4033"
style="fill:#f7432e;fill-opacity:1;stroke:none" />
<path
d="m 191.57094,855.65046 4.77297,-4.06587 c 8.44599,4.49145 14.50078,13.36678 20.15255,22.98097 l -2.47488,2.47488 c -7.3081,-7.48088 -14.67726,-14.83965 -22.45064,-21.38998 z"
id="path4035"
style="fill:url(#linearGradient4979);fill-opacity:1;stroke:none" />
</g>
<path
d="m 128,26.25 c -12.15497,0 -23.81896,2.153186 -34.625,6.0625 l 2.34375,0.15625 C 105.86477,29.075845 116.71287,28.25 128,28.25 c 15.02248,0 29.28979,2.249441 42.125,8.09375 L 173.0625,36.75 C 159.47932,30.02936 144.18114,26.25 128,26.25 z"
transform="translate(0,796.36218)"
id="path4462"
style="opacity:0.93081761;fill:#35373c;fill-opacity:1;stroke:none;filter:url(#filter4487)" />
<path
d="M 49.71875,63 C 46.008058,67.46453 42.663152,72.251252 39.75,77.3125 l 0.1875,0.03125 c 1.24315,-2.050869 3.554075,-4.055585 4.931758,-6.00967 1.73058,-2.454631 3.566499,-5.829368 5.505742,-8.11533 z"
transform="translate(0,796.36218)"
id="path4469"
style="opacity:0.39308178;fill:#bab7b2;fill-opacity:1;stroke:none;filter:url(#filter4511)" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -4,3 +4,6 @@ spring.datasource.username=sa
spring.datasource.password=sa
app.mealsmadeeasy.api.security.access-token-lifetime=60
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000
app.mealsmadeeasy.api.minio.accessKey=minio-root
app.mealsmadeeasy.api.minio.secretKey=test0123

View File

@ -0,0 +1,23 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserEntity;
import org.jetbrains.annotations.Nullable;
import java.time.LocalDateTime;
import java.util.Set;
public interface Image {
Long getId();
LocalDateTime getCreated();
@Nullable LocalDateTime getModified();
String getUserFilename();
String getMimeType();
@Nullable String getAlt();
@Nullable String getCaption();
String getObjectName();
String getInternalUrl();
User getOwner();
boolean isPublic();
Set<UserEntity> getViewers();
}

View File

@ -0,0 +1,164 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserEntity;
import jakarta.persistence.*;
import org.jetbrains.annotations.Nullable;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity(name = "Image")
public class ImageEntity implements Image {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false, updatable = false)
private Long id;
@Column(nullable = false)
private LocalDateTime created = LocalDateTime.now();
private LocalDateTime modified;
@Column(nullable = false)
private String userFilename;
@Column(nullable = false)
private String mimeType;
private String alt;
private String caption;
@Column(nullable = false)
private String objectName;
@Column(nullable = false)
private String internalUrl;
@ManyToOne(optional = false)
@JoinColumn(name = "owner_id", nullable = false)
private UserEntity owner;
@Column(nullable = false)
private Boolean isPublic = false;
@ManyToMany
private Set<UserEntity> viewers = new HashSet<>();
@Override
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public LocalDateTime getCreated() {
return this.created;
}
public void setCreated(LocalDateTime created) {
this.created = created;
}
@Override
public @Nullable LocalDateTime getModified() {
return this.modified;
}
public void setModified(LocalDateTime modified) {
this.modified = modified;
}
@Override
public String getUserFilename() {
return this.userFilename;
}
public void setUserFilename(String userFilename) {
this.userFilename = userFilename;
}
@Override
public String getMimeType() {
return this.mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public @Nullable String getAlt() {
return this.alt;
}
public void setAlt(String alt) {
this.alt = alt;
}
@Override
public @Nullable String getCaption() {
return this.caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
@Override
public String getObjectName() {
return this.objectName;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
@Override
public String getInternalUrl() {
return this.internalUrl;
}
public void setInternalUrl(String internalUrl) {
this.internalUrl = internalUrl;
}
@Override
public User getOwner() {
return this.owner;
}
public void setOwner(UserEntity owner) {
this.owner = owner;
}
@Override
public boolean isPublic() {
return this.isPublic;
}
public void setPublic(Boolean aPublic) {
isPublic = aPublic;
}
@Override
public Set<UserEntity> getViewers() {
return this.viewers;
}
public void setViewers(Set<UserEntity> viewers) {
this.viewers = viewers;
}
@Override
public String toString() {
return "ImageEntity(" + this.id + ", " + this.internalUrl + ")";
}
}

View File

@ -0,0 +1,25 @@
package app.mealsmadeeasy.api.image;
public class ImageException extends Exception {
public enum Type {
INVALID_ID, UNKNOWN_MIME_TYPE
}
private final Type type;
public ImageException(Type type, String message, Throwable cause) {
super(message, cause);
this.type = type;
}
public ImageException(Type type, String message) {
super(message);
this.type = type;
}
public Type getType() {
return type;
}
}

View File

@ -0,0 +1,13 @@
package app.mealsmadeeasy.api.image;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface ImageRepository extends JpaRepository<ImageEntity, Long> {
@Query("SELECT image FROM Image image WHERE image.id = ?1")
@EntityGraph(attributePaths = { "viewers" })
ImageEntity getByIdWithViewers(long id);
}

View File

@ -0,0 +1,9 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Nullable;
public interface ImageSecurity {
boolean isViewableBy(Image image, @Nullable User viewer);
boolean isOwner(Image image, @Nullable User user);
}

View File

@ -0,0 +1,47 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component("imageSecurity")
public class ImageSecurityImpl implements ImageSecurity {
private final ImageRepository imageRepository;
public ImageSecurityImpl(ImageRepository imageRepository) {
this.imageRepository = imageRepository;
}
@Override
public boolean isViewableBy(Image image, @Nullable User viewer) {
if (image.isPublic()) {
// public image
return true;
} else if (viewer == null) {
// non-public and no principal
return false;
} else if (Objects.equals(image.getOwner().getId(), viewer.getId())) {
// is owner
return true;
} else {
// check if viewer
final ImageEntity withViewers = this.imageRepository.getByIdWithViewers(image.getId());
for (final User user : withViewers.getViewers()) {
if (user.getId() != null && user.getId().equals(viewer.getId())) {
return true;
}
}
}
// non-public and not viewer
return false;
}
@Override
public boolean isOwner(Image image, @Nullable User user) {
return image.getOwner() != null && user != null && Objects.equals(image.getOwner().getId(), user.getId());
}
}

View File

@ -0,0 +1,28 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.user.User;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public interface ImageService {
Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize)
throws IOException, ImageException;
Image getById(long id);
Image getById(long id, User viewer);
List<Image> getImagesOwnedBy(User user);
Image updateOwner(Image image, User oldOwner, User newOwner);
Image setAlt(Image image, User owner, String alt);
Image setCaption(Image image, User owner, String caption);
Image setPublic(Image image, User owner, boolean isPublic);
void deleteImage(Image image, User owner) throws IOException;
void deleteById(long id, User owner) throws IOException;
}

View File

@ -0,0 +1,103 @@
package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.s3.S3Manager;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserEntity;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
@Service
public class S3ImageService implements ImageService {
private final S3Manager s3Manager;
private final ImageRepository imageRepository;
public S3ImageService(S3Manager s3Manager, ImageRepository imageRepository) {
this.s3Manager = s3Manager;
this.imageRepository = imageRepository;
}
private String getExtension(String mimeType) throws ImageException {
return switch (mimeType) {
case "image/svg+xml" -> "svg";
default -> throw new ImageException(
ImageException.Type.UNKNOWN_MIME_TYPE,
"unknown mime type: " + mimeType
);
};
}
@Override
public Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize)
throws IOException, ImageException {
final String uuid = UUID.randomUUID().toString();
final String extension = this.getExtension(mimeType);
final String filename = uuid + "." + extension;
final String objectName = this.s3Manager.store("images", filename, mimeType, inputStream, objectSize);
final ImageEntity draft = new ImageEntity();
draft.setOwner((UserEntity) owner);
draft.setUserFilename(userFilename);
draft.setMimeType(mimeType);
draft.setObjectName(objectName);
draft.setInternalUrl(this.s3Manager.getUrl("images", objectName));
return this.imageRepository.save(draft);
}
@Override
@PostAuthorize("returnObject.isPublic")
public Image getById(long id) {
return this.imageRepository.getReferenceById(id);
}
@Override
@PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)")
public Image getById(long id, User viewer) {
return this.imageRepository.getReferenceById(id);
}
@Override
public List<Image> getImagesOwnedBy(User user) {
return List.of();
}
@Override
public Image updateOwner(Image image, User oldOwner, User newOwner) {
return null;
}
@Override
public Image setAlt(Image image, User owner, String alt) {
return null;
}
@Override
public Image setCaption(Image image, User owner, String caption) {
return null;
}
@Override
public Image setPublic(Image image, User owner, boolean isPublic) {
return null;
}
@Override
@PreAuthorize("@imageSecurity.isOwner(image, owner)")
public void deleteImage(Image image, User owner) throws IOException {
this.imageRepository.delete((ImageEntity) image);
this.s3Manager.delete("images", image.getObjectName()); // TODO
}
@Override
public void deleteById(long id, User owner) throws IOException {
final ImageEntity toDelete = this.imageRepository.getReferenceById(id);
this.deleteImage(toDelete, owner);
}
}

View File

@ -0,0 +1,112 @@
package app.mealsmadeeasy.api.s3;
import io.minio.*;
import io.minio.errors.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@Component
public class MinioS3Manager implements S3Manager {
@Value("${app.mealsmadeeasy.api.minio.endpoint}")
private String endpoint;
@Value("${app.mealsmadeeasy.api.minio.accessKey}")
private String accessKey;
@Value("${app.mealsmadeeasy.api.minio.secretKey}")
private String secretKey;
@Override
public String store(
String bucketName,
String filename,
String mimeType,
InputStream inputStream,
long objectSize
) throws IOException {
try (final MinioClient client = MinioClient.builder()
.endpoint(this.endpoint)
.credentials(this.accessKey, this.secretKey)
.build()) {
final boolean bucketExists = client.bucketExists(BucketExistsArgs.builder()
.bucket(bucketName)
.build());
if (!bucketExists) {
client.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
}
final ObjectWriteResponse response = client.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.stream(inputStream, objectSize, -1)
.contentType(mimeType)
.object(filename)
.build()
);
return response.object();
} catch (ErrorResponseException | XmlParserException | InsufficientDataException | InternalException |
InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException | ServerException e) {
throw new IOException(e);
} catch (Exception minioBuildException) {
throw new RuntimeException(minioBuildException);
}
}
@Override
public void delete(String bucketName, String objectName) throws IOException {
try (final MinioClient client = MinioClient.builder()
.endpoint(this.endpoint)
.credentials(this.accessKey, this.secretKey)
.build()) {
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
InvalidResponseException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
throw new IOException(e);
} catch (Exception minioBuildException) {
throw new RuntimeException(minioBuildException);
}
}
@Override
public String getUrl(String bucketName, String objectName) {
return this.endpoint + "/" + bucketName + "/" + objectName;
}
public String getEndpoint() {
return this.endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return this.accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return this.secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}

View File

@ -0,0 +1,29 @@
package app.mealsmadeeasy.api.s3;
import java.io.IOException;
import java.io.InputStream;
public interface S3Manager {
/**
* @param bucket the target bucket in which to store the content
* @param filename the filename to store, usually a uuid + appropriate extension
* @param mimeType the mimeType of the content
* @param inputStream the content
* @param size the size of the content
* @return the object name
* @throws IOException if there is an exception with the backing service
*/
String store(
String bucket,
String filename,
String mimeType,
InputStream inputStream,
long size
) throws IOException;
void delete(String bucketName, String objectName) throws IOException;
String getUrl(String bucketName, String objectName);
}

View File

@ -6,3 +6,6 @@ spring.datasource.password=devpass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
app.mealsmadeeasy.api.security.access-token-lifetime=60
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000
app.mealsmadeeasy.api.minio.accessKey=minio-root
app.mealsmadeeasy.api.minio.secretKey=test0123