diff --git a/pom.xml b/pom.xml index f6c152c68..bb2052755 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ org.projectlombok lombok true + 1.18.30 org.mapstruct @@ -142,6 +143,12 @@ junit-platform-launcher test + + + com.h2database + h2 + runtime + diff --git a/resources/mails/email-confirmation.html b/resources/mails/email-confirmation.html index 106e6129a..687157cd9 100644 --- a/resources/mails/email-confirmation.html +++ b/resources/mails/email-confirmation.html @@ -1,13 +1,12 @@ - + - JiraRush - подтверждение почты + -

-

Чтобы завершить настройку учетной записи и начать пользоваться JiraRush, подтвердите, что вы правильно указали вашу - электронную почту.

-Подтвердить почту +

+

+ \ No newline at end of file diff --git a/resources/view/index.html b/resources/view/index.html index e8656ef96..bc32596ff 100644 --- a/resources/view/index.html +++ b/resources/view/index.html @@ -1,13 +1,15 @@ - - - - + + -

JiraRush Home page

+

JiraRush Home page

+
+ English | + Русский +
- +
diff --git a/resources/view/login.html b/resources/view/login.html index 8765ca8ff..d49ce5691 100644 --- a/resources/view/login.html +++ b/resources/view/login.html @@ -48,14 +48,6 @@

Sign in

type="button"> - - - - - - diff --git a/resources/view/unauth/register.html b/resources/view/unauth/register.html index 2ba955045..63c5c8bc3 100644 --- a/resources/view/unauth/register.html +++ b/resources/view/unauth/register.html @@ -77,14 +77,7 @@

Registration

type="button"> - - - - - - + diff --git a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java index 6cffbe175..2b79ce414 100644 --- a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java +++ b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java @@ -25,14 +25,18 @@ public static void upload(MultipartFile multipartFile, String directoryPath, Str throw new IllegalRequestDataException("Select a file to upload."); } - File dir = new File(directoryPath); - if (dir.exists() || dir.mkdirs()) { - File file = new File(directoryPath + fileName); - try (OutputStream outStream = new FileOutputStream(file)) { - outStream.write(multipartFile.getBytes()); - } catch (IOException ex) { - throw new IllegalRequestDataException("Failed to upload file" + multipartFile.getOriginalFilename()); + try { + Path dir = Paths.get(directoryPath); + if (Files.notExists(dir)) { + Files.createDirectories(dir); } + + Path filePath = dir.resolve(fileName); + + Files.write(filePath, multipartFile.getBytes()); + + } catch (IOException ex) { + throw new IllegalRequestDataException("Failed to upload file " + multipartFile.getOriginalFilename()); } } diff --git a/src/main/java/com/javarush/jira/common/internal/config/AppConfig.java b/src/main/java/com/javarush/jira/common/internal/config/AppConfig.java index fbc6ef804..2a6b94de0 100644 --- a/src/main/java/com/javarush/jira/common/internal/config/AppConfig.java +++ b/src/main/java/com/javarush/jira/common/internal/config/AppConfig.java @@ -5,18 +5,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.hibernate5.jakarta.Hibernate5JakartaModule; import com.javarush.jira.common.util.JsonUtil; +import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.http.ProblemDetail; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import javax.sql.DataSource; import java.util.Map; import java.util.concurrent.Executor; @@ -52,6 +56,16 @@ public boolean isTest() { return env.acceptsProfiles(Profiles.of("test")); } + @Profile("prod") + public DataSource prodDataSource() { + return DataSourceBuilder.create().build(); + } + + @Profile("test") + public DataSource testDataSource() { + return DataSourceBuilder.create().build(); + } + @Autowired void configureAndStoreObjectMapper(ObjectMapper objectMapper) { objectMapper.registerModule(new Hibernate5JakartaModule()); diff --git a/src/main/java/com/javarush/jira/common/internal/config/MvcConfig.java b/src/main/java/com/javarush/jira/common/internal/config/MvcConfig.java index 8a434a807..2803fc2ce 100644 --- a/src/main/java/com/javarush/jira/common/internal/config/MvcConfig.java +++ b/src/main/java/com/javarush/jira/common/internal/config/MvcConfig.java @@ -7,21 +7,26 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.ui.ModelMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.filter.ForwardedHeaderFilter; import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; import org.springframework.web.servlet.mvc.UrlFilenameViewController; import java.time.Duration; +import java.util.Locale; import java.util.Properties; //@EnableWebMvc : http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration @@ -32,6 +37,29 @@ public class MvcConfig implements WebMvcConfigurer { private final AppProperties appProperties; + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver localeResolver = new SessionLocaleResolver(); + localeResolver.setDefaultLocale(Locale.ENGLISH); + return localeResolver; + } + + @Bean + public ReloadableResourceBundleMessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasename("classpath:messages"); + messageSource.setDefaultEncoding("UTF-8"); + return messageSource; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); + interceptor.setParamName("lang"); + return interceptor; + } + // Add authUser to view model private final HandlerInterceptor authInterceptor = new WebRequestHandlerInterceptorAdapter(new WebRequestInterceptor() { @Override @@ -56,6 +84,7 @@ public void preHandle(WebRequest request) { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor).excludePathPatterns("/api/**"); + registry.addInterceptor(localeChangeInterceptor()); } // http://www.codejava.net/frameworks/spring/spring-mvc-url-based-view-resolution-with-urlfilenameviewcontroller-example @@ -79,6 +108,7 @@ ForwardedHeaderFilter forwardedHeaderFilter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); + } @Override diff --git a/src/main/java/com/javarush/jira/mail/MailService.java b/src/main/java/com/javarush/jira/mail/MailService.java index 1b1623138..b224ba2b9 100644 --- a/src/main/java/com/javarush/jira/mail/MailService.java +++ b/src/main/java/com/javarush/jira/mail/MailService.java @@ -10,6 +10,7 @@ import com.javarush.jira.mail.internal.MailCaseRepository; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletRequest; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,6 +20,8 @@ import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import org.thymeleaf.context.Context; import org.thymeleaf.spring6.SpringTemplateEngine; @@ -29,7 +32,6 @@ @Service @RequiredArgsConstructor public class MailService { - private static final Locale LOCALE_RU = Locale.forLanguageTag("ru"); private static final String OK = "OK"; private final MailCaseRepository mailCaseRepository; @@ -50,20 +52,31 @@ public static boolean isOk(String result) { return OK.equals(result); } + private Locale determineUserLocale() { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String acceptLanguage = request.getHeader("Accept-Language"); + if (acceptLanguage != null && !acceptLanguage.isEmpty()) { + return Locale.forLanguageTag(acceptLanguage.split(",")[0]); + } + + return Locale.ENGLISH; + } + public String sendToUserWithParams(@NonNull String template, @NonNull User user, @NonNull Map params) { String email = Objects.requireNonNull(user.getEmail()); Map extParams = Util.mergeMap(params, Map.of("user", user)); - return send(appConfig.isProd() ? email : appProperties.getTestMail(), user.getFirstName(), template, extParams); + Locale locale = determineUserLocale(); + return send(appConfig.isProd() ? email : appProperties.getTestMail(), user.getFirstName(), template, extParams, locale); } - public String send(String toEmail, String toName, String template, Map params) { + public String send(String toEmail, String toName, String template, Map params, Locale locale) { log.debug("Send email to {}, {} with template {}", toEmail, toName, template); String result = OK; try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "UTF-8"); message.setFrom(email, "JiraRush"); - String content = getContent(template, params); + String content = getContent(template, params, locale); message.setText(content, true); message.setSubject(Util.getTitle(content)); // TODO calculate title for group emailing only once message.setTo(new InternetAddress(toEmail, toName, "UTF-8")); @@ -78,8 +91,8 @@ public String send(String toEmail, String toName, String template, Map params) { - Context context = new Context(LOCALE_RU, params); + private String getContent(String template, Map params, Locale locale) { + Context context = new Context(locale, params); return templateEngine.process(template, context); } diff --git a/src/main/resources/application-secrets.yaml b/src/main/resources/application-secrets.yaml new file mode 100644 index 000000000..beaf7793d --- /dev/null +++ b/src/main/resources/application-secrets.yaml @@ -0,0 +1,46 @@ +spring: + datasource: + url: jdbc:postgresql://localhost:5432/jira + username: jira + password: JiraRush + + security: + oauth2: + client: + registration: + github: + client-id: 3d0d8738e65881fff266 + client-secret: 0f97031ce6178b7dfb67a6af587f37e222a16120 + scope: + - email + google: + client-id: 329113642700-f8if6pu68j2repq3ef6umd5jgiliup60.apps.googleusercontent.com + client-secret: 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 + scope: + - email + - profile + gitlab: + client-id: b8520a3266089063c0d8261cce36971defa513f5ffd9f9b7a3d16728fc83a494 + client-secret: e72c65320cf9d6495984a37b0f9cc03ec46be0bb6f071feaebbfe75168117004 + client-name: GitLab + redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" + authorization-grant-type: authorization_code + scope: read_user + provider: + gitlab: + authorization-uri: https://gitlab.com/oauth/authorize + token-uri: https://gitlab.com/oauth/token + user-info-uri: https://gitlab.com/api/v4/user + user-name-attribute: email + + mail: + properties: + mail: + smtp: + starttls: + enable: true + auth: true + host: smtp.gmail.com + username: jira4jr@gmail.com + password: zdfzsrqvgimldzyj + port: 587 diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7fcba1570..be8319782 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,6 +8,8 @@ app: max-pool-size: 100 spring: + config: + import: optional:application-secrets.yaml init: mode: never jpa: @@ -25,10 +27,6 @@ spring: default_batch_fetch_size: 20 # https://stackoverflow.com/questions/21257819/what-is-the-difference-between-hibernate-jdbc-fetch-size-and-hibernate-jdbc-batc jdbc.batch_size: 20 - datasource: - url: jdbc:postgresql://localhost:5432/jira - username: jira - password: JiraRush liquibase: changeLog: "classpath:db/changelog.sql" @@ -46,74 +44,10 @@ spring: cache-names: users caffeine.spec: maximumSize=10000,expireAfterAccess=5m - security: - oauth2: - client: - registration: - github: - client-id: 3d0d8738e65881fff266 - client-secret: 0f97031ce6178b7dfb67a6af587f37e222a16120 - scope: - - email - google: - client-id: 329113642700-f8if6pu68j2repq3ef6umd5jgiliup60.apps.googleusercontent.com - client-secret: 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 - scope: - - email - - profile - vk: - client-id: 51562377 - client-secret: jNM1YHQy1362Mqs49wUN - client-name: Vkontakte - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - client-authentication-method: client_secret_post - authorization-grant-type: authorization_code - scope: email - yandex: - client-id: 2f3395214ba84075956b76a34b231985 - client-secret: ed236c501e444a609b0f419e5e88f1e1 - client-name: Yandex - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - authorization-grant-type: authorization_code - gitlab: - client-id: b8520a3266089063c0d8261cce36971defa513f5ffd9f9b7a3d16728fc83a494 - client-secret: e72c65320cf9d6495984a37b0f9cc03ec46be0bb6f071feaebbfe75168117004 - client-name: GitLab - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - authorization-grant-type: authorization_code - scope: read_user - provider: - vk: - authorization-uri: https://oauth.vk.com/authorize - token-uri: https://oauth.vk.com/access_token - user-info-uri: https://api.vk.com/method/users.get?v=8.1 - user-name-attribute: response - yandex: - authorization-uri: https://oauth.yandex.ru/authorize - token-uri: https://oauth.yandex.ru/token - user-info-uri: https://login.yandex.ru/info - user-name-attribute: login - gitlab: - authorization-uri: https://gitlab.com/oauth/authorize - token-uri: https://gitlab.com/oauth/token - user-info-uri: https://gitlab.com/api/v4/user - user-name-attribute: email - sql: init: mode: always - mail: - properties: - mail: - smtp: - starttls: - enable: true - auth: true - host: smtp.gmail.com - username: jira4jr@gmail.com - password: zdfzsrqvgimldzyj - port: 587 thymeleaf.check-template-location: false mvc.throw-exception-if-no-handler-found: true diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties new file mode 100644 index 000000000..7ba8dd948 --- /dev/null +++ b/src/main/resources/messages_en.properties @@ -0,0 +1,8 @@ +email.confirmation.subject=JiraRush - Email Confirmation +email.confirmation.greeting=Hello, {0}. +email.confirmation.body=To complete setting up your account and start using JiraRush, please confirm your email. +email.confirmation.link=Confirm Email +home.title=JiraRush: mini bugtracking system +home.logout=Logout +home.language.en=English +home.language.ru=Russian \ No newline at end of file diff --git a/src/main/resources/messages_ru.properties b/src/main/resources/messages_ru.properties new file mode 100644 index 000000000..5cb75f163 --- /dev/null +++ b/src/main/resources/messages_ru.properties @@ -0,0 +1,8 @@ +email.confirmation.subject=JiraRush - \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b +email.confirmation.greeting=\u041f\u0440\u0438\u0432\u0435\u0442, {0}. +email.confirmation.body=\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0438 \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f JiraRush, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435, \u0447\u0442\u043e \u0432\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0432\u0430\u0448\u0443 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443\u044e \u043f\u043e\u0447\u0442\u0443. +email.confirmation.link=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043f\u043e\u0447\u0442\u0443 +home.title=JiraRush: \u043c\u0438\u043d\u0438 \u0431\u0430\u0433\u0442\u0440\u0435\u043a\u0435\u0440 +home.logout=\u0412\u044b\u0439\u0442\u0438 +home.language.en=\u0410\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 +home.language.ru=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \ No newline at end of file diff --git a/src/test/java/com/javarush/jira/AbstractControllerTest.java b/src/test/java/com/javarush/jira/AbstractControllerTest.java index 5981bae53..8fa9264de 100644 --- a/src/test/java/com/javarush/jira/AbstractControllerTest.java +++ b/src/test/java/com/javarush/jira/AbstractControllerTest.java @@ -9,7 +9,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; //https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications -@Sql(scripts = {"classpath:db/changelog.sql", "classpath:data.sql"}, config = @SqlConfig(encoding = "UTF-8")) +@Sql(scripts = {"classpath:changelog-test.sql", "classpath:data.sql"}, config = @SqlConfig(encoding = "UTF-8")) @AutoConfigureMockMvc //https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment public abstract class AbstractControllerTest extends BaseTests { diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 51137fd06..056d63a5d 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -1,8 +1,7 @@ spring.cache.type: none spring: - init: - mode: always datasource: - url: jdbc:postgresql://localhost:5433/jira-test - username: jira - password: JiraRush \ No newline at end of file + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE + driver-class-name: org.h2.Driver + liquibase: + changeLog: "classpath:changelog-test.sql" \ No newline at end of file diff --git a/src/test/resources/changelog-test.sql b/src/test/resources/changelog-test.sql new file mode 100644 index 000000000..d921d08d5 --- /dev/null +++ b/src/test/resources/changelog-test.sql @@ -0,0 +1,316 @@ +--liquibase formatted sql + +--changeset kmpk:init_schema +DROP TABLE IF EXISTS USER_ROLE; +DROP TABLE IF EXISTS CONTACT; +DROP TABLE IF EXISTS MAIL_CASE; +DROP SEQUENCE IF EXISTS MAIL_CASE_ID_SEQ; +DROP TABLE IF EXISTS PROFILE; +DROP TABLE IF EXISTS TASK_TAG; +DROP TABLE IF EXISTS USER_BELONG; +DROP SEQUENCE IF EXISTS USER_BELONG_ID_SEQ; +DROP TABLE IF EXISTS ACTIVITY; +DROP SEQUENCE IF EXISTS ACTIVITY_ID_SEQ; +DROP TABLE IF EXISTS TASK; +DROP SEQUENCE IF EXISTS TASK_ID_SEQ; +DROP TABLE IF EXISTS SPRINT; +DROP SEQUENCE IF EXISTS SPRINT_ID_SEQ; +DROP TABLE IF EXISTS PROJECT; +DROP SEQUENCE IF EXISTS PROJECT_ID_SEQ; +DROP TABLE IF EXISTS REFERENCE; +DROP SEQUENCE IF EXISTS REFERENCE_ID_SEQ; +DROP TABLE IF EXISTS ATTACHMENT; +DROP SEQUENCE IF EXISTS ATTACHMENT_ID_SEQ; +DROP TABLE IF EXISTS USERS; +DROP SEQUENCE IF EXISTS USERS_ID_SEQ; + +create table PROJECT +( + ID bigserial primary key, + CODE varchar(32) not null + constraint UK_PROJECT_CODE unique, + TITLE varchar(1024) not null, + DESCRIPTION varchar(4096) not null, + TYPE_CODE varchar(32) not null, + STARTPOINT timestamp, + ENDPOINT timestamp, + PARENT_ID bigint, + constraint FK_PROJECT_PARENT foreign key (PARENT_ID) references PROJECT (ID) on delete cascade +); + +create table MAIL_CASE +( + ID bigserial primary key, + EMAIL varchar(255) not null, + NAME varchar(255) not null, + DATE_TIME timestamp not null, + RESULT varchar(255) not null, + TEMPLATE varchar(255) not null +); + +create table SPRINT +( + ID bigserial primary key, + STATUS_CODE varchar(32) not null, + STARTPOINT timestamp, + ENDPOINT timestamp, + TITLE varchar(1024) not null, + PROJECT_ID bigint not null, + constraint FK_SPRINT_PROJECT foreign key (PROJECT_ID) references PROJECT (ID) on delete cascade +); + +create table REFERENCE +( + ID bigserial primary key, + CODE varchar(32) not null, + REF_TYPE smallint not null, + ENDPOINT timestamp, + STARTPOINT timestamp, + TITLE varchar(1024) not null, + AUX varchar, + constraint UK_REFERENCE_REF_TYPE_CODE unique (REF_TYPE, CODE) +); + +create table USERS +( + ID bigserial primary key, + DISPLAY_NAME varchar(32) not null + constraint UK_USERS_DISPLAY_NAME unique, + EMAIL varchar(128) not null + constraint UK_USERS_EMAIL unique, + FIRST_NAME varchar(32) not null, + LAST_NAME varchar(32), + PASSWORD varchar(128) not null, + ENDPOINT timestamp, + STARTPOINT timestamp +); + +create table PROFILE +( + ID bigint primary key, + LAST_LOGIN timestamp, + LAST_FAILED_LOGIN timestamp, + MAIL_NOTIFICATIONS bigint, + constraint FK_PROFILE_USERS foreign key (ID) references USERS (ID) on delete cascade +); + +create table CONTACT +( + ID bigint not null, + CODE varchar(32) not null, + "VALUE" varchar(256) not null, + primary key (ID, CODE), + constraint FK_CONTACT_PROFILE foreign key (ID) references PROFILE (ID) on delete cascade +); + +create table TASK +( + ID bigserial primary key, + TITLE varchar(1024) not null, + DESCRIPTION varchar(4096) not null, + TYPE_CODE varchar(32) not null, + STATUS_CODE varchar(32) not null, + PRIORITY_CODE varchar(32) not null, + ESTIMATE integer, + UPDATED timestamp, + PROJECT_ID bigint not null, + SPRINT_ID bigint, + PARENT_ID bigint, + STARTPOINT timestamp, + ENDPOINT timestamp, + constraint FK_TASK_SPRINT foreign key (SPRINT_ID) references SPRINT (ID) on delete set null, + constraint FK_TASK_PROJECT foreign key (PROJECT_ID) references PROJECT (ID) on delete cascade, + constraint FK_TASK_PARENT_TASK foreign key (PARENT_ID) references TASK (ID) on delete cascade +); + +create table ACTIVITY +( + ID bigserial primary key, + AUTHOR_ID bigint not null, + TASK_ID bigint not null, + UPDATED timestamp, + COMMENT varchar(4096), +-- history of task field change + TITLE varchar(1024), + DESCRIPTION varchar(4096), + ESTIMATE integer, + TYPE_CODE varchar(32), + STATUS_CODE varchar(32), + PRIORITY_CODE varchar(32), + constraint FK_ACTIVITY_USERS foreign key (AUTHOR_ID) references USERS (ID), + constraint FK_ACTIVITY_TASK foreign key (TASK_ID) references TASK (ID) on delete cascade +); + +create table TASK_TAG +( + TASK_ID bigint not null, + TAG varchar(32) not null, + constraint UK_TASK_TAG unique (TASK_ID, TAG), + constraint FK_TASK_TAG foreign key (TASK_ID) references TASK (ID) on delete cascade +); + +create table USER_BELONG +( + ID bigserial primary key, + OBJECT_ID bigint not null, + OBJECT_TYPE smallint not null, + USER_ID bigint not null, + USER_TYPE_CODE varchar(32) not null, + STARTPOINT timestamp, + ENDPOINT timestamp, + constraint FK_USER_BELONG foreign key (USER_ID) references USERS (ID) +); +create unique index UK_USER_BELONG on USER_BELONG (OBJECT_ID, OBJECT_TYPE, USER_ID, USER_TYPE_CODE); +create index IX_USER_BELONG_USER_ID on USER_BELONG (USER_ID); + +create table ATTACHMENT +( + ID bigserial primary key, + NAME varchar(128) not null, + FILE_LINK varchar(2048) not null, + OBJECT_ID bigint not null, + OBJECT_TYPE smallint not null, + USER_ID bigint not null, + DATE_TIME timestamp, + constraint FK_ATTACHMENT foreign key (USER_ID) references USERS (ID) +); + +create table USER_ROLE +( + USER_ID bigint not null, + ROLE smallint not null, + constraint UK_USER_ROLE unique (USER_ID, ROLE), + constraint FK_USER_ROLE foreign key (USER_ID) references USERS (ID) on delete cascade +); + +--changeset kmpk:populate_data +--============ References ================= +insert into REFERENCE (CODE, TITLE, REF_TYPE) +-- TASK +values ('task', 'Task', 2), + ('story', 'Story', 2), + ('bug', 'Bug', 2), + ('epic', 'Epic', 2), +-- SPRINT_STATUS + ('planning', 'Planning', 4), + ('active', 'Active', 4), + ('finished', 'Finished', 4), +-- USER_TYPE + ('author', 'Author', 5), + ('developer', 'Developer', 5), + ('reviewer', 'Reviewer', 5), + ('tester', 'Tester', 5), +-- PROJECT + ('scrum', 'Scrum', 1), + ('task_tracker', 'Task tracker', 1), +-- CONTACT + ('skype', 'Skype', 0), + ('tg', 'Telegram', 0), + ('mobile', 'Mobile', 0), + ('phone', 'Phone', 0), + ('website', 'Website', 0), + ('vk', 'VK', 0), + ('linkedin', 'LinkedIn', 0), + ('github', 'GitHub', 0), +-- PRIORITY + ('critical', 'Critical', 7), + ('high', 'High', 7), + ('normal', 'Normal', 7), + ('low', 'Low', 7), + ('neutral', 'Neutral', 7); + +insert into REFERENCE (CODE, TITLE, REF_TYPE, AUX) +-- MAIL_NOTIFICATION +values ('assigned', 'Assigned', 6, '1'), + ('three_days_before_deadline', 'Three days before deadline', 6, '2'), + ('two_days_before_deadline', 'Two days before deadline', 6, '4'), + ('one_day_before_deadline', 'One day before deadline', 6, '8'), + ('deadline', 'Deadline', 6, '16'), + ('overdue', 'Overdue', 6, '32'), +-- TASK_STATUS + ('todo', 'ToDo', 3, 'in_progress,canceled'), + ('in_progress', 'In progress', 3, 'ready_for_review,canceled'), + ('ready_for_review', 'Ready for review', 3, 'review,canceled'), + ('review', 'Review', 3, 'in_progress,ready_for_test,canceled'), + ('ready_for_test', 'Ready for test', 3, 'test,canceled'), + ('test', 'Test', 3, 'done,in_progress,canceled'), + ('done', 'Done', 3, 'canceled'), + ('canceled', 'Canceled', 3, null); + +--changeset gkislin:change_backtracking_tables + +alter table SPRINT rename COLUMN TITLE to CODE; +alter table SPRINT +alter column CODE type varchar (32); +alter table SPRINT + alter column CODE set not null; +create unique index UK_SPRINT_PROJECT_CODE on SPRINT (PROJECT_ID, CODE); + +ALTER TABLE TASK +DROP COLUMN DESCRIPTION; +ALTER TABLE TASK +DROP COLUMN PRIORITY_CODE; +ALTER TABLE TASK +DROP COLUMN ESTIMATE; +ALTER TABLE TASK +DROP COLUMN UPDATED; + +--changeset ishlyakhtenkov:change_task_status_reference + +delete +from REFERENCE +where REF_TYPE = 3; +insert into REFERENCE (CODE, TITLE, REF_TYPE, AUX) +values ('todo', 'ToDo', 3, 'in_progress,canceled'), + ('in_progress', 'In progress', 3, 'ready_for_review,canceled'), + ('ready_for_review', 'Ready for review', 3, 'in_progress,review,canceled'), + ('review', 'Review', 3, 'in_progress,ready_for_test,canceled'), + ('ready_for_test', 'Ready for test', 3, 'review,test,canceled'), + ('test', 'Test', 3, 'done,in_progress,canceled'), + ('done', 'Done', 3, 'canceled'), + ('canceled', 'Canceled', 3, null); + +--changeset gkislin:users_add_on_delete_cascade + +alter table ACTIVITY drop constraint FK_ACTIVITY_USERS; +alter table ACTIVITY add constraint FK_ACTIVITY_USERS foreign key (AUTHOR_ID) references USERS (ID) on delete cascade; + +alter table USER_BELONG drop constraint FK_USER_BELONG; +alter table USER_BELONG add constraint FK_USER_BELONG foreign key (USER_ID) references USERS (ID) on delete cascade; + +alter table ATTACHMENT drop constraint FK_ATTACHMENT; +alter table ATTACHMENT add constraint FK_ATTACHMENT foreign key (USER_ID) references USERS (ID) on delete cascade; + +--changeset valeriyemelyanov:change_user_type_reference + +delete +from REFERENCE +where REF_TYPE = 5; +insert into REFERENCE (CODE, TITLE, REF_TYPE) +-- USER_TYPE +values ('project_author', 'Author', 5), + ('project_manager', 'Manager', 5), + ('sprint_author', 'Author', 5), + ('sprint_manager', 'Manager', 5), + ('task_author', 'Author', 5), + ('task_developer', 'Developer', 5), + ('task_reviewer', 'Reviewer', 5), + ('task_tester', 'Tester', 5); + +--changeset apolik:refactor_reference_aux + +-- TASK_TYPE +delete +from REFERENCE +where REF_TYPE = 3; +insert into REFERENCE (CODE, TITLE, REF_TYPE, AUX) +values ('todo', 'ToDo', 3, 'in_progress,canceled|'), + ('in_progress', 'In progress', 3, 'ready_for_review,canceled|task_developer'), + ('ready_for_review', 'Ready for review', 3, 'in_progress,review,canceled|'), + ('review', 'Review', 3, 'in_progress,ready_for_test,canceled|task_reviewer'), + ('ready_for_test', 'Ready for test', 3, 'review,test,canceled|'), + ('test', 'Test', 3, 'done,in_progress,canceled|task_tester'), + ('done', 'Done', 3, 'canceled|'), + ('canceled', 'Canceled', 3, null); + +drop index UK_USER_BELONG; \ No newline at end of file diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql index 5087dbddc..5a1c52922 100644 --- a/src/test/resources/data.sql +++ b/src/test/resources/data.sql @@ -8,25 +8,25 @@ from PROFILE; delete from ACTIVITY; -alter -sequence ACTIVITY_ID_SEQ restart with 1; +alter table ACTIVITY + alter column ID restart with 1; delete from TASK; -alter -sequence TASK_ID_SEQ restart with 1; +alter table TASK + alter column ID restart with 1; delete from SPRINT; -alter -sequence SPRINT_ID_SEQ restart with 1; +alter table SPRINT + alter column ID restart with 1; delete from PROJECT; -alter -sequence PROJECT_ID_SEQ restart with 1; +alter table PROJECT + alter column ID restart with 1; delete from USERS; -alter -sequence USERS_ID_SEQ restart with 1; +alter table USERS + alter column ID restart with 1; insert into USERS (EMAIL, PASSWORD, FIRST_NAME, LAST_NAME, DISPLAY_NAME) values ('user@gmail.com', '{noop}password', 'userFirstName', 'userLastName', 'userDisplayName'), @@ -48,7 +48,7 @@ insert into PROFILE (ID, LAST_FAILED_LOGIN, LAST_LOGIN, MAIL_NOTIFICATIONS) values (1, null, null, 49), (2, null, null, 14); -insert into CONTACT (ID, CODE, VALUE) +insert into CONTACT (ID, CODE, "VALUE") values (1, 'skype', 'userSkype'), (1, 'mobile', '+01234567890'), (1, 'website', 'user.com'), @@ -95,4 +95,4 @@ values (1, 2, 2, 'task_developer', '2023-06-14 08:35:10', '2023-06-14 08:55:00') (1, 2, 1, 'task_tester', '2023-06-14 15:20:00', null), (2, 2, 2, 'task_developer', '2023-06-08 07:10:00', null), (2, 2, 1, 'task_developer', '2023-06-09 14:48:00', null), - (2, 2, 1, 'task_tester', '2023-06-10 16:37:00', null); + (2, 2, 1, 'task_tester', '2023-06-10 16:37:00', null); \ No newline at end of file