diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts
index ad90b739..eafc5b36 100644
--- a/annotations/build.gradle.kts
+++ b/annotations/build.gradle.kts
@@ -3,4 +3,5 @@ plugins {
}
dependencies {
api("io.micronaut:micronaut-http")
+ api("io.micronaut.views:micronaut-views-core:5.3.0")
}
\ No newline at end of file
diff --git a/annotations/src/main/java/org/projectcheckins/annotations/GetHtml.java b/annotations/src/main/java/org/projectcheckins/annotations/GetHtml.java
index a43563e6..20ba7978 100644
--- a/annotations/src/main/java/org/projectcheckins/annotations/GetHtml.java
+++ b/annotations/src/main/java/org/projectcheckins/annotations/GetHtml.java
@@ -4,6 +4,7 @@
import io.micronaut.scheduling.TaskExecutors;
import java.lang.annotation.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import io.micronaut.views.turbo.TurboStreamAction;
@Documented
@Retention(RUNTIME)
@@ -19,4 +20,6 @@
boolean hidden() default true;
String executesOn() default TaskExecutors.BLOCKING;
+
+ String turboView() default "";
}
diff --git a/bootstrap/build.gradle.kts b/bootstrap/build.gradle.kts
index 69afe25e..adb08367 100644
--- a/bootstrap/build.gradle.kts
+++ b/bootstrap/build.gradle.kts
@@ -2,6 +2,6 @@ plugins {
id("org.projectcheckins.micronaut-modules-conventions")
}
dependencies {
- implementation("io.micronaut.views:micronaut-views-fieldset")
+ implementation("io.micronaut.views:micronaut-views-fieldset:5.3.0")
testImplementation(project(":test-utils"))
}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-http-modules-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-http-modules-conventions.gradle.kts
index f7973aac..5f508eae 100644
--- a/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-http-modules-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-http-modules-conventions.gradle.kts
@@ -11,9 +11,9 @@ dependencies {
implementation("io.micronaut:micronaut-http-server")
// Views
- implementation("io.micronaut.views:micronaut-views-fieldset")
- implementation("io.micronaut.views:micronaut-views-core")
- runtimeOnly("io.micronaut.views:micronaut-views-thymeleaf")
+ implementation("io.micronaut.views:micronaut-views-fieldset:5.3.0")
+ implementation("io.micronaut.views:micronaut-views-core:5.3.0")
+ runtimeOnly("io.micronaut.views:micronaut-views-thymeleaf:5.3.0")
// Test Server
testImplementation("io.micronaut:micronaut-http-server-netty")
diff --git a/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-modules-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-modules-conventions.gradle.kts
index 11a3e605..8dd25dfd 100644
--- a/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-modules-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/org.projectcheckins.micronaut-modules-conventions.gradle.kts
@@ -43,3 +43,11 @@ dependencies {
tasks.withType {
useJUnitPlatform()
}
+
+configurations.all {
+ resolutionStrategy.eachDependency {
+ if (requested.group == "io.micronaut.views") {
+ useVersion("5.3.0")
+ }
+ }
+}
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index a0477b6d..78fcca5d 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -5,8 +5,8 @@ dependencies {
api(project(":multitenancy"))
api(project(":security"))
api("io.micronaut.security:micronaut-security")
- api("io.micronaut.views:micronaut-views-core")
- api("io.micronaut.views:micronaut-views-fieldset")
+ api("io.micronaut.views:micronaut-views-core:5.3.0")
+ api("io.micronaut.views:micronaut-views-fieldset:5.3.0")
implementation("com.vladsch.flexmark:flexmark:${project.properties["flexmarkVersion"]}")
diff --git a/http/build.gradle.kts b/http/build.gradle.kts
index 16c42fdf..c708b382 100644
--- a/http/build.gradle.kts
+++ b/http/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
id("org.projectcheckins.micronaut-http-modules-conventions")
}
dependencies {
+ api(project(":security-http"))
api(project(":core"))
api(project(":bootstrap"))
implementation("io.micronaut:micronaut-management")
diff --git a/http/src/main/java/org/projectcheckins/http/controllers/AnswerController.java b/http/src/main/java/org/projectcheckins/http/controllers/AnswerController.java
index 1249fff4..5e07d072 100644
--- a/http/src/main/java/org/projectcheckins/http/controllers/AnswerController.java
+++ b/http/src/main/java/org/projectcheckins/http/controllers/AnswerController.java
@@ -15,6 +15,7 @@
import io.micronaut.views.fields.Form;
import io.micronaut.views.fields.FormGenerator;
import io.micronaut.views.fields.messages.Message;
+import io.micronaut.views.turbo.http.TurboMediaType;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
@@ -28,6 +29,8 @@
import org.projectcheckins.core.forms.*;
import org.projectcheckins.core.services.AnswerService;
import org.projectcheckins.core.services.QuestionService;
+import org.projectcheckins.security.http.TurboFrameUtils;
+import org.projectcheckins.security.http.TurboStreamUtils;
import java.net.URI;
import java.util.List;
@@ -39,6 +42,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static org.projectcheckins.http.controllers.ApiConstants.SLASH;
+
@Controller
class AnswerController {
private static final String ANSWER = "answer";
@@ -77,24 +82,30 @@ class AnswerController {
public static final BiFunction PATH_SHOW_URI_BUILDER = (questionId, id) -> UriBuilder.of(QuestionController.PATH).path(questionId).path(ANSWER).path(id).path(ApiConstants.ACTION_SHOW).build();
public static final Function PATH_SHOW_BUILDER = answer -> PATH_SHOW_URI_BUILDER.apply(answer.questionId(), answer.id());
private static final String VIEW_SHOW = ANSWER + ApiConstants.VIEW_SHOW;
+ public static final String VIEW_SHOW_FRAGMENT = ANSWER + SLASH + ApiConstants.FRAGMENT_SHOW;
// EDIT
private static final String PATH_EDIT = PATH + ApiConstants.PATH_EDIT;
private static final String VIEW_EDIT = ANSWER + ApiConstants.VIEW_EDIT;
+ public static final String VIEW_EDIT_FRAGMENT = ANSWER + SLASH + ApiConstants.FRAGMENT_EDIT;
private static final Breadcrumb BREADCRUMB_EDIT = new Breadcrumb(Message.of("Edit Answer", ANSWER + ApiConstants.DOT + ApiConstants.ACTION_EDIT));
private final QuestionService questionService;
private final AnswerService answerService;
private final FormGenerator formGenerator;
+ private final AnswerSaveFormGenerator answerSaveFormGenerator;
+
private final HttpLocaleResolver httpLocaleResolver;
AnswerController(QuestionService questionService,
AnswerService answerService,
FormGenerator formGenerator,
+ AnswerSaveFormGenerator answerSaveFormGenerator,
HttpLocaleResolver httpLocaleResolver) {
this.questionService = questionService;
this.answerService = answerService;
this.formGenerator = formGenerator;
+ this.answerSaveFormGenerator = answerSaveFormGenerator;
this.httpLocaleResolver = httpLocaleResolver;
}
@@ -124,13 +135,16 @@ HttpResponse> answerShow(HttpRequest> request,
@Nullable Tenant tenant) {
Locale locale = httpLocaleResolver.resolveOrDefault(request);
return answerShowModel(questionId, id, authentication, locale, tenant)
- .map(model -> new ModelAndView<>(VIEW_SHOW, model))
+ .map(model -> TurboFrameUtils.turboFrame(request)
+ .map(frame -> (Object) TurboFrameUtils.turboFrame(frame, VIEW_SHOW_FRAGMENT, model))
+ .orElseGet(() -> new ModelAndView<>(VIEW_SHOW, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
@GetHtml(uri = PATH_EDIT, rolesAllowed = SecurityRule.IS_AUTHENTICATED)
- HttpResponse> answerEdit(@NonNull @NotNull HttpRequest> request, @PathVariable @NotBlank String questionId,
+ HttpResponse> answerEdit(@NonNull @NotNull HttpRequest> request,
+ @PathVariable @NotBlank String questionId,
@PathVariable @NotBlank String id,
@NonNull Authentication authentication,
@Nullable Locale locale,
@@ -138,7 +152,9 @@ HttpResponse> answerEdit(@NonNull @NotNull HttpRequest> request, @PathVariab
return questionService.findById(questionId, tenant)
.flatMap(question -> answerService.findById(id, authentication, tenant)
.map(answer -> updateAnswerModel(question, answer, locale)))
- .map(model -> new ModelAndView<>(VIEW_EDIT, model))
+ .map(model -> TurboFrameUtils.turboFrame(request)
+ .map(frame -> (Object) TurboFrameUtils.turboFrame(frame, VIEW_EDIT_FRAGMENT, model))
+ .orElseGet(() -> new ModelAndView<>(VIEW_EDIT, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
@@ -151,6 +167,12 @@ HttpResponse> answerUpdate(@NonNull @NotNull HttpRequest> request,
@Nullable Tenant tenant,
@Body @NonNull @NotNull @Valid AnswerUpdateRecord answerUpdate) {
answerService.update(authentication, questionId, id, answerUpdate, tenant);
+ if (TurboMediaType.acceptsTurboStream(request)) {
+ return answerShowModel(questionId, id, authentication, httpLocaleResolver.resolveOrDefault(request), tenant)
+ .flatMap(model -> TurboStreamUtils.turboStream(request, VIEW_SHOW_FRAGMENT, model))
+ .map(HttpResponse::ok)
+ .orElseGet(NotFoundController::notFoundRedirect);
+ }
return HttpResponse.seeOther(PATH_SHOW_URI_BUILDER.apply(questionId, id));
}
@@ -169,9 +191,10 @@ private Optional> retrySave(HttpRequest> request, Authenticati
return answerForm(request)
.map(f -> questionService.findById(f.questionId(), tenant)
.map(q -> QuestionController.showModel(answerService, q, generateForm(f, ex), auth, tenant))
- .map(model -> new ModelAndView<>(QuestionController.VIEW_SHOW, model))
+ .map(model -> TurboMediaType.acceptsTurboStream(request) ? TurboStreamUtils.turboStream(request, VIEW_SHOW_FRAGMENT, model) : new ModelAndView<>(QuestionController.VIEW_SHOW, model))
.map(b -> HttpResponse.unprocessableEntity().body(b))
- .orElseGet(NotFoundController::notFoundRedirect));
+ .orElseGet(NotFoundController::notFoundRedirect));
+
}
private Optional extends AnswerForm> answerForm(HttpRequest> request) {
@@ -268,6 +291,12 @@ private HttpResponse> answerSave(@NonNull HttpRequest> request,
return HttpResponse.unprocessableEntity();
}
answerService.save(authentication, new AnswerSave(form.questionId(), form.answerDate(), format, text), tenant);
+ if (TurboMediaType.acceptsTurboStream(request)) {
+ return QuestionController.showModel(answerService, questionService, answerSaveFormGenerator, questionId, authentication, tenant)
+ .flatMap(model -> TurboStreamUtils.turboStream(request, QuestionController.VIEW_SHOW_FRAGMENT, model))
+ .map(HttpResponse::ok)
+ .orElseGet(NotFoundController::notFoundRedirect);
+ }
return HttpResponse.seeOther(QuestionController.PATH_SHOW_BUILDER.apply(questionId));
}
diff --git a/http/src/main/java/org/projectcheckins/http/controllers/ApiConstants.java b/http/src/main/java/org/projectcheckins/http/controllers/ApiConstants.java
index f8974dc3..98663c74 100644
--- a/http/src/main/java/org/projectcheckins/http/controllers/ApiConstants.java
+++ b/http/src/main/java/org/projectcheckins/http/controllers/ApiConstants.java
@@ -10,6 +10,8 @@
)
)
public interface ApiConstants {
+ String FRAME_ID_MAIN = "main";
+ String DATA_TURBO_ACTION = "advance";
String PATH_VARIABLE_ID = "{id}";
String SLASH = "/";
@@ -19,6 +21,10 @@ public interface ApiConstants {
String ACTION_SHOW = "show";
String ACTION_CREATE = "create";
+ String FRAGMENT_SHOW = "_show.html";
+ String FRAGMENT_CREATE = "_create.html";
+ String FRAGMENT_EDIT = "_edit.html";
+ String FRAGMENT_LIST = "_list.html";
String ACTION_EDIT = "edit";
String ACTION_SAVE = "save";
String ACTION_UPDATE = "update";
diff --git a/http/src/main/java/org/projectcheckins/http/controllers/NotFoundController.java b/http/src/main/java/org/projectcheckins/http/controllers/NotFoundController.java
index 7040b0a4..7f02d3d4 100644
--- a/http/src/main/java/org/projectcheckins/http/controllers/NotFoundController.java
+++ b/http/src/main/java/org/projectcheckins/http/controllers/NotFoundController.java
@@ -19,7 +19,6 @@ Map index() {
return Collections.emptyMap();
}
-
public static MutableHttpResponse notFoundRedirect() {
return HttpResponse.seeOther(URI.create(PATH));
}
diff --git a/http/src/main/java/org/projectcheckins/http/controllers/ProfileController.java b/http/src/main/java/org/projectcheckins/http/controllers/ProfileController.java
index 877f6ee9..cdb94a1a 100644
--- a/http/src/main/java/org/projectcheckins/http/controllers/ProfileController.java
+++ b/http/src/main/java/org/projectcheckins/http/controllers/ProfileController.java
@@ -12,6 +12,7 @@
import io.micronaut.views.fields.Form;
import io.micronaut.views.fields.FormGenerator;
import io.micronaut.views.fields.messages.Message;
+import io.micronaut.views.turbo.http.TurboMediaType;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
@@ -25,6 +26,9 @@
import org.projectcheckins.core.api.Profile;
import org.projectcheckins.core.forms.ProfileUpdate;
import org.projectcheckins.core.repositories.ProfileRepository;
+
+import org.projectcheckins.security.http.TurboFrameUtils;
+import org.projectcheckins.security.http.TurboStreamUtils;
import static org.projectcheckins.http.controllers.ApiConstants.*;
@Controller
@@ -36,11 +40,15 @@ class ProfileController {
private static final String MODEL_PROFILE = "profile";
// SHOW
+ public static final String VIEW_SHOW_FRAGMENT = MODEL_PROFILE + SLASH + ApiConstants.FRAGMENT_SHOW;
+
private static final Message MESSAGE_SHOW = Message.of("Profile", PROFILE + ApiConstants.DOT + ApiConstants.ACTION_SHOW);
private static final String PATH_SHOW = PATH + SLASH + ApiConstants.ACTION_SHOW;
private static final String VIEW_SHOW = PATH + ApiConstants.VIEW_SHOW;
// EDIT
+ private static final String VIEW_EDIT_FRAGMENT = PATH + SLASH + FRAGMENT_EDIT;
+
private static final Message MESSAGE_BREADCRUMB_EDIT = Message.of("Edit", PROFILE + ApiConstants.DOT + ApiConstants.ACTION_EDIT);
private static final Breadcrumb BREADCRUMB_EDIT = new Breadcrumb(MESSAGE_BREADCRUMB_EDIT);
private static final String PATH_EDIT = PATH + SLASH + ApiConstants.ACTION_EDIT;
@@ -62,7 +70,9 @@ HttpResponse> profileShow(@NonNull @NotNull HttpRequest> request,
@NonNull @NotNull Authentication authentication,
@Nullable Tenant tenant) {
return showModel(authentication, tenant)
- .map(model -> new ModelAndView<>(VIEW_SHOW, model))
+ .map(model -> TurboFrameUtils.turboFrame(request)
+ .map(frame -> (Object) TurboFrameUtils.turboFrame(frame, VIEW_SHOW_FRAGMENT, model))
+ .orElseGet(() -> new ModelAndView<>(VIEW_SHOW, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
@@ -73,7 +83,9 @@ HttpResponse> profileEdit(@NonNull @NotNull HttpRequest> request,
@Nullable Tenant tenant) {
return profileRepository.findByAuthentication(authentication, tenant)
.map(this::updateModel)
- .map(model -> new ModelAndView<>(VIEW_EDIT, model))
+ .map(model -> TurboFrameUtils.turboFrame(request)
+ .map(frame -> (Object) TurboFrameUtils.turboFrame(frame, VIEW_EDIT_FRAGMENT, model))
+ .orElseGet(() -> new ModelAndView<>(VIEW_EDIT, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
@@ -85,6 +97,12 @@ HttpResponse> profileUpdate(
@NonNull @NotNull @Valid @Body ProfileUpdate profileUpdate,
@Nullable Tenant tenant) {
profileRepository.update(authentication, profileUpdate, tenant);
+ if (TurboMediaType.acceptsTurboStream(request)) {
+ return showModel(authentication, tenant)
+ .flatMap(model -> TurboStreamUtils.turboStream(request, VIEW_SHOW_FRAGMENT, model))
+ .map(HttpResponse::ok)
+ .orElseGet(HttpResponse::notFound);
+ }
return HttpResponse.seeOther(URI.create(PATH_SHOW));
}
diff --git a/http/src/main/java/org/projectcheckins/http/controllers/QuestionController.java b/http/src/main/java/org/projectcheckins/http/controllers/QuestionController.java
index 10e8b727..df287394 100644
--- a/http/src/main/java/org/projectcheckins/http/controllers/QuestionController.java
+++ b/http/src/main/java/org/projectcheckins/http/controllers/QuestionController.java
@@ -16,6 +16,9 @@
import io.micronaut.views.ModelAndView;
import io.micronaut.views.fields.Form;
import io.micronaut.views.fields.messages.Message;
+import io.micronaut.views.turbo.TurboStream;
+import io.micronaut.views.turbo.http.TurboHttpHeaders;
+import io.micronaut.views.turbo.http.TurboMediaType;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
@@ -27,6 +30,8 @@
import org.projectcheckins.core.forms.*;
import org.projectcheckins.core.services.AnswerService;
import org.projectcheckins.core.services.QuestionService;
+import org.projectcheckins.security.http.TurboFrameUtils;
+import org.projectcheckins.security.http.TurboStreamUtils;
import java.net.URI;
import java.time.DayOfWeek;
@@ -37,6 +42,8 @@
import java.util.regex.Pattern;
import static org.projectcheckins.http.controllers.ApiConstants.*;
+import static org.projectcheckins.http.controllers.ApiConstants.FRAME_ID_MAIN;
+import static org.projectcheckins.http.controllers.ApiConstants.SLASH;
@Controller
class QuestionController {
@@ -50,6 +57,7 @@ class QuestionController {
public static final String MODEL_ANSWERS = "answers";
// LIST
+ private static final String VIEW_LIST_FRAGMENT = PATH + SLASH + FRAGMENT_LIST;
public static final String PATH_LIST = PATH + ApiConstants.PATH_LIST;
private static final String VIEW_LIST = PATH + ApiConstants.VIEW_LIST;
public static final Message MESSAGE_QUESTIONS = Message.of("Questions", QUESTION + DOT + ACTION_LIST);
@@ -58,6 +66,7 @@ class QuestionController {
// CREATE
private static final String PATH_CREATE = PATH + ApiConstants.PATH_CREATE;
private static final String VIEW_CREATE = PATH + ApiConstants.VIEW_CREATE;
+ private static final String VIEW_CREATE_FRAGMENT = PATH + SLASH + FRAGMENT_CREATE;
private static final Breadcrumb BREADCRUMB_CREATE = new Breadcrumb(Message.of("New Question", QUESTION + DOT + ACTION_CREATE));
// SAVE
@@ -67,11 +76,13 @@ class QuestionController {
private static final String PATH_SHOW = PATH + ApiConstants.PATH_SHOW;
public static final Function PATH_SHOW_BUILDER = id -> UriBuilder.of(PATH).path(id).path(ACTION_SHOW).build();
public static final String VIEW_SHOW = PATH + ApiConstants.VIEW_SHOW;
+ public static final String VIEW_SHOW_FRAGMENT = PATH + SLASH + FRAGMENT_SHOW;
public static final Function BREADCRUMB_SHOW = question -> new Breadcrumb(Message.of(question.title()), PATH_SHOW_BUILDER.andThen(URI::toString).apply(question.id()));
// EDIT
private static final String PATH_EDIT = PATH + ApiConstants.PATH_EDIT;
private static final String VIEW_EDIT = PATH + ApiConstants.VIEW_EDIT;
+ private static final String VIEW_EDIT_FRAGMENT = PATH + SLASH + FRAGMENT_EDIT;
private static final Breadcrumb BREADCRUMB_EDIT = new Breadcrumb(Message.of("Edit Question", QUESTION + DOT + ACTION_EDIT));
// UPDATE
@@ -98,14 +109,16 @@ class QuestionController {
@GetHtml(uri = PATH_LIST,
rolesAllowed = SecurityRule.IS_AUTHENTICATED,
- view = VIEW_LIST)
+ view = VIEW_LIST,
+ turboView = VIEW_LIST_FRAGMENT)
Map questionList(@Nullable Tenant tenant) {
return listModel(tenant);
}
@GetHtml(uri = PATH_CREATE,
rolesAllowed = SecurityRule.IS_AUTHENTICATED,
- view = VIEW_CREATE)
+ view = VIEW_CREATE,
+ turboView = VIEW_CREATE_FRAGMENT)
Map questionCreate(@Nullable Tenant tenant) {
final QuestionForm form = QuestionFormRecord.of(new QuestionRecord(
null,
@@ -120,12 +133,14 @@ Map questionCreate(@Nullable Tenant tenant) {
}
@PostForm(uri = PATH_SAVE, rolesAllowed = SecurityRule.IS_AUTHENTICATED)
- HttpResponse> questionSave(HttpRequest> request,
+ HttpResponse> questionSave(@NonNull @NotNull HttpRequest> request,
@NonNull @NotNull @Valid @Body QuestionFormRecord form,
@NonNull Authentication authentication,
@Nullable Tenant tenant) {
String id = questionService.save(form, tenant);
- return HttpResponse.seeOther(PATH_SHOW_BUILDER.apply(id));
+ return TurboMediaType.acceptsTurboStream(request)
+ ? showTurboStream(request, id, authentication, tenant).map(HttpResponse::ok).orElseGet(HttpResponse::notFound)
+ : HttpResponse.seeOther(PATH_SHOW_BUILDER.apply(id));
}
@GetHtml(uri = PATH_SHOW, rolesAllowed = SecurityRule.IS_AUTHENTICATED)
@@ -134,29 +149,39 @@ HttpResponse> questionShow(HttpRequest> request,
@NonNull Authentication authentication,
@Nullable Tenant tenant) {
return showModel(answerService, questionService, answerSaveFormGenerator, id, authentication, tenant)
- .map(model -> new ModelAndView<>(VIEW_SHOW, model))
+ .map(model -> TurboFrameUtils.turboFrame(request, VIEW_SHOW_FRAGMENT, model)
+ .orElseGet(() -> new ModelAndView<>(VIEW_SHOW, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
+
@GetHtml(uri = PATH_EDIT, rolesAllowed = SecurityRule.IS_AUTHENTICATED)
HttpResponse> questionEdit(HttpRequest> request,
@PathVariable @NotBlank String id,
@Nullable Tenant tenant) {
return questionService.findById(id, tenant)
.map(question -> updateModel(question, QuestionFormRecord.of(question), tenant))
- .map(model -> new ModelAndView<>(VIEW_EDIT, model))
+ .map(model -> TurboFrameUtils.turboFrame(request)
+ .map(frame -> (Object) TurboFrameUtils.turboFrame(frame, VIEW_EDIT_FRAGMENT, model))
+ .orElseGet(() -> new ModelAndView<>(VIEW_EDIT, model)))
.map(HttpResponse::ok)
.orElseGet(NotFoundController::notFoundRedirect);
}
@PostForm(uri = PATH_UPDATE, rolesAllowed = SecurityRule.IS_AUTHENTICATED)
+
HttpResponse> questionUpdate(@NonNull @NotNull HttpRequest> request,
@NonNull Authentication authentication,
@PathVariable @NotBlank String id,
@NonNull @NotNull @Valid @Body QuestionFormRecord form,
@Nullable Tenant tenant) {
questionService.update(id, form, tenant);
+ if (TurboMediaType.acceptsTurboStream(request)) {
+ return showTurboStream(request, id, authentication, tenant)
+ .map(HttpResponse::ok)
+ .orElseGet(HttpResponse::notFound);
+ }
return HttpResponse.seeOther(PATH_SHOW_BUILDER.apply(id));
}
@@ -165,19 +190,24 @@ HttpResponse> questionDelete(HttpRequest> request,
@PathVariable @NotBlank String id,
@Nullable Tenant tenant) {
questionService.deleteById(id, tenant);
- return HttpResponse.seeOther(URI.create(PATH_LIST));
+ return TurboMediaType.acceptsTurboStream(request)
+ ? HttpResponse.ok(TurboStreamUtils.turboStream(request, VIEW_LIST_FRAGMENT, listModel(tenant)))
+ : HttpResponse.seeOther(URI.create(PATH_LIST));
}
@Error(exception = ConstraintViolationException.class)
public HttpResponse> onConstraintViolationException(HttpRequest> request,
@Nullable Tenant tenant,
ConstraintViolationException ex) {
- String contentType = MediaType.TEXT_HTML;
+ String turboFrame = request.getHeaders().get(TurboHttpHeaders.TURBO_FRAME, String.class, FRAME_ID_MAIN);
+ boolean turboRequest = TurboMediaType.acceptsTurboStream(request);
+ String contentType = turboRequest ? TurboMediaType.TURBO_STREAM : MediaType.TEXT_HTML;
+
final Matcher matcher = REGEX_UPDATE.matcher(request.getPath());
if (request.getPath().equals(PATH_SAVE)) {
return request.getBody(QuestionForm.class)
.map(form -> saveModel(QuestionFormRecord.of(form, ex), tenant))
- .map(model -> unprocessableEntity(request, model, contentType, VIEW_CREATE))
+ .map(model -> unprocessableEntity(model, contentType, turboRequest, VIEW_CREATE, VIEW_CREATE_FRAGMENT, turboFrame))
.orElseGet(HttpResponse::serverError);
} else if (matcher.find()) {
final String id = matcher.group(1);
@@ -188,19 +218,22 @@ public HttpResponse> onConstraintViolationException(HttpRequest> request,
QuestionForm form = updateFormOptional.get();
return questionService.findById(id, tenant)
.map(question -> updateModel(question, QuestionFormRecord.of(form, ex), tenant))
- .map(model -> unprocessableEntity(request, model, contentType, VIEW_EDIT))
+ .map(model -> unprocessableEntity(model, contentType, turboRequest, VIEW_EDIT, VIEW_EDIT_FRAGMENT, turboFrame))
.orElseGet(NotFoundController::notFoundRedirect);
}
return HttpResponse.serverError();
}
@NonNull
- private HttpResponse> unprocessableEntity(@NonNull @NotNull HttpRequest> request,
- Object model,
+ private HttpResponse> unprocessableEntity(Object model,
String contentType,
- String view) {
- return HttpResponse.unprocessableEntity().body(new ModelAndView<>(view, model))
- .contentType(contentType);
+ boolean turboRequest,
+ String view,
+ String turboView,
+ @Nullable String turboFrame) {
+ return HttpResponse.unprocessableEntity().body(turboRequest
+ ? TurboStream.builder().targetDomId(turboFrame).template(turboView, model).update()
+ : new ModelAndView<>(view, model));
}
@NonNull
@@ -248,6 +281,14 @@ public static Optional
-
+