From 35d25bbc9d261e7b5585d0fd1d398dff3ab4a176 Mon Sep 17 00:00:00 2001
From: Vitaly Takmazov
Date: Fri, 13 Jan 2023 17:38:06 +0300
Subject: Fix OpenAPI generation
* Use HandlerMethodArgumentResolver to pass visitor
* Hide visitor from OpenAPI definitions
* Drop unused AsciiDoc template
---
src/main/java/com/juick/config/OpenAPIConfig.java | 29 +++++++++++
src/main/java/com/juick/config/SecurityConfig.java | 1 +
src/main/java/com/juick/config/WebConfig.java | 15 +++++-
.../java/com/juick/www/SiteAttributesHandler.java | 15 ------
.../com/juick/www/VisitorArgumentResolver.java | 59 ++++++++++++++++++++++
src/main/java/com/juick/www/ad/SapeService.java | 3 +-
src/main/java/com/juick/www/api/Index.java | 2 +-
src/main/java/com/juick/www/api/Mastodon.java | 11 ++--
src/main/java/com/juick/www/api/Messages.java | 13 ++---
src/main/java/com/juick/www/api/Notifications.java | 13 ++---
src/main/java/com/juick/www/api/PM.java | 7 +--
src/main/java/com/juick/www/api/Post.java | 13 ++---
src/main/java/com/juick/www/api/Service.java | 7 +--
src/main/java/com/juick/www/api/Tags.java | 3 +-
src/main/java/com/juick/www/api/Users.java | 17 ++++---
.../java/com/juick/www/api/activity/Profile.java | 7 +--
src/main/java/com/juick/www/controllers/Help.java | 3 +-
.../java/com/juick/www/controllers/Settings.java | 7 +--
.../java/com/juick/www/controllers/SignUp.java | 5 +-
src/main/java/com/juick/www/controllers/Site.java | 27 +++++-----
.../com/juick/www/controllers/SocialLogin.java | 2 +-
src/main/java/com/juick/www/rss/Feeds.java | 6 ++-
22 files changed, 184 insertions(+), 81 deletions(-)
create mode 100644 src/main/java/com/juick/config/OpenAPIConfig.java
create mode 100644 src/main/java/com/juick/www/VisitorArgumentResolver.java
(limited to 'src/main/java/com/juick')
diff --git a/src/main/java/com/juick/config/OpenAPIConfig.java b/src/main/java/com/juick/config/OpenAPIConfig.java
new file mode 100644
index 00000000..860565b3
--- /dev/null
+++ b/src/main/java/com/juick/config/OpenAPIConfig.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.juick.config;
+
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@SecurityScheme(name = "basicAuth",
+ type = SecuritySchemeType.HTTP,
+ scheme = "basic")
+public class OpenAPIConfig {
+}
diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java
index d8faff7b..cb448cf3 100644
--- a/src/main/java/com/juick/config/SecurityConfig.java
+++ b/src/main/java/com/juick/config/SecurityConfig.java
@@ -189,6 +189,7 @@ public class SecurityConfig {
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/api/", "/api/messages", "/api/avatar",
+ "/v3/api-docs",
"/api/swagger-ui/**",
"/api/messages/discussions",
"/api/users", "/api/thread", "/api/tags",
diff --git a/src/main/java/com/juick/config/WebConfig.java b/src/main/java/com/juick/config/WebConfig.java
index d736b863..136faea9 100644
--- a/src/main/java/com/juick/config/WebConfig.java
+++ b/src/main/java/com/juick/config/WebConfig.java
@@ -24,14 +24,18 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
+import com.juick.service.UserService;
+import com.juick.www.VisitorArgumentResolver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.VersionResourceResolver;
@@ -45,6 +49,8 @@ public class WebConfig implements WebMvcConfigurer {
private String baseDir;
@Inject
private List extends Converter, ?>> converters;
+ @Inject
+ private UserService userService;
@Override
public void addFormatters(FormatterRegistry registry) {
@@ -52,7 +58,14 @@ public class WebConfig implements WebMvcConfigurer {
registry.addConverter(converter);
}
}
-
+ @Bean
+ HandlerMethodArgumentResolver visitorArgumentResolver() {
+ return new VisitorArgumentResolver(userService);
+ }
+ @Override
+ public void addArgumentResolvers(List resolvers) {
+ resolvers.add(visitorArgumentResolver());
+ }
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
try {
diff --git a/src/main/java/com/juick/www/SiteAttributesHandler.java b/src/main/java/com/juick/www/SiteAttributesHandler.java
index 4ead9d1e..0e1ca1db 100644
--- a/src/main/java/com/juick/www/SiteAttributesHandler.java
+++ b/src/main/java/com/juick/www/SiteAttributesHandler.java
@@ -36,8 +36,6 @@ import javax.inject.Inject;
@ControllerAdvice
public class SiteAttributesHandler {
- @Inject
- private UserService userService;
@ModelAttribute
public void setVaryResponseHeader(HttpServletResponse response) {
response.setHeader("Vary", "Accept-Language");
@@ -46,17 +44,4 @@ public class SiteAttributesHandler {
public void setReturnPathAttribute(Model model) {
model.addAttribute("retpath", ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
}
- @ModelAttribute
- public User visitor(Model model, @AuthenticationPrincipal Object principal) {
- if (principal != null) {
- if (principal instanceof JuickUser) {
- return ((JuickUser) principal).getUser();
- }
- if (principal instanceof Jwt) {
- var uname = (String) ((Jwt) principal).getClaims().get("sub");
- return userService.getUserByName(uname);
- }
- }
- return AnonymousUser.INSTANCE;
- }
}
diff --git a/src/main/java/com/juick/www/VisitorArgumentResolver.java b/src/main/java/com/juick/www/VisitorArgumentResolver.java
new file mode 100644
index 00000000..a092581a
--- /dev/null
+++ b/src/main/java/com/juick/www/VisitorArgumentResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.juick.www;
+
+import com.juick.model.AnonymousUser;
+import com.juick.model.User;
+import com.juick.service.UserService;
+import com.juick.service.security.entities.JuickUser;
+import org.springframework.core.MethodParameter;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import javax.inject.Inject;
+
+public class VisitorArgumentResolver implements HandlerMethodArgumentResolver {
+ private final UserService userService;
+ public VisitorArgumentResolver(UserService userService) {
+ this.userService = userService;
+ }
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.getParameterType().equals(User.class);
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
+ var principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ if (principal != null) {
+ if (principal instanceof JuickUser) {
+ return ((JuickUser) principal).getUser();
+ }
+ if (principal instanceof Jwt) {
+ var uname = (String) ((Jwt) principal).getClaims().get("sub");
+ return userService.getUserByName(uname);
+ }
+ }
+ return AnonymousUser.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/juick/www/ad/SapeService.java b/src/main/java/com/juick/www/ad/SapeService.java
index 7b398806..97ab882b 100644
--- a/src/main/java/com/juick/www/ad/SapeService.java
+++ b/src/main/java/com/juick/www/ad/SapeService.java
@@ -19,6 +19,7 @@ package com.juick.www.ad;
import com.juick.model.User;
import com.juick.www.controllers.Site;
+import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.ui.ModelMap;
@@ -46,7 +47,7 @@ public class SapeService {
@ModelAttribute
public void addSapeLinks(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie,
@RequestParam(required = false, defaultValue = "0") int before,
@RequestParam(name = "show", required = false) String paramShow,
diff --git a/src/main/java/com/juick/www/api/Index.java b/src/main/java/com/juick/www/api/Index.java
index 46aa65b5..921ebca4 100644
--- a/src/main/java/com/juick/www/api/Index.java
+++ b/src/main/java/com/juick/www/api/Index.java
@@ -45,7 +45,7 @@ public class Index {
@Hidden
@RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET)
public ResponseEntity description() {
- URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/swagger-ui.html").build().toUri();
+ URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().replacePath("/api/swagger-ui/index.html").build().toUri();
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).location(redirectUri).build();
}
diff --git a/src/main/java/com/juick/www/api/Mastodon.java b/src/main/java/com/juick/www/api/Mastodon.java
index 5982209e..c595bb19 100644
--- a/src/main/java/com/juick/www/api/Mastodon.java
+++ b/src/main/java/com/juick/www/api/Mastodon.java
@@ -33,6 +33,7 @@ import com.juick.util.HttpBadRequestException;
import com.juick.util.MessageUtils;
import com.juick.www.WebApp;
import com.juick.www.api.activity.helpers.ProfileUriBuilder;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -166,7 +167,7 @@ public class Mastodon {
}
@GetMapping("/api/v1/accounts/verify_credentials")
- public CredentialAccount account(@ModelAttribute User visitor) {
+ public CredentialAccount account(@Parameter(hidden = true) User visitor) {
return toAccount(visitor);
}
@@ -220,7 +221,7 @@ public class Mastodon {
}
@GetMapping("/api/v1/accounts/relationships")
- public List relationships(@ModelAttribute User visitor, @RequestParam(value = "id[]") String[] ids) {
+ public List relationships(@Parameter(hidden = true) User visitor, @RequestParam(value = "id[]") String[] ids) {
return Stream.of(ids).map(
id -> findRelationships(String.valueOf(visitor.getUid()), id)
).collect(Collectors.toList());
@@ -283,7 +284,7 @@ public class Mastodon {
}
@GetMapping("/api/v1/conversations")
- public List conversations(@ModelAttribute User visitor) {
+ public List conversations(@Parameter(hidden = true) User visitor) {
return chatService.getLastChats(visitor).stream().map(
this::toConversation
).collect(Collectors.toList());
@@ -313,7 +314,7 @@ public class Mastodon {
}
@GetMapping("/api/v1/timelines/{timeline}")
- public List publicTimeline(@ModelAttribute User visitor,
+ public List publicTimeline(@Parameter(hidden = true) User visitor,
@PathVariable String timeline,
@RequestParam(value = "max_id", required = false) String maxId,
@RequestParam(value = "only_media", required = false, defaultValue = "false") Boolean media) {
@@ -349,7 +350,7 @@ public class Mastodon {
}
@GetMapping("/api/v1/statuses/{mid}-{rid}/context")
- public Context thread(@ModelAttribute User visitor, @PathVariable int mid, @PathVariable int rid) {
+ public Context thread(@Parameter(hidden = true) User visitor, @PathVariable int mid, @PathVariable int rid) {
var thread = messagesService.getReplies(visitor, mid).stream()
.filter(m -> m.getRid() > rid)
.peek(msg -> msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser())))
diff --git a/src/main/java/com/juick/www/api/Messages.java b/src/main/java/com/juick/www/api/Messages.java
index e23356a4..fb10d7a7 100644
--- a/src/main/java/com/juick/www/api/Messages.java
+++ b/src/main/java/com/juick/www/api/Messages.java
@@ -30,6 +30,7 @@ import com.juick.service.MessagesService;
import com.juick.service.TagService;
import com.juick.service.UserService;
import com.juick.service.component.SystemEvent;
+import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
@@ -68,7 +69,7 @@ public class Messages {
// TODO: serialize image urls
@GetMapping({"/api/home"})
- public List getHome(@ModelAttribute User visitor,
+ public List getHome(@Parameter(hidden = true) User visitor,
@RequestParam(defaultValue = "0") int before_mid) {
int vuid = visitor.getUid();
List mids = messagesService.getMyFeed(vuid, before_mid, true);
@@ -78,7 +79,7 @@ public class Messages {
}
@GetMapping("/api/messages")
- public List getMessages(@ModelAttribute User visitor,
+ public List getMessages(@Parameter(hidden = true) User visitor,
@RequestParam(required = false) String uname,
@RequestParam(name = "before_mid", defaultValue = "0") Integer before,
@RequestParam(required = false, defaultValue = "0") Integer daysback,
@@ -136,7 +137,7 @@ public class Messages {
}
@DeleteMapping("/api/messages")
- public CommandResult deleteMessage(@ModelAttribute User visitor, @RequestParam int mid,
+ public CommandResult deleteMessage(@Parameter(hidden = true) User visitor, @RequestParam int mid,
@RequestParam(required = false, defaultValue = "0") int rid) {
if (rid > 0) {
if (messagesService.deleteReply(visitor.getUid(), mid, rid)) {
@@ -150,7 +151,7 @@ public class Messages {
}
@GetMapping("/api/messages/discussions")
- public List getDiscussions(@ModelAttribute User visitor,
+ public List getDiscussions(@Parameter(hidden = true) User visitor,
@RequestParam(required = false, defaultValue = "0") Long to) {
List msgs = messagesService.getMessages(visitor, messagesService.getDiscussions(visitor.getUid(), to));
msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
@@ -158,7 +159,7 @@ public class Messages {
}
@GetMapping("/api/thread")
- public List getThread(@ModelAttribute User visitor, @RequestParam(defaultValue = "0") int mid,
+ public List getThread(@Parameter(hidden = true) User visitor, @RequestParam(defaultValue = "0") int mid,
@RequestParam(defaultValue = "true") boolean showReplies) {
Optional message = messagesService.getMessage(mid);
if (message.isPresent()) {
@@ -188,7 +189,7 @@ public class Messages {
}
@GetMapping(value = "/api/thread/mark_read/{mid}-{rid}.gif", produces = MediaType.IMAGE_GIF_VALUE)
- public byte[] markThreadRead(@ModelAttribute User visitor, @PathVariable int mid, @PathVariable int rid)
+ public byte[] markThreadRead(@Parameter(hidden = true) User visitor, @PathVariable int mid, @PathVariable int rid)
throws IOException {
if (!visitor.isAnonymous()) {
messagesService.setLastReadComment(visitor, mid, rid);
diff --git a/src/main/java/com/juick/www/api/Notifications.java b/src/main/java/com/juick/www/api/Notifications.java
index 32ba3dc1..e5e85eb7 100644
--- a/src/main/java/com/juick/www/api/Notifications.java
+++ b/src/main/java/com/juick/www/api/Notifications.java
@@ -22,6 +22,7 @@ import com.juick.service.*;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpForbiddenException;
import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
@@ -71,7 +72,7 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGet(
- @ModelAttribute(binding = false) User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(required = false, defaultValue = "0") int uid,
@RequestParam(required = false, defaultValue = "0") int mid,
@RequestParam(required = false, defaultValue = "0") int rid) {
@@ -110,7 +111,7 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doDelete(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestBody List list) {
if (!visitor.equals(serviceUser)) {
throw new HttpForbiddenException();
@@ -129,7 +130,7 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications/delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doDeleteTokens(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestBody List list) {
if (!visitor.equals(serviceUser)) {
throw new HttpForbiddenException();
@@ -149,7 +150,7 @@ public class Notifications {
@Hidden
@RequestMapping(value = "/api/notifications", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doPut(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestBody List list) {
list.forEach(t -> {
switch (t.type()) {
@@ -165,7 +166,7 @@ public class Notifications {
@Deprecated
@RequestMapping(value = "/api/android/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doAndroidRegister(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(name = "regid") String regId) {
pushQueriesService.addGCMToken(visitor.getUid(), regId);
return Status.OK;
@@ -174,7 +175,7 @@ public class Notifications {
@Deprecated
@RequestMapping(value = "/api/winphone/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Status doWinphoneRegister(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(name = "url") String regId) {
pushQueriesService.addMPNSToken(visitor.getUid(), regId);
return Status.OK;
diff --git a/src/main/java/com/juick/www/api/PM.java b/src/main/java/com/juick/www/api/PM.java
index c4acd4b3..5f0988c1 100644
--- a/src/main/java/com/juick/www/api/PM.java
+++ b/src/main/java/com/juick/www/api/PM.java
@@ -29,6 +29,7 @@ import com.juick.www.WebApp;
import com.juick.service.ChatService;
import com.juick.service.UserService;
import com.juick.service.component.SystemEvent;
+import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -53,7 +54,7 @@ public class PM {
@RequestMapping(value = "/api/pm", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetPM(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(required = false) String uname) {
int uid = 0;
if (uname != null && uname.matches("^[a-zA-Z0-9\\-]{2,16}$")) {
@@ -71,7 +72,7 @@ public class PM {
@RequestMapping(value = "/api/pm", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Message doPostPM(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam String uname,
@RequestParam String body) {
User userTo = AnonymousUser.INSTANCE;
@@ -103,7 +104,7 @@ public class PM {
}
@RequestMapping(value = "/api/groups_pms", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public PrivateChats doGetGroupsPMs(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(defaultValue = "5") int cnt) {
// TODO: ignore cnt param for now but make sure paging param will not be cnt
diff --git a/src/main/java/com/juick/www/api/Post.java b/src/main/java/com/juick/www/api/Post.java
index 2dba6e07..97311c21 100644
--- a/src/main/java/com/juick/www/api/Post.java
+++ b/src/main/java/com/juick/www/api/Post.java
@@ -25,6 +25,7 @@ import java.util.Optional;
import javax.inject.Inject;
import com.juick.www.api.activity.helpers.ProfileUriBuilder;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.NotNull;
import com.juick.CommandsManager;
@@ -74,7 +75,7 @@ public class Post {
@RequestMapping(value = "/api/post", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public CommandResult doPostMessage(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(required = false, defaultValue = StringUtils.EMPTY) String body,
@RequestParam(required = false) String img,
@RequestParam(required = false) MultipartFile attach) throws Exception {
@@ -105,7 +106,7 @@ public class Post {
@RequestMapping(value = "/api/comment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public CommandResult doPostComment(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(defaultValue = "0") int mid,
@RequestParam(defaultValue = "0") int rid,
@RequestParam(required = false, defaultValue = StringUtils.EMPTY) final String body,
@@ -157,7 +158,7 @@ public class Post {
@PostMapping("/api/like")
@ResponseStatus(value = HttpStatus.OK)
- public Status doPostRecommendation(@ModelAttribute User visitor, @RequestParam Integer mid) throws Exception {
+ public Status doPostRecommendation(@Parameter(hidden = true) User visitor, @RequestParam Integer mid) throws Exception {
Optional message = messagesService.getMessage(mid);
if (message.isEmpty()) {
throw new HttpNotFoundException();
@@ -173,7 +174,7 @@ public class Post {
@PostMapping("/api/subscribe")
@ResponseStatus(value = HttpStatus.OK)
- public Status doPostSubscribe(@ModelAttribute User visitor,
+ public Status doPostSubscribe(@Parameter(hidden = true) User visitor,
@RequestParam Integer mid) throws Exception {
Optional message = messagesService.getMessage(mid);
if (message.isEmpty()) {
@@ -197,7 +198,7 @@ public class Post {
@PostMapping("/api/react")
@ResponseStatus(value = HttpStatus.OK)
public Status doPostReact(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam Integer mid, @RequestParam @NotNull int reactionId,
@RequestParam(required = false, defaultValue = "1") int count) {
@@ -219,7 +220,7 @@ public class Post {
}
@PostMapping("/api/update")
- public CommandResult updateMessage(@ModelAttribute User visitor,
+ public CommandResult updateMessage(@Parameter(hidden = true) User visitor,
@RequestParam Integer mid,
@RequestParam(required = false, defaultValue = "0") Integer rid,
@RequestParam String body) {
diff --git a/src/main/java/com/juick/www/api/Service.java b/src/main/java/com/juick/www/api/Service.java
index f4599a56..1e3dcdc8 100644
--- a/src/main/java/com/juick/www/api/Service.java
+++ b/src/main/java/com/juick/www/api/Service.java
@@ -31,6 +31,7 @@ import com.juick.service.StorageService;
import com.juick.service.UserService;
import com.juick.service.component.AccountVerificationEvent;
import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.mail.Session;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
@@ -88,7 +89,7 @@ public class Service {
@Hidden
@PostMapping("/api/mail")
@ResponseStatus(value = HttpStatus.OK)
- public void processMail(@ModelAttribute User current, InputStream data) throws Exception {
+ public void processMail(@Parameter(hidden = true) User current, InputStream data) throws Exception {
if (current.equals(serviceUser)) {
MimeMessage msg = new MimeMessage(session, data);
String[] returnPaths = msg.getHeader("Return-Path");
@@ -203,7 +204,7 @@ public class Service {
@Hidden
@PostMapping("/api/mail/unsubscribe")
@ResponseStatus(value = HttpStatus.OK)
- public void processMailUnsubscribe(@ModelAttribute User current, InputStream data) throws Exception {
+ public void processMailUnsubscribe(@Parameter(hidden = true) User current, InputStream data) throws Exception {
if (current.equals(serviceUser)) {
MimeMessage msg = new MimeMessage(session, data);
String from = msg.getFrom() == null || msg.getFrom().length > 1
@@ -227,7 +228,7 @@ public class Service {
}
@GetMapping("/api/events")
- public SseEmitter handle(@ModelAttribute User visitor) {
+ public SseEmitter handle(@Parameter(hidden = true) User visitor) {
logger.info("{} connected", visitor.getName());
if (!visitor.isAnonymous()) {
userService.updateLastSeen(visitor);
diff --git a/src/main/java/com/juick/www/api/Tags.java b/src/main/java/com/juick/www/api/Tags.java
index 2b6405ac..5bb25af6 100644
--- a/src/main/java/com/juick/www/api/Tags.java
+++ b/src/main/java/com/juick/www/api/Tags.java
@@ -20,6 +20,7 @@ package com.juick.www.api;
import com.juick.model.User;
import com.juick.model.TagStats;
import com.juick.service.TagService;
+import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -36,7 +37,7 @@ public class Tags {
@RequestMapping(value = "/api/tags", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List tags(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(required = false, defaultValue = "0") int user_id
) {
if (user_id > 0) {
diff --git a/src/main/java/com/juick/www/api/Users.java b/src/main/java/com/juick/www/api/Users.java
index 124632d0..afca7ee3 100644
--- a/src/main/java/com/juick/www/api/Users.java
+++ b/src/main/java/com/juick/www/api/Users.java
@@ -42,6 +42,7 @@ import com.juick.util.HttpUtils;
import com.juick.util.WebUtils;
import com.juick.www.WebApp;
+import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@@ -74,7 +75,7 @@ public class Users {
@RequestMapping(value = "/api/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUsers(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(value = "uname", required = false) List unames) {
List users = new ArrayList<>();
@@ -96,7 +97,7 @@ public class Users {
}
@GetMapping("/api/me")
- public SecureUser getMe(@ModelAttribute User visitor) {
+ public SecureUser getMe(@Parameter(hidden = true) User visitor) {
SecureUser me = new SecureUser();
me.setUid(visitor.getUid());
me.setName(visitor.getName());
@@ -115,7 +116,7 @@ public class Users {
return (SecureUser)userService.getUserInfo(me);
}
@PostMapping("/api/me")
- public void updateMe(@ModelAttribute User visitor,
+ public void updateMe(@Parameter(hidden = true) User visitor,
@RequestParam(required = false) String password,
@RequestParam(value = "jid-del", required = false) String jidForDeletion,
@RequestParam(value = "email-add", required = false) String newEmail,
@@ -159,12 +160,12 @@ public class Users {
}
}
@PostMapping("/api/me/subscribe")
- public void subscribeMe(@ModelAttribute User visitor, String email) {
+ public void subscribeMe(@Parameter(hidden = true) User visitor, String email) {
// TODO: check status
emailService.setNotificationsEmail(visitor.getUid(), email);
}
@PostMapping("/api/me/upload")
- public void updateInfo(@ModelAttribute User visitor,
+ public void updateInfo(@Parameter(hidden = true) User visitor,
@RequestParam MultipartFile avatar) throws IOException {
String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, storageService.getTemporaryDirectory()).getHost();
if (StringUtils.isNotEmpty(avatarTmpPath)) {
@@ -175,7 +176,7 @@ public class Users {
@RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUserRead(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
@@ -199,7 +200,7 @@ public class Users {
@RequestMapping(value = "/api/users/readers", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUserReaders(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
@@ -222,7 +223,7 @@ public class Users {
}
@GetMapping("/api/info/{uname}")
- public User getUserInfo(@ModelAttribute User visitor, @PathVariable String uname) {
+ public User getUserInfo(@Parameter(hidden = true) User visitor, @PathVariable String uname) {
User user = userService.getUserByName(uname);
if (!user.isBanned()) {
user.setRead(doGetUserRead(visitor, uname));
diff --git a/src/main/java/com/juick/www/api/activity/Profile.java b/src/main/java/com/juick/www/api/activity/Profile.java
index bf4bda25..19a28a39 100644
--- a/src/main/java/com/juick/www/api/activity/Profile.java
+++ b/src/main/java/com/juick/www/api/activity/Profile.java
@@ -52,6 +52,7 @@ import com.juick.service.activities.FollowEvent;
import com.juick.service.activities.UndoAnnounceEvent;
import com.juick.service.activities.UndoFollowEvent;
import com.overzealous.remark.Remark;
+import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -133,8 +134,8 @@ public class Profile {
@GetMapping(value = "/u/{userName}/blog", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_MEDIA_TYPE,
Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE, MediaType.APPLICATION_JSON_VALUE })
- public OrderedCollectionPage getOutboxPage(@ModelAttribute User visitor, @PathVariable String userName,
- @RequestParam(required = false, defaultValue = "0") int before) {
+ public OrderedCollectionPage getOutboxPage(@Parameter(hidden = true) User visitor, @PathVariable String userName,
+ @RequestParam(required = false, defaultValue = "0") int before) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous() && !user.isBanned()) {
UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri);
@@ -278,7 +279,7 @@ public class Profile {
@CacheEvict(cacheNames = "profiles", key = "{ #visitor.uri }")
@PostMapping(value = "/api/inbox", consumes = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_MEDIA_TYPE,
Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE, MediaType.APPLICATION_JSON_VALUE })
- public ResponseEntity processInbox(@ModelAttribute User visitor, InputStream inboxData)
+ public ResponseEntity processInbox(@Parameter(hidden = true) User visitor, InputStream inboxData)
throws Exception {
String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8);
Activity activity = jsonMapper.readValue(inbox, Activity.class);
diff --git a/src/main/java/com/juick/www/controllers/Help.java b/src/main/java/com/juick/www/controllers/Help.java
index 32d96a53..ec60d7df 100644
--- a/src/main/java/com/juick/www/controllers/Help.java
+++ b/src/main/java/com/juick/www/controllers/Help.java
@@ -21,6 +21,7 @@ import com.juick.model.User;
import com.juick.util.HttpNotFoundException;
import com.juick.service.HelpService;
import com.juick.www.WebApp;
+import io.swagger.v3.oas.annotations.Parameter;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.stereotype.Controller;
@@ -49,7 +50,7 @@ public class Help {
@GetMapping({"/help/", "/help", "/help/{langOrPage}", "/help/{lang}/{page}"})
public String showHelp(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
Locale locale,
@PathVariable(required = false, name = "lang") String lang,
@PathVariable(required = false, name = "page") String page,
diff --git a/src/main/java/com/juick/www/controllers/Settings.java b/src/main/java/com/juick/www/controllers/Settings.java
index f2cd4aa9..bc6b6614 100644
--- a/src/main/java/com/juick/www/controllers/Settings.java
+++ b/src/main/java/com/juick/www/controllers/Settings.java
@@ -38,6 +38,7 @@ import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpUtils;
import com.juick.www.WebApp;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Session;
@@ -88,7 +89,7 @@ public class Settings {
@GetMapping("/settings")
protected String doGet(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
Locale locale,
@RequestParam(required = false, defaultValue = "main") String page,
@RequestParam(required = false) String code, ModelMap model) {
@@ -125,7 +126,7 @@ public class Settings {
@PostMapping("/settings")
protected String doPost(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
HttpServletRequest request, HttpServletResponse response,
@RequestParam(required = false) MultipartFile newAvatar,
ModelMap model)
@@ -271,7 +272,7 @@ public class Settings {
}
@PostMapping("/settings/unsubscribe")
public String unsubscribeOneClick(
- @ModelAttribute User user,
+ User user,
@RequestParam(name = "List-Unsubscribe") String unsubscribe,
ModelMap model) {
if (!user.isAnonymous()) {
diff --git a/src/main/java/com/juick/www/controllers/SignUp.java b/src/main/java/com/juick/www/controllers/SignUp.java
index b746c09b..87182ebd 100644
--- a/src/main/java/com/juick/www/controllers/SignUp.java
+++ b/src/main/java/com/juick/www/controllers/SignUp.java
@@ -24,6 +24,7 @@ import com.juick.www.WebApp;
import com.juick.service.EmailService;
import com.juick.service.UserService;
import com.juick.service.security.entities.JuickUser;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
@@ -58,7 +59,7 @@ public class SignUp {
@GetMapping("/signup")
protected String doGet(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam String type, @RequestParam String hash, ModelMap model) {
if (hash.length() > 36 || !type.matches("^[a-zA-Z0-9\\-]+$")
|| !hash.matches("^[a-zA-Z0-9\\-]+$")) {
@@ -99,7 +100,7 @@ public class SignUp {
protected String doPost(
HttpServletRequest request,
HttpServletResponse response,
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam String type,
@RequestParam String hash,
@RequestParam String action,
diff --git a/src/main/java/com/juick/www/controllers/Site.java b/src/main/java/com/juick/www/controllers/Site.java
index 217bb798..f4f8f744 100644
--- a/src/main/java/com/juick/www/controllers/Site.java
+++ b/src/main/java/com/juick/www/controllers/Site.java
@@ -26,6 +26,7 @@ import com.juick.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.www.api.activity.model.Context;
+import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
@@ -79,7 +80,7 @@ public class Site {
@Value("${telegram_botname:Juick_bot}")
private String tgBot;
- private void fillUserModel(ModelMap model, User user, User visitor) {
+ private void fillUserModel(ModelMap model, User user, @Parameter(hidden = true) User visitor) {
user.setAvatar(webApp.getAvatarWebPath(user));
model.addAttribute("user", user);
model.addAttribute("isSubscribed", userService.isSubscribed(visitor.getUid(), user.getUid()));
@@ -98,7 +99,7 @@ public class Site {
}
@GetMapping("/login")
- public String getloginForm(@ModelAttribute User visitor,
+ public String getloginForm(@Parameter(hidden = true) User visitor,
@RequestParam(name = "retpath", required = false, defaultValue = "/") String retPath,
HttpSession session,
ModelMap model) {
@@ -126,7 +127,7 @@ public class Site {
}
@GetMapping("/")
- protected String doGet(@ModelAttribute User visitor, Locale locale, @RequestParam(required = false) String tag,
+ protected String doGet(@Parameter(hidden = true) User visitor, Locale locale, @RequestParam(required = false) String tag,
@RequestParam(name = "show", required = false) String paramShow,
@RequestParam(name = "search", required = false) String paramSearch,
@RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore,
@@ -218,7 +219,7 @@ public class Site {
}
@GetMapping(path = "/{uname}/", headers = "Connection!=Upgrade")
- protected String doGetBlog(@ModelAttribute User visitor, @RequestParam(required = false, name = "show") String paramShow,
+ protected String doGetBlog(@Parameter(hidden = true) User visitor, @RequestParam(required = false, name = "show") String paramShow,
@RequestParam(required = false, name = "tag") String paramTagStr,
@RequestParam(required = false, name = "search") String paramSearch,
@RequestParam(required = false, name = "page", defaultValue = "0") Integer page, @PathVariable String uname,
@@ -325,7 +326,7 @@ public class Site {
}
@GetMapping("/{uname}/tags")
- protected String doGetTags(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetTags(@Parameter(hidden = true) User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.isBanned()) {
throw new HttpNotFoundException();
@@ -345,7 +346,7 @@ public class Site {
}
@GetMapping("/{uname}/friends")
- protected String doGetFriends(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetFriends(@Parameter(hidden = true) User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.isBanned()) {
throw new HttpNotFoundException();
@@ -361,7 +362,7 @@ public class Site {
}
@GetMapping("/{uname}/readers")
- protected String doGetReaders(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetReaders(@Parameter(hidden = true) User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
model.addAttribute("title", "Читатели " + user.getName());
@@ -374,7 +375,7 @@ public class Site {
}
@GetMapping("/{uname}/bl")
- protected String doGetBL(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) {
+ protected String doGetBL(@Parameter(hidden = true) User visitor, @PathVariable String uname, ModelMap model) {
User user = userService.getUserByName(uname);
if (visitor.getUid() != user.getUid()) {
throw new HttpForbiddenException();
@@ -390,7 +391,7 @@ public class Site {
}
@GetMapping("/tag/{tagName}")
- protected String tagAction(@ModelAttribute User visitor, HttpServletRequest request, @PathVariable String tagName,
+ protected String tagAction(@Parameter(hidden = true) User visitor, HttpServletRequest request, @PathVariable String tagName,
@RequestParam(required = false, defaultValue = "0") int before, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName);
@@ -453,7 +454,7 @@ public class Site {
}
@GetMapping("/pm/inbox")
- protected String doGetInbox(@ModelAttribute User visitor, ModelMap model) {
+ protected String doGetInbox(@Parameter(hidden = true) User visitor, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String title = "PM: Inbox";
List msgs = chatService.getInbox(visitor.getUid());
@@ -467,7 +468,7 @@ public class Site {
}
@GetMapping("/pm/sent")
- protected String doGetSent(@ModelAttribute User visitor, @RequestParam(required = false) String uname, ModelMap model) {
+ protected String doGetSent(@Parameter(hidden = true) User visitor, @RequestParam(required = false) String uname, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
String title = "PM: Sent";
List msgs = chatService.getOutbox(visitor.getUid());
@@ -492,7 +493,7 @@ public class Site {
}
@GetMapping(value = "/{uname}/{mid}", produces = { MediaType.TEXT_HTML_VALUE, MediaType.ALL_VALUE })
- protected String threadAction(@ModelAttribute User visitor, ModelMap model, @PathVariable String uname,
+ protected String threadAction(@Parameter(hidden = true) User visitor, ModelMap model, @PathVariable String uname,
@PathVariable int mid,
@CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie) {
if (!messagesService.canViewThread(mid, visitor.getUid())) {
@@ -585,7 +586,7 @@ public class Site {
}
@GetMapping("/post")
- protected String postAction(@ModelAttribute User visitor, @RequestParam(required = false) String body, ModelMap model) {
+ protected String postAction(@Parameter(hidden = true) User visitor, @RequestParam(required = false) String body, ModelMap model) {
fillUserModel(model, visitor, visitor);
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
model.addAttribute("title", "Написать");
diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java
index 4e26ab99..b43b65c6 100644
--- a/src/main/java/com/juick/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/www/controllers/SocialLogin.java
@@ -193,7 +193,7 @@ public class SocialLogin {
}
@GetMapping("/_twitter")
- protected void doTwitterLogin(@ModelAttribute com.juick.model.User user, HttpServletRequest request,
+ protected void doTwitterLogin(com.juick.model.User user, HttpServletRequest request,
HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
String hash = StringUtils.EMPTY, request_token = StringUtils.EMPTY, request_token_secret = StringUtils.EMPTY;
String verifier = request.getParameter("oauth_verifier");
diff --git a/src/main/java/com/juick/www/rss/Feeds.java b/src/main/java/com/juick/www/rss/Feeds.java
index 41b006a6..4e64bc5a 100644
--- a/src/main/java/com/juick/www/rss/Feeds.java
+++ b/src/main/java/com/juick/www/rss/Feeds.java
@@ -22,6 +22,7 @@ import com.juick.util.HttpNotFoundException;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
+import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -49,7 +50,7 @@ public class Feeds {
}
@GetMapping("/rss/{userName}/{feedType}")
- public ModelAndView getBlog(@ModelAttribute User visitor, @PathVariable String userName, @PathVariable FeedType feedType) {
+ public ModelAndView getBlog(@Parameter(hidden = true) User visitor, @PathVariable String userName, @PathVariable FeedType feedType) {
User user = userService.getUserByName(userName);
if (!user.isAnonymous() && !user.isBanned()) {
List mids = feedType == FeedType.blog ? messagesService.getUserBlog(user.getUid(), 0, 0) : messagesService.getUserBlogWithRecommendations(user, visitor, 0, 0);
@@ -65,11 +66,12 @@ public class Feeds {
@GetMapping("/rss/")
public ModelAndView getLast(
- @ModelAttribute User visitor,
+ @Parameter(hidden = true) User visitor,
@RequestParam(value = "hours", required = false, defaultValue = "0") Integer hours) {
List mids = messagesService.getLastMessages(hours);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("messagesView");
+ modelAndView.addObject("user", visitor);
modelAndView.addObject("messages", messagesService.getMessages(visitor, mids));
return modelAndView;
}
--
cgit v1.2.3