Skip to content

Commit 6af63bc

Browse files
authored
Merge pull request #91 from v79/90-prod-build-bugs
90 prod build bugs - fixed svelte build and image upload bugs, etc.
2 parents 6bb9ccc + 69e1f81 commit 6af63bc

File tree

7 files changed

+61
-42
lines changed

7 files changed

+61
-42
lines changed

API/src/main/kotlin/org/liamjd/cantilever/api/API.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class LambdaRouter : RequestHandlerWrapper() {
241241

242242
get(
243243
"/images",
244-
mediaController::getImages,
244+
mediaController::getImageList,
245245
).spec(Spec.PathItem("Get images", "Returns a list of all images"))
246246

247247
get("/images/$SRCKEY/{resolution}", mediaController::getImage).spec(

API/src/main/kotlin/org/liamjd/cantilever/api/controllers/MediaController.kt

+20-16
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class MediaController(sourceBucket: String, generationBucket: String) : KoinComp
2121
* Return a list of all the images in the content tree
2222
* @return [ImageListDTO] object containing the list of images, a count and the last updated date/time
2323
*/
24-
fun getImages(request: Request<Unit>): ResponseEntity<APIResult<ImageListDTO>> {
24+
fun getImageList(request: Request<Unit>): ResponseEntity<APIResult<ImageListDTO>> {
2525
val projectKeyHeader = request.headers["cantilever-project-domain"]!!
2626
return if (s3Service.objectExists(projectKeyHeader + "/" + S3_KEY.metadataKey, sourceBucket)) {
2727
loadContentTree(projectKeyHeader)
@@ -44,7 +44,7 @@ class MediaController(sourceBucket: String, generationBucket: String) : KoinComp
4444
}
4545

4646
/**
47-
* Load an image file with the specified `srcKey` and specified `resolution` and return it as [ImageDTO] response
47+
* Load an image file with the specified `srcKey` and specified `resolution` and return it as a byte array [ImageDTO] response
4848
* If resolution is not specified, return the original image
4949
*/
5050
@OptIn(ExperimentalEncodingApi::class)
@@ -58,25 +58,28 @@ class MediaController(sourceBucket: String, generationBucket: String) : KoinComp
5858
// srcKey will be /sources/images/<image-name>.<ext> so we need to strip off the /sources/images/ prefix and add the /generated/images/ prefix
5959
// I also need to move the <ext> to the end of the generated key
6060
val ext = decodedKey.substringAfterLast(".")
61-
val generatedKey = decodedKey.removeSuffix(".${ext}")
62-
.replace(S3_KEY.imagesPrefix, S3_KEY.generatedImagesPrefix) + if (resolution != null) {
61+
val generatedKey = decodedKey
62+
.replace("sources/images/", "generated/images/") + if (resolution != null) {
6363
"/${resolution}.$ext"
6464
} else {
6565
".${ext}"
6666
}
67-
val image = s3Service.getObjectAsBytes(generatedKey, sourceBucket)
68-
69-
// need to base64 encode the image
70-
val encoded = Base64.encode(image)
71-
return ResponseEntity.ok(
72-
APIResult.Success(
73-
value = ImageDTO(
74-
srcKey = srcKey,
75-
getContentTypeFromExtension(ext),
76-
bytes = encoded
67+
if(s3Service.objectExists(generatedKey, generationBucket)) {
68+
val image = s3Service.getObjectAsBytes(generatedKey, generationBucket)
69+
val encoded = Base64.encode(image)
70+
return ResponseEntity.ok(
71+
APIResult.Success(
72+
value = ImageDTO(
73+
srcKey = srcKey,
74+
getContentTypeFromExtension(ext),
75+
bytes = encoded
76+
)
7777
)
7878
)
79-
)
79+
} else {
80+
error("Image '$generatedKey' not found")
81+
return ResponseEntity.notFound(APIResult.Error("Image '$generatedKey' not found"))
82+
}
8083
}
8184

8285
/**
@@ -88,7 +91,7 @@ class MediaController(sourceBucket: String, generationBucket: String) : KoinComp
8891
loadContentTree(projectKeyHeader)
8992

9093
val imageBody = request.body
91-
val srcKey = "sources/images/${imageBody.srcKey}"
94+
val srcKey = "$projectKeyHeader/sources/images/${imageBody.srcKey}"
9295
val contentType = imageBody.contentType
9396
var dto: ImageDTO? = null
9497
try {
@@ -148,6 +151,7 @@ class MediaController(sourceBucket: String, generationBucket: String) : KoinComp
148151

149152
/**
150153
* Get the content type from the file extension
154+
* Defaults to image/jpeg if the extension is not recognised
151155
*/
152156
private fun getContentTypeFromExtension(extension: String): String {
153157
return when (extension) {

ImageProcessor/src/main/kotlin/org/liamjd/cantilever/lambda/image/ImageProcessorHandler.kt

+28-18
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
3030
init {
3131
s3Service = S3ServiceImpl(Region.EU_WEST_2)
3232
sqsService = SQSServiceImpl(Region.EU_WEST_2)
33-
3433
}
3534

3635
override fun handleRequest(event: SQSEvent, context: Context): String {
3736
val sourceBucket = System.getenv("source_bucket")
3837
val destinationBucket = System.getenv("destination_bucket")
38+
val generationBucket = System.getenv("generation_bucket")
3939
logger = context.logger
4040
processor = ImageProcessor(logger)
4141

@@ -44,20 +44,17 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
4444
logger.info("Received ${event.records.size} events received for image processing")
4545

4646
try {
47-
val projectYaml= s3Service.getObjectAsString(S3_KEY.projectKey, sourceBucket)
48-
val project = Yaml.default.decodeFromString(CantileverProject.serializer(), projectYaml)
49-
5047
event.records.forEach { eventRecord ->
5148
logger.info("Event record: ${eventRecord.body}")
5249

5350
when (val sqsMsg = Json.decodeFromString<ImageSQSMessage>(eventRecord.body)) {
5451
is ImageSQSMessage.ResizeImageMsg -> {
55-
response = processImageResize(sqsMsg, sourceBucket)
52+
response = processImageResize(sqsMsg, sourceBucket, generationBucket)
5653
}
5754

5855
is ImageSQSMessage.CopyImagesMsg -> {
59-
logger.info("Received request to copy images from source to destination bucket")
60-
response = processImageCopy(sqsMsg, project, sourceBucket, destinationBucket)
56+
logger.info("Received request to copy images from generation to destination bucket")
57+
response = processImageCopy(sqsMsg, generationBucket, destinationBucket)
6158
}
6259
}
6360
}
@@ -72,15 +69,17 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
7269
* Process the image resize message. For each resolution defined in the project metadata, create a new image based on the original
7370
* @param imageMessage the SQS message containing the image to resize
7471
* @param sourceBucket the bucket containing the image to resize
72+
* @param generationBucket the bucket to write the resized images to
7573
* @return a String response to the SQS message
7674
*/
7775
private fun processImageResize(
78-
imageMessage: ImageSQSMessage.ResizeImageMsg, sourceBucket: String
76+
imageMessage: ImageSQSMessage.ResizeImageMsg, sourceBucket: String, generationBucket: String
7977
): String {
8078
var responseString = "200 OK"
8179

8280
try {
83-
val projectString = s3Service.getObjectAsString(S3_KEY.projectKey, sourceBucket)
81+
val domain = imageMessage.projectDomain
82+
val projectString = s3Service.getObjectAsString("$domain.yaml", sourceBucket)
8483
val project = Yaml.default.decodeFromString(CantileverProject.serializer(), projectString)
8584
logger.info("Project: $project")
8685

@@ -102,16 +101,18 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
102101
val destKey = calculateFilename(imageMessage, name)
103102
logger.info("Resize image: writing $destKey (${resizedBytes.size} bytes) to $sourceBucket")
104103
s3Service.putObjectAsBytes(
105-
destKey, sourceBucket, resizedBytes, contentType ?: "image/jpeg"
104+
destKey, generationBucket, resizedBytes, contentType ?: "image/jpeg"
106105
)
107106
}
108107
}
109108
// finally, copy the original image to the generated folder, unchanged
110109
logger.info("Copying original image to generated folder")
110+
val copyToKey = "$domain/generated/images/${imageMessage.metadata.srcKey.substringAfterLast("/")}"
111111
s3Service.copyObject(
112112
imageMessage.metadata.srcKey,
113-
calculateFilename(imageMessage, null),
114-
sourceBucket
113+
copyToKey,
114+
sourceBucket,
115+
generationBucket
115116
)
116117
logger.info("Creating internal thumbnail 100x100")
117118
val thumbNailRes = ImgRes(100, 100)
@@ -120,7 +121,7 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
120121
val destKey = calculateFilename(imageMessage, S3_KEY.thumbnail)
121122
logger.info("Resize image: writing $destKey (${resizedBytes.size} bytes) to $sourceBucket")
122123
s3Service.putObjectAsBytes(
123-
destKey, sourceBucket, resizedBytes, contentType ?: "image/jpeg"
124+
destKey, generationBucket, resizedBytes, contentType ?: "image/jpeg"
124125
)
125126
} else {
126127
logger.error("Resize image: ${imageMessage.metadata.srcKey} is empty")
@@ -133,7 +134,6 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
133134
return "202 Accepted"
134135
}
135136

136-
137137
} catch (e: Exception) {
138138
logger.error("Failed to process image file; ${e.message}")
139139
responseString = "500 Internal Server Error"
@@ -143,10 +143,14 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
143143
}
144144

145145
// TODO: Need stronger error handling here; there's a good chance that the source image won't exist
146-
private fun processImageCopy(imageMessage: ImageSQSMessage.CopyImagesMsg, project: CantileverProject, sourceBucket: String, destinationBucket: String): String {
146+
private fun processImageCopy(imageMessage: ImageSQSMessage.CopyImagesMsg,sourceBucket: String, destinationBucket: String): String {
147147
var responseString = "200 OK"
148148

149149
try {
150+
// load the project
151+
val domain = imageMessage.projectDomain
152+
val projectString = s3Service.getObjectAsString("$domain/metadata.json", sourceBucket)
153+
val project = Yaml.default.decodeFromString(CantileverProject.serializer(), projectString)
150154
imageMessage.imageList.forEach { imageKey ->
151155
// imageKey will be as requested by the markdown file, e.g. /images/my-image.jpg
152156
// sourceKey will be the full path in the source bucket, e.g. generated/images/my-image.jpg
@@ -172,17 +176,23 @@ class ImageProcessorHandler : RequestHandler<SQSEvent, String> {
172176
/**
173177
* Calculate the filename for the resized image
174178
* Based on the original file name, and the resolution name
179+
* Will be in the format domain/generated/images/my-image.100x100.jpg
180+
* @param imageMessage the SQS message containing details of the image to resize
181+
* @param resName the name of the resolution to append to the filename
175182
*/
176183
private fun calculateFilename(
177184
imageMessage: ImageSQSMessage.ResizeImageMsg, resName: String?
178185
): String {
186+
// original image key is in the format domain/sources/images/my-image.jpg
187+
// destination image key should be in format domain/generated/images/my-image/100x100.jpg
188+
val domain = imageMessage.projectDomain
189+
val sourceLeafName = imageMessage.metadata.srcKey.substringAfterLast("/")
179190
val origSuffix = imageMessage.metadata.srcKey.substringAfterLast(".")
180-
val newPrefix = S3_KEY.generated + "/"
181-
val leafName = imageMessage.metadata.srcKey.removePrefix(S3_KEY.sourcesPrefix).substringBeforeLast(".")
191+
val newPrefix = "$domain/generated/images/"
182192
val finalResName = if (resName != null) {
183193
"/$resName."
184194
} else "."
185-
return "$newPrefix$leafName${finalResName}${origSuffix}"
195+
return "$newPrefix$sourceLeafName${finalResName}${origSuffix}"
186196
}
187197

188198
/**

WebEditor/src/lib/modals/createNewPostModal.svelte

+7-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@
3030
errorToast.message = 'Could not fetch templates. Create a "post" template first';
3131
toastStore.trigger(errorToast);
3232
templatesReady = false;
33-
templatesLoading = false;
33+
templatesLoading = false;
3434
} else {
3535
// templates loaded into store
3636
templatesReady = true;
3737
templatesLoading = false;
3838
}
3939
} else {
4040
// templates already loaded
41+
templatesReady = true;
42+
templatesLoading = false;
4143
}
4244
});
4345
@@ -64,7 +66,10 @@
6466
<footer class="modal-footer {parent.regionFooter}">
6567
<button class="btn {parent.buttonNeutral}" on:click={parent.onClose}
6668
>{parent.buttonTextCancel}</button>
67-
<button disabled={!templatesReady} class="btn variant-filled-primary" on:click={closeAndSubmit}>Create</button>
69+
<button
70+
disabled={!templatesReady}
71+
class="btn variant-filled-primary"
72+
on:click={closeAndSubmit}>Create</button>
6873
</footer>
6974
</div>
7075
{/if}

WebEditor/src/lib/stores/contentStore.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { TemplateNode } from '$lib/models/templates.svelte';
55
66
//@ts-ignore
7-
export const CLEAR_MARKDOWN = new MarkdownContent(null, '');
7+
export const CLEAR_MARKDOWN = new MarkdownContent(undefined, '');
88
export const CLEAR_HANDLEBARS = new TemplateNode('', new Date(), '', [], '');
99
1010
function createMarkdownStore() {
@@ -37,6 +37,6 @@
3737
clear: () => set(CLEAR_HANDLEBARS)
3838
};
3939
}
40-
/** This store manages the handlebars content for the editor, i.e the content of the current Template */
40+
// This store manages the handlebars content for the editor, i.e the content of the current Template
4141
export const handlebars = createHandlebarsStore();
4242
</script>

WebEditor/src/routes/pages/+page.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@
431431
<h3 class="h3 text-center mb-2">
432432
{#if pgTitle}{pgTitle}{/if}
433433
</h3>
434-
{#if $markdownStore.metadata}
434+
{#if $markdownStore.metadata instanceof PageItem}
435435
<div class="flex flex-row justify-end">
436436
<div class="btn-group variant-filled" role="group">
437437
<button

WebEditor/src/routes/posts/+page.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
}
143143
144144
function initiateNewPost() {
145-
let newPost = new MarkdownContent(
145+
let newPost = new MarkdownContent(
146146
new PostItem('', '', 'sources/templates/post.html.hbs', '', new Date(), new Date(), true),
147147
''
148148
);
@@ -270,7 +270,7 @@
270270
<h3 class="h3 text-center mb-2">
271271
{#if pgTitle}{pgTitle}{/if}
272272
</h3>
273-
{#if $markdownStore.metadata}
273+
{#if $markdownStore.metadata instanceof PostItem}
274274
<div class="flex flex-row justify-end">
275275
<div class="btn-group variant-filled" role="group">
276276
<button

0 commit comments

Comments
 (0)