initial commit
This commit is contained in:
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>de.bstly.we</groupId>
|
||||
<artifactId>webstly-main</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>urlshortener</name>
|
||||
<artifactId>webstly-urlshortener</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.bstly.we</groupId>
|
||||
<artifactId>webstly-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.businesslogic;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
|
||||
import de.bstly.we.businesslogic.QuotaManager;
|
||||
import de.bstly.we.businesslogic.SystemPropertyManager;
|
||||
import de.bstly.we.businesslogic.UserDataProvider;
|
||||
import de.bstly.we.model.Quota;
|
||||
import de.bstly.we.model.UserData;
|
||||
import de.bstly.we.urlshortener.model.QShortenedUrl;
|
||||
import de.bstly.we.urlshortener.model.ShortenedUrl;
|
||||
import de.bstly.we.urlshortener.repository.ShortenedUrlRepository;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class ShortenedUrlManager implements SmartInitializingSingleton, UserDataProvider {
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
@Autowired
|
||||
private SystemPropertyManager systemPropertyManager;
|
||||
@Autowired
|
||||
private QuotaManager quotaManager;
|
||||
@Autowired
|
||||
private ShortenedUrlRepository shortenedUrlRepository;
|
||||
private QShortenedUrl qShortenedUrl = QShortenedUrl.shortenedUrl;
|
||||
|
||||
private int codeLength;
|
||||
|
||||
public static final String SYSTEM_PROPERTY_URL_SHORTENER_CODE_LENGTH = "urlShortener.codeLength";
|
||||
public static final String SYSTEM_PROPERTY_URL_SHORTENER_LINK = "urlShortener.link";
|
||||
public static final String SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND = "urlShortener.notFoundRedirect";
|
||||
public static final String SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD = "urlShortener.passwordRedirect";
|
||||
|
||||
/*
|
||||
* @see org.springframework.beans.factory.SmartInitializingSingleton#
|
||||
* afterSingletonsInstantiated()
|
||||
*/
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
if (!systemPropertyManager.has(SYSTEM_PROPERTY_URL_SHORTENER_CODE_LENGTH)) {
|
||||
systemPropertyManager.add(SYSTEM_PROPERTY_URL_SHORTENER_CODE_LENGTH,
|
||||
environment.getProperty("we.bstly.urlShortener.codeLength", "6"));
|
||||
}
|
||||
if (!systemPropertyManager.has(SYSTEM_PROPERTY_URL_SHORTENER_LINK)) {
|
||||
systemPropertyManager.add(SYSTEM_PROPERTY_URL_SHORTENER_LINK,
|
||||
environment.getProperty("we.bstly.urlShortener.link", ""));
|
||||
}
|
||||
if (!systemPropertyManager.has(SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND)) {
|
||||
systemPropertyManager.add(SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND,
|
||||
environment.getProperty("we.bstly.urlShortener.notFoundRedirect", ""));
|
||||
}
|
||||
if (!systemPropertyManager.has(SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD)) {
|
||||
systemPropertyManager.add(SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD,
|
||||
environment.getProperty("we.bstly.urlShortener.passwordRedirect", ""));
|
||||
}
|
||||
|
||||
codeLength = systemPropertyManager.getInteger(SYSTEM_PROPERTY_URL_SHORTENER_CODE_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public ShortenedUrl get(String code) {
|
||||
return shortenedUrlRepository.findById(code).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
public List<ShortenedUrl> getAllByOwner(Long userId) {
|
||||
return Lists.newArrayList(shortenedUrlRepository.findAll(qShortenedUrl.owner.eq(userId)));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param page
|
||||
* @param size
|
||||
* @param sortBy
|
||||
* @param descending
|
||||
* @param search
|
||||
* @return
|
||||
*/
|
||||
public Page<ShortenedUrl> get(int page, int size, String sortBy, boolean descending,
|
||||
String search) {
|
||||
if (StringUtils.hasText(search)) {
|
||||
return shortenedUrlRepository.findAll(
|
||||
qShortenedUrl.note.containsIgnoreCase(search)
|
||||
.or(qShortenedUrl.url.containsIgnoreCase(search))
|
||||
.or(qShortenedUrl.link.containsIgnoreCase(search)),
|
||||
PageRequest.of(page, size, descending ? Sort.by(sortBy).descending()
|
||||
: Sort.by(sortBy).ascending()));
|
||||
}
|
||||
|
||||
return shortenedUrlRepository.findAll(PageRequest.of(page, size,
|
||||
descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending()));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param page
|
||||
* @param size
|
||||
* @param sortBy
|
||||
* @param descending
|
||||
* @param search
|
||||
* @return
|
||||
*/
|
||||
public Page<ShortenedUrl> getForUserId(Long userId, int page, int size, String sortBy,
|
||||
boolean descending, String search) {
|
||||
// delete expired urls
|
||||
shortenedUrlRepository.deleteAll(shortenedUrlRepository.findAll(
|
||||
qShortenedUrl.owner.eq(userId).and(qShortenedUrl.expires.before(Instant.now()))));
|
||||
|
||||
BooleanBuilder query = new BooleanBuilder();
|
||||
|
||||
query.and(qShortenedUrl.owner.eq(userId));
|
||||
|
||||
if (StringUtils.hasText(search)) {
|
||||
query.and(qShortenedUrl.note.containsIgnoreCase(search)
|
||||
.or(qShortenedUrl.url.containsIgnoreCase(search))
|
||||
.or(qShortenedUrl.link.containsIgnoreCase(search)));
|
||||
}
|
||||
|
||||
return shortenedUrlRepository.findAll(query.getValue(), PageRequest.of(page, size,
|
||||
descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending()));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param owner
|
||||
* @param url
|
||||
* @param code
|
||||
* @param expires
|
||||
* @param password
|
||||
* @param quota
|
||||
* @return
|
||||
*/
|
||||
public ShortenedUrl create(Long owner, String url, String note, String code, Instant expires,
|
||||
String password, boolean queryParameters, boolean quota) {
|
||||
ShortenedUrl shortenedUrl = new ShortenedUrl();
|
||||
|
||||
shortenedUrl.setOwner(owner);
|
||||
shortenedUrl.setUrl(url);
|
||||
shortenedUrl.setNote(note);
|
||||
|
||||
if (StringUtils.hasText(code)) {
|
||||
Assert.isTrue(!shortenedUrlRepository.existsById(code), "Given code already exists!");
|
||||
} else {
|
||||
code = RandomStringUtils.random(codeLength, true, true).toUpperCase();
|
||||
while (shortenedUrlRepository.existsById(code)) {
|
||||
code = RandomStringUtils.random(codeLength, true, true).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
shortenedUrl.setCode(code);
|
||||
shortenedUrl.setExpires(expires);
|
||||
shortenedUrl.setQueryParameters(queryParameters);
|
||||
|
||||
if (StringUtils.hasText(password)) {
|
||||
shortenedUrl.setPasswordHash(passwordEncoder.encode(password));
|
||||
}
|
||||
|
||||
String link = systemPropertyManager.get(SYSTEM_PROPERTY_URL_SHORTENER_LINK);
|
||||
if (StringUtils.hasText(link)) {
|
||||
shortenedUrl.setLink(String.format(link, shortenedUrl.getCode()));
|
||||
}
|
||||
|
||||
shortenedUrl = shortenedUrlRepository.save(shortenedUrl);
|
||||
|
||||
if (quota) {
|
||||
Quota shortenedUrlsQuota = quotaManager.get(shortenedUrl.getOwner(),
|
||||
ShortenedUrlQuotas.URL_SHORTENER);
|
||||
if (shortenedUrlsQuota != null) {
|
||||
shortenedUrlsQuota.setValue(shortenedUrlsQuota.getValue() - 1);
|
||||
quotaManager.update(shortenedUrlsQuota);
|
||||
}
|
||||
}
|
||||
|
||||
return shortenedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrl
|
||||
* @return
|
||||
*/
|
||||
public ShortenedUrl save(ShortenedUrl shortenedUrl) {
|
||||
|
||||
String link = systemPropertyManager.get(SYSTEM_PROPERTY_URL_SHORTENER_LINK);
|
||||
if (StringUtils.hasText(link)) {
|
||||
shortenedUrl.setLink(String.format(link, shortenedUrl.getCode()));
|
||||
}
|
||||
|
||||
return shortenedUrlRepository.save(shortenedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrl
|
||||
* @param quota
|
||||
*/
|
||||
public void delete(ShortenedUrl shortenedUrl, boolean quota) {
|
||||
if (quota) {
|
||||
Quota shortenedUrlsQuota = quotaManager.get(shortenedUrl.getOwner(),
|
||||
ShortenedUrlQuotas.URL_SHORTENER);
|
||||
if (shortenedUrlsQuota == null) {
|
||||
shortenedUrlsQuota = quotaManager.create(shortenedUrl.getOwner(),
|
||||
ShortenedUrlQuotas.URL_SHORTENER, 0, "#", true);
|
||||
}
|
||||
|
||||
shortenedUrlsQuota.setValue(shortenedUrlsQuota.getValue() + 1);
|
||||
quotaManager.update(shortenedUrlsQuota);
|
||||
}
|
||||
|
||||
shortenedUrlRepository.delete(shortenedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param owner
|
||||
* @param quota
|
||||
*/
|
||||
public void deleteAll(Long owner, boolean quota) {
|
||||
List<ShortenedUrl> shortenedUrls = Lists
|
||||
.newArrayList(shortenedUrlRepository.findAll(qShortenedUrl.owner.eq(owner)));
|
||||
|
||||
for (ShortenedUrl shortenedUrl : shortenedUrls) {
|
||||
delete(shortenedUrl, quota);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see de.bstly.we.businesslogic.UserDataProvider#getId()
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return "shortend-urls";
|
||||
}
|
||||
|
||||
/*
|
||||
* @see de.bstly.we.businesslogic.UserDataProvider#getUserData(java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public List<UserData> getUserData(Long userId) {
|
||||
List<UserData> result = Lists.newArrayList();
|
||||
for (ShortenedUrl shortenedUrl : getAllByOwner(userId)) {
|
||||
result.add(shortenedUrl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see de.bstly.we.businesslogic.UserDataProvider#purgeUserData(java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public void purgeUserData(Long userId) {
|
||||
for (ShortenedUrl shortenedUrl : getAllByOwner(userId)) {
|
||||
shortenedUrlRepository.delete(shortenedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Scheduled(cron = "0 */5 * * * *")
|
||||
public void clearOrUpdateShortenedUrls() {
|
||||
shortenedUrlRepository.deleteAll(
|
||||
shortenedUrlRepository.findAll(qShortenedUrl.expires.before(Instant.now())));
|
||||
|
||||
String link = systemPropertyManager.get(SYSTEM_PROPERTY_URL_SHORTENER_LINK);
|
||||
String linkRegex = "";
|
||||
if (StringUtils.hasText(link)) {
|
||||
linkRegex = String.format(link, "%");
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(linkRegex)) {
|
||||
for (ShortenedUrl shortenedUrl : shortenedUrlRepository
|
||||
.findAll(qShortenedUrl.link.isNull().or(qShortenedUrl.link.isEmpty()
|
||||
.or(qShortenedUrl.link.matches(linkRegex).not())))) {
|
||||
shortenedUrl.setLink(String.format(link, shortenedUrl.getCode()));
|
||||
shortenedUrl = shortenedUrlRepository.save(shortenedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.businesslogic;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public interface ShortenedUrlPermissions {
|
||||
public static final String URL_SHORTENER = "url_shortener";
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.businesslogic;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public interface ShortenedUrlQuotas {
|
||||
|
||||
public static final String URL_SHORTENER = "url_shortener";
|
||||
}
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.bstly.we.businesslogic.PermissionManager;
|
||||
import de.bstly.we.businesslogic.QuotaManager;
|
||||
import de.bstly.we.businesslogic.SystemPropertyManager;
|
||||
import de.bstly.we.controller.BaseController;
|
||||
import de.bstly.we.controller.support.EntityResponseStatusException;
|
||||
import de.bstly.we.controller.support.RequestBodyErrors;
|
||||
import de.bstly.we.model.Quota;
|
||||
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlManager;
|
||||
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlPermissions;
|
||||
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlQuotas;
|
||||
import de.bstly.we.urlshortener.controller.model.ShortenedUrlModel;
|
||||
import de.bstly.we.urlshortener.controller.validation.ShortenedUrlModelValidator;
|
||||
import de.bstly.we.urlshortener.model.ShortenedUrl;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/url/shortener")
|
||||
public class ShortenedUrlController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ShortenedUrlManager shortenedUrlManager;
|
||||
@Autowired
|
||||
private PermissionManager permissionManager;
|
||||
@Autowired
|
||||
private QuotaManager quotaManager;
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Autowired
|
||||
private SystemPropertyManager systemPropertyManager;
|
||||
@Autowired
|
||||
private ShortenedUrlModelValidator shortenedUrlModelValidator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
@GetMapping("/{code}")
|
||||
public void getShortenedUrlLink(@PathVariable("code") String code, HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
|
||||
try {
|
||||
if (shortenedUrl == null) {
|
||||
if (StringUtils.hasText(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND))) {
|
||||
response.sendRedirect(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND));
|
||||
} else {
|
||||
response.sendError(404, "code not found");
|
||||
}
|
||||
} else if (StringUtils.hasText(shortenedUrl.getPasswordHash())) {
|
||||
if (StringUtils.hasText(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD))) {
|
||||
response.sendRedirect(String.format(
|
||||
systemPropertyManager.get(
|
||||
ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD),
|
||||
shortenedUrl.getCode()));
|
||||
} else {
|
||||
response.sendError(401, "password protected");
|
||||
}
|
||||
} else {
|
||||
sendValidRedirect(shortenedUrl, request, response);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
response.sendError(400, code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
* @param password
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
@PostMapping("/{code}")
|
||||
public void getProtectedShortenedUrlLink(@PathVariable("code") String code,
|
||||
@ModelAttribute("password") String password, HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
|
||||
try {
|
||||
if (shortenedUrl == null) {
|
||||
if (StringUtils.hasText(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND))) {
|
||||
response.sendRedirect(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_NOT_FOUND));
|
||||
} else {
|
||||
response.sendError(404, "code not found");
|
||||
}
|
||||
} else if (!StringUtils.hasText(shortenedUrl.getPasswordHash())
|
||||
|| passwordEncoder.matches(password, shortenedUrl.getPasswordHash())) {
|
||||
sendValidRedirect(shortenedUrl, request, response);
|
||||
} else {
|
||||
if (StringUtils.hasText(systemPropertyManager
|
||||
.get(ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD))) {
|
||||
String passwordErrorUrl = String.format(
|
||||
systemPropertyManager.get(
|
||||
ShortenedUrlManager.SYSTEM_PROPERTY_URL_SHORTENER_PASSWORD),
|
||||
shortenedUrl.getCode());
|
||||
URI passwordErrorUri;
|
||||
try {
|
||||
passwordErrorUri = new URI(passwordErrorUrl);
|
||||
if (StringUtils.hasText(passwordErrorUri.getQuery())) {
|
||||
passwordErrorUrl += "&error";
|
||||
} else {
|
||||
passwordErrorUrl += "?error";
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
|
||||
}
|
||||
|
||||
response.sendRedirect(passwordErrorUrl);
|
||||
} else {
|
||||
response.sendError(401, "password protected");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
response.sendError(400, code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrl
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void sendValidRedirect(ShortenedUrl shortenedUrl, HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
String url = shortenedUrl.getUrl();
|
||||
if (shortenedUrl.isQueryParameters() && StringUtils.hasText(request.getQueryString())) {
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
if (StringUtils.hasText(uri.getQuery())) {
|
||||
url += "&" + request.getQueryString();
|
||||
} else {
|
||||
url += "?" + request.getQueryString();
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
}
|
||||
}
|
||||
|
||||
response.sendRedirect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/model/{code}")
|
||||
public ShortenedUrl getShortenedUrl(@PathVariable("code") String code) {
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
if (shortenedUrl == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(shortenedUrl.getPasswordHash())) {
|
||||
shortenedUrl.setUrl(null);
|
||||
shortenedUrl.setPasswordHash(null);
|
||||
}
|
||||
|
||||
if (!shortenedUrl.getOwner().equals(getCurrentUserId())) {
|
||||
shortenedUrl.setOwner(null);
|
||||
shortenedUrl.setNote(null);
|
||||
}
|
||||
|
||||
return shortenedUrl;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping
|
||||
public Page<ShortenedUrl> getShortenedUrls(
|
||||
@RequestParam("page") Optional<Integer> pageParameter,
|
||||
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||
@RequestParam("sort") Optional<String> sortParameter,
|
||||
@RequestParam("desc") Optional<Boolean> descParameter,
|
||||
@RequestParam("search") Optional<String> searchParameter) {
|
||||
if (!permissionManager.hasPermission(getCurrentUserId(),
|
||||
ShortenedUrlPermissions.URL_SHORTENER)) {
|
||||
shortenedUrlManager.deleteAll(getCurrentUserId(), true);
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return shortenedUrlManager.getForUserId(getCurrentUserId(), pageParameter.orElse(0),
|
||||
sizeParameter.orElse(10), sortParameter.orElse("code"), descParameter.orElse(false),
|
||||
searchParameter.orElse(""));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrlModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@PostMapping
|
||||
public ShortenedUrl createShortenedUrl(@RequestBody ShortenedUrlModel shortenedUrlModel) {
|
||||
if (!permissionManager.hasPermission(getCurrentUserId(),
|
||||
ShortenedUrlPermissions.URL_SHORTENER)
|
||||
|| !permissionManager.isFullUser(getCurrentUserId())) {
|
||||
shortenedUrlManager.deleteAll(getCurrentUserId(), true);
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
Quota shortenedUrlsQuota = quotaManager.get(getCurrentUserId(),
|
||||
ShortenedUrlQuotas.URL_SHORTENER);
|
||||
if (shortenedUrlsQuota == null || shortenedUrlsQuota.getValue() < 1) {
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
Errors errors = new RequestBodyErrors(shortenedUrlModel);
|
||||
|
||||
shortenedUrlModelValidator.validate(shortenedUrlModel, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
return shortenedUrlManager.create(getCurrentUserId(), shortenedUrlModel.getUrl(),
|
||||
shortenedUrlModel.getNote(), shortenedUrlModel.getCode(),
|
||||
shortenedUrlModel.getExpires(), shortenedUrlModel.getPassword(),
|
||||
shortenedUrlModel.isQueryParameters(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrlModel
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@PatchMapping
|
||||
public ShortenedUrl updateShortenedUrl(@RequestBody ShortenedUrlModel shortenedUrlModel) {
|
||||
if (!permissionManager.hasPermission(getCurrentUserId(),
|
||||
ShortenedUrlPermissions.URL_SHORTENER)
|
||||
|| !permissionManager.isFullUser(getCurrentUserId())) {
|
||||
shortenedUrlManager.deleteAll(getCurrentUserId(), true);
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
String code = shortenedUrlModel.getCode();
|
||||
boolean newShortenedUrl = false;
|
||||
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
|
||||
if (shortenedUrl == null || !shortenedUrl.getOwner().equals(getCurrentUserId())) {
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
Errors errors = new RequestBodyErrors(shortenedUrlModel);
|
||||
|
||||
if (StringUtils.hasText(shortenedUrlModel.getNewCode())
|
||||
&& !shortenedUrlModel.getNewCode().equals(code)) {
|
||||
shortenedUrlModel.setCode(shortenedUrlModel.getNewCode());
|
||||
newShortenedUrl = true;
|
||||
} else {
|
||||
shortenedUrlModel.setCode(null);
|
||||
}
|
||||
|
||||
shortenedUrlModelValidator.validate(shortenedUrlModel, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
if (newShortenedUrl) {
|
||||
String passwordHash = shortenedUrl.getPasswordHash();
|
||||
shortenedUrlManager.delete(shortenedUrl, false);
|
||||
shortenedUrl = new ShortenedUrl();
|
||||
shortenedUrl.setCode(shortenedUrlModel.getCode());
|
||||
shortenedUrl.setPasswordHash(passwordHash);
|
||||
shortenedUrl.setOwner(getCurrentUserId());
|
||||
}
|
||||
|
||||
shortenedUrl.setQueryParameters(shortenedUrlModel.isQueryParameters());
|
||||
shortenedUrl.setExpires(shortenedUrlModel.getExpires());
|
||||
shortenedUrl.setUrl(shortenedUrlModel.getUrl());
|
||||
shortenedUrl.setNote(shortenedUrlModel.getNote());
|
||||
|
||||
if (shortenedUrlModel.isNewPassword()) {
|
||||
if (StringUtils.hasText(shortenedUrlModel.getPassword())) {
|
||||
shortenedUrl
|
||||
.setPasswordHash(passwordEncoder.encode(shortenedUrlModel.getPassword()));
|
||||
} else {
|
||||
shortenedUrl.setPasswordHash(null);
|
||||
}
|
||||
}
|
||||
|
||||
return shortenedUrlManager.save(shortenedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@DeleteMapping("/{code}")
|
||||
public void deleteShortenedUrl(@PathVariable("code") String code) {
|
||||
if (!permissionManager.hasPermission(getCurrentUserId(),
|
||||
ShortenedUrlPermissions.URL_SHORTENER)) {
|
||||
shortenedUrlManager.deleteAll(getCurrentUserId(), true);
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
if (shortenedUrl == null || !shortenedUrl.getOwner().equals(getCurrentUserId())) {
|
||||
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
shortenedUrlManager.delete(shortenedUrl, true);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.bstly.we.controller.BaseController;
|
||||
import de.bstly.we.controller.support.EntityResponseStatusException;
|
||||
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlManager;
|
||||
import de.bstly.we.urlshortener.model.ShortenedUrl;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/url/shortener/manage")
|
||||
public class ShortenedUrlManagementController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ShortenedUrlManager shortenedUrlManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping
|
||||
public Page<ShortenedUrl> getShortenedUrls(
|
||||
@RequestParam("page") Optional<Integer> pageParameter,
|
||||
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||
@RequestParam("search") Optional<String> searchParameter) {
|
||||
return shortenedUrlManager.get(pageParameter.orElse(0), sizeParameter.orElse(10), "code",
|
||||
true, searchParameter.orElse(""));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortenedUrl
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping
|
||||
public ShortenedUrl createOrUpdateShortenedUrl(@RequestBody ShortenedUrl shortenedUrl) {
|
||||
return shortenedUrlManager.save(shortenedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@DeleteMapping("/{code}")
|
||||
public void deleteShortenedUrl(@PathVariable("code") String code,
|
||||
@RequestParam("quota") Optional<Boolean> quota) {
|
||||
|
||||
ShortenedUrl shortenedUrl = shortenedUrlManager.get(code);
|
||||
if (shortenedUrl == null) {
|
||||
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
shortenedUrlManager.delete(shortenedUrl, quota.isPresent() && quota.get().booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param owner
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@DeleteMapping("/all/{owner}")
|
||||
public void deleteAll(@PathVariable("owner") Long owner,
|
||||
@RequestParam("quota") Optional<Boolean> quota) {
|
||||
shortenedUrlManager.deleteAll(owner, quota.isPresent() && quota.get().booleanValue());
|
||||
}
|
||||
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.controller.model;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
public class ShortenedUrlModel {
|
||||
|
||||
private String code;
|
||||
private String newCode;
|
||||
private String url;
|
||||
private String note;
|
||||
private Instant expires;
|
||||
private String password;
|
||||
private String password2;
|
||||
private boolean queryParameters;
|
||||
private boolean newPassword;
|
||||
|
||||
/**
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param code the code to set
|
||||
*/
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newCode
|
||||
*/
|
||||
public String getNewCode() {
|
||||
return newCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newCode the newCode to set
|
||||
*/
|
||||
public void setNewCode(String newCode) {
|
||||
this.newCode = newCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the url
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the url to set
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the note
|
||||
*/
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param note the note to set
|
||||
*/
|
||||
public void setNote(String note) {
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expires
|
||||
*/
|
||||
public Instant getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expires the expires to set
|
||||
*/
|
||||
public void setExpires(Instant expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password the password to set
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the password2
|
||||
*/
|
||||
public String getPassword2() {
|
||||
return password2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password2 the password2 to set
|
||||
*/
|
||||
public void setPassword2(String password2) {
|
||||
this.password2 = password2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the queryParameters
|
||||
*/
|
||||
public boolean isQueryParameters() {
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param queryParameters the queryParameters to set
|
||||
*/
|
||||
public void setQueryParameters(boolean queryParameters) {
|
||||
this.queryParameters = queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newPassword
|
||||
*/
|
||||
public boolean isNewPassword() {
|
||||
return newPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newPassword the newPassword to set
|
||||
*/
|
||||
public void setNewPassword(boolean newPassword) {
|
||||
this.newPassword = newPassword;
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.controller.validation;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.apache.commons.validator.routines.UrlValidator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlManager;
|
||||
import de.bstly.we.urlshortener.controller.model.ShortenedUrlModel;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class ShortenedUrlModelValidator implements Validator {
|
||||
|
||||
@Autowired
|
||||
private ShortenedUrlManager shortenedUrlManager;
|
||||
private UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_ALL_SCHEMES);
|
||||
protected static final String codePart = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\\\.[A-Z0-9_!#$%&'*+/=?`{|}~^-]+)*$";
|
||||
|
||||
/*
|
||||
* @see org.springframework.validation.Validator#supports(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return clazz.isAssignableFrom(ShortenedUrlModel.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.validation.Validator#validate(java.lang.Object,
|
||||
* org.springframework.validation.Errors)
|
||||
*/
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
ShortenedUrlModel shortenedUrlModel = (ShortenedUrlModel) target;
|
||||
|
||||
if (!urlValidator.isValid(shortenedUrlModel.getUrl())) {
|
||||
errors.rejectValue("url", "INVALID_URL");
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(shortenedUrlModel.getCode())) {
|
||||
if (shortenedUrlManager.get(shortenedUrlModel.getCode()) != null) {
|
||||
errors.rejectValue("code", "NOT_VALID");
|
||||
} else if (!shortenedUrlModel.getCode().matches(codePart)) {
|
||||
errors.rejectValue("code", "NOT_VALID");
|
||||
}
|
||||
}
|
||||
|
||||
if (shortenedUrlModel.getExpires() != null
|
||||
&& shortenedUrlModel.getExpires().isBefore(Instant.now())) {
|
||||
errors.rejectValue("expires", "NOT_VALID");
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(shortenedUrlModel.getPassword())
|
||||
&& !(shortenedUrlModel.getPassword().equals(shortenedUrlModel.getPassword2()))) {
|
||||
errors.rejectValue("password", "NOT_MATCH");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.model;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import de.bstly.we.businesslogic.support.AbstractModelEventListener;
|
||||
import de.bstly.we.model.AbstractModel;
|
||||
import de.bstly.we.model.UserData;
|
||||
|
||||
/**
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "shortened_urls")
|
||||
@EntityListeners(AbstractModelEventListener.class)
|
||||
public class ShortenedUrl implements UserData, AbstractModel {
|
||||
|
||||
@Id
|
||||
@Column(name = "code")
|
||||
private String code;
|
||||
private Long owner;
|
||||
@Lob
|
||||
private String url;
|
||||
private Instant expires;
|
||||
@JsonIgnore
|
||||
@Column(name = "password", nullable = true)
|
||||
private String passwordHash;
|
||||
private String link;
|
||||
@Lob
|
||||
private String note;
|
||||
@Column(name = "addon", columnDefinition = "boolean default false")
|
||||
private boolean queryParameters;
|
||||
@JsonProperty("password")
|
||||
@Transient
|
||||
private boolean hasPassword;
|
||||
|
||||
/**
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param code the code to set
|
||||
*/
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the owner
|
||||
*/
|
||||
public Long getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param owner the owner to set
|
||||
*/
|
||||
public void setOwner(Long owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the url
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the url to set
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expires
|
||||
*/
|
||||
public Instant getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expires the expires to set
|
||||
*/
|
||||
public void setExpires(Instant expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the passwordHash
|
||||
*/
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param passwordHash the passwordHash to set
|
||||
*/
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the link
|
||||
*/
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param link the link to set
|
||||
*/
|
||||
public void setLink(String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the note
|
||||
*/
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param note the note to set
|
||||
*/
|
||||
public void setNote(String note) {
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the queryParameters
|
||||
*/
|
||||
public boolean isQueryParameters() {
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param queryParameters the queryParameters to set
|
||||
*/
|
||||
public void setQueryParameters(boolean queryParameters) {
|
||||
this.queryParameters = queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the hasPassword
|
||||
*/
|
||||
public boolean isHasPassword() {
|
||||
return StringUtils.hasText(passwordHash);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package de.bstly.we.urlshortener.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import de.bstly.we.urlshortener.model.ShortenedUrl;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author _bastler@bstly.de
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public interface ShortenedUrlRepository
|
||||
extends JpaRepository<ShortenedUrl, String>, QuerydslPredicateExecutor<ShortenedUrl> {
|
||||
}
|
||||
Reference in New Issue
Block a user