diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 03ed569..e2ac59c 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,7 +16,7 @@ name: Lint Code Base ############################# on: push: - branches-ignore: [master, main] + branches-ignore: [master, main, back_development] # Remove the line above to run when pushing to master pull_request: branches: [master, main] @@ -35,6 +35,22 @@ jobs: # Load all steps # ################## steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + - name: Build with Maven + run: | + cd src/backend/TimetableLinkAPI + mvn --batch-mode --update-snapshots package + - name: Run the Maven verify phase + run: | + #cd src/backend/TimetableLinkAPI + mvn --batch-mode --update-snapshots verify + + ########################## # Checkout the code base # ########################## diff --git a/.github/workflows/mvn.yml b/.github/workflows/mvn.yml new file mode 100644 index 0000000..3af3d18 --- /dev/null +++ b/.github/workflows/mvn.yml @@ -0,0 +1,28 @@ +name: Java CI + +on: + push: + branches-ignore: [master, main, back_development] + # Remove the line above to run when pushing to master + pull_request: + branches: [master, main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + - name: Build with Maven + run: | + cd src/backend/TimetableLinkAPI + mvn --batch-mode --update-snapshots package + - name: Run the Maven verify phase + run: | + cd src/backend/TimetableLinkAPI + mvn --batch-mode --update-snapshots verify diff --git a/.gitignore b/.gitignore index b6e4761..b275ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +#IntelliJ IDEA +.idea \ No newline at end of file diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..953cb1d --- /dev/null +++ b/License.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2022] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 90aa2b9..5b23819 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,46 @@ -# Brief Intro +[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) -Project repo to link Moodle, MS Exchange, DoE updates and University Events in one centralized system. +# Cross Link for University Innopolis schedules + +--- + +This application is a combination of all `Innopolis University` timetables. With it, DoE will create events that will be sent to the calendars of students and teachers of the university. Moreover, this application combines all the events created on `Moodle and MS Exchange` and also sends them to the `Outlook calendars` of students and teachers. + +![Create Mailing list](images/CreateList.png) + +## Why this important? + +In `University Innopolis` there a lot of links that have some events on them(e.g. `Moodle, Outlook, MS Exchange`). So, our application unite all these timetables and send them to `Outlook Calendar`, because every participant of University Innopolis has Outlook account. + +## How to use? + +our application is based on the site, so just open the site and log in. +[Event adder](https://degrassi-minister-88544.herokuapp.com/index.html) + +![Edit events](images/EditEvent.png) + +![Edit events Outlook](images/EditEvent2.png) + +## Features + +There's some features in our application : +1. `Adding events` to students' and teachers' calendars. +2. `Edit and Delete` already created `events` +3. `Import list of emails` from Outlook. +4. `Create list of emails` on our site. +5. `Edit and Delete` already created `mailing lists`. + +## Tools + +For frontend developing we used html, css and js. For backend developing of our site we used spring, docker, maven. + +![Edit mailing list](images/EditList.png) + +![Edit mailing list](images/EditList2.png) ## Important Links - [Usecase Diagram](https://drive.google.com/file/d/1nr23I5055SIXLq0PMvGGDPZAzz01xiW4/view?usp=sharing) - [User Story](https://docs.google.com/spreadsheets/d/12BQN_QRp9IU6oKfjrk3xJsV7YisZib4y3s285RwqUo4/edit?usp=sharing) - [Product Backlog](https://docs.google.com/document/d/1eF4ok6R33ai33qpmHXXPxxG4ZWCH8k8phtmZDnNVtxg/edit?usp=sharing) -- [API Design](https://app.swaggerhub.com/apis/Timetable2/timetable/1.0.0) - [Mock Server](https://www.postman.com/orange-astronaut-888988/workspace/timetable-api/collection/21222264-0d7b6da3-1e13-4bd9-af09-11720e694a00?ctx=documentation) - -## Developer Guide - -### Branching Rules -- Keep branch names relevant to the sprint task (i.e. LoginFeature, EventFrontend) -- Maintain separate branch for separate tasks - -### Commits -- Commit message format - `[Commit Type] - [Brief]` - Commit types - - Patch - - Feature - - Minor Change -- Keep commit to their respective branch (i.e. `Patch - Fixed frontend taskbar responsiveness` should not be commited to `LoginFeature` branch) - -## Pushing -- Pushing to master is forbidden, branches would be merged later -- To be accepted for a merge, code should either pass unittest (if applicable) or be checked for bugs by at least one other developer \ No newline at end of file diff --git a/images/CreateList.png b/images/CreateList.png new file mode 100644 index 0000000..73e5e30 Binary files /dev/null and b/images/CreateList.png differ diff --git a/images/EditEvent.png b/images/EditEvent.png new file mode 100644 index 0000000..3241596 Binary files /dev/null and b/images/EditEvent.png differ diff --git a/images/EditEvent2.png b/images/EditEvent2.png new file mode 100644 index 0000000..40d50b3 Binary files /dev/null and b/images/EditEvent2.png differ diff --git a/images/EditList.png b/images/EditList.png new file mode 100644 index 0000000..a59f8c8 Binary files /dev/null and b/images/EditList.png differ diff --git a/images/EditList2.png b/images/EditList2.png new file mode 100644 index 0000000..5b4ece3 Binary files /dev/null and b/images/EditList2.png differ diff --git a/src/backend/TimetableLinkAPI/.DS_Store b/src/backend/TimetableLinkAPI/.DS_Store index 9a874b5..feca861 100644 Binary files a/src/backend/TimetableLinkAPI/.DS_Store and b/src/backend/TimetableLinkAPI/.DS_Store differ diff --git a/src/backend/TimetableLinkAPI/Dockerfile b/src/backend/TimetableLinkAPI/Dockerfile new file mode 100644 index 0000000..72ab994 --- /dev/null +++ b/src/backend/TimetableLinkAPI/Dockerfile @@ -0,0 +1,11 @@ +FROM maven as builder +WORKDIR /usr/src/app +COPY pom.xml . +RUN mvn -B -e -C -T 1C org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline +COPY . . +RUN mvn package + +FROM openjdk:17 +EXPOSE 8080 +COPY --from=builder /usr/src/app/target/*.jar ./ +ENTRYPOINT ["java", "-jar", "/spring-boot-docker.jar"] diff --git a/src/backend/TimetableLinkAPI/Procfile b/src/backend/TimetableLinkAPI/Procfile new file mode 100644 index 0000000..678da31 --- /dev/null +++ b/src/backend/TimetableLinkAPI/Procfile @@ -0,0 +1 @@ +web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/docker-compose.yml b/src/backend/TimetableLinkAPI/docker-compose.yml index b42f906..890d604 100644 --- a/src/backend/TimetableLinkAPI/docker-compose.yml +++ b/src/backend/TimetableLinkAPI/docker-compose.yml @@ -1,16 +1,32 @@ version: '3' - services: + server: + container_name: timetable_server + build: . + environment: + SPRING_APPLICATION_JSON: '{ + "spring.datasource.url" : "jdbc:mysql://db:3306/timetable" + }' + ports: + - "8080:8080" + depends_on: + - db + restart: on-failure + + db: + container_name: timetable_db image: mysql:8.0 platform: linux/x86_64 restart: always - command: --init-file /data/application/init.sql - volumes: - - ./init.sql:/data/application/init.sql +# command: --init-file /data/application/schema.sql +# volumes: +# - ./schema.sql:/data/application/schema.sql environment: - MYSQL_DATABASE=timetable - MYSQL_ROOT_PASSWORD=pass ports: - - '3306:3306' + - "3306:3306" + + diff --git a/src/backend/TimetableLinkAPI/index.html b/src/backend/TimetableLinkAPI/index.html deleted file mode 100644 index 0ebcfd4..0000000 --- a/src/backend/TimetableLinkAPI/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Title - - - - - \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/pom.xml b/src/backend/TimetableLinkAPI/pom.xml index eb25729..86b0605 100644 --- a/src/backend/TimetableLinkAPI/pom.xml +++ b/src/backend/TimetableLinkAPI/pom.xml @@ -53,24 +53,62 @@ 5.3.20 - + - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter-api + 5.9.0-M1 test + + com.h2database + h2 + test + - + + + org.assertj + assertj-core + 3.23.1 + test + + + + org.mockito + mockito-core + 4.6.1 + test + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + + + + org.springframework.boot spring-boot-maven-plugin + ${project.parent.version} + spring-boot-docker - diff --git a/src/backend/TimetableLinkAPI/src/main/.DS_Store b/src/backend/TimetableLinkAPI/src/main/.DS_Store index 55c1fcb..5ddc54c 100644 Binary files a/src/backend/TimetableLinkAPI/src/main/.DS_Store and b/src/backend/TimetableLinkAPI/src/main/.DS_Store differ diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/App.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/App.java index 9558d10..eb3d28d 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/App.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/App.java @@ -1,6 +1,9 @@ package com.timetable; //import com.timetable.jdbc.SpringJdbcConfig; +import com.timetable.authentication.AuthenticationConfig; +import com.timetable.authentication.AuthenticationRepository; +import com.timetable.authentication.RequestAuthenticationCheck; import com.timetable.jdbc.SpringJdbcConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -11,31 +14,25 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; -import org.springframework.web.servlet.config.annotation.*; - -import javax.sql.DataSource; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @SpringBootApplication -@Import({SpringJdbcConfig.class}) -//@EnableWebMvc -public class App { +@Import({SpringJdbcConfig.class, AuthenticationConfig.class}) +public class App extends WebMvcConfigurerAdapter { public static void main(String[] args) { SpringApplication.run(App.class, args); } -// @Override -// public void addViewControllers(ViewControllerRegistry registry) { -// registry.addViewController("/").setViewName("forward:/index.html"); -// } -// -// @Bean -// public WebMvcConfigurer corsConfigurer() { -// return new WebMvcConfigurerAdapter() { -// @Override -// public void addCorsMappings(CorsRegistry registry) { -// registry.addMapping("/**") -// .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH"); -// } -// }; -// } + @Autowired + private RequestAuthenticationCheck authenticationInterceptor; + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authenticationInterceptor); + } } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationConfig.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationConfig.java new file mode 100644 index 0000000..ab3783f --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationConfig.java @@ -0,0 +1,29 @@ +package com.timetable.authentication; + +import com.timetable.jdbc.SpringJdbcConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@ComponentScan +public class AuthenticationConfig extends WebMvcConfigurerAdapter { + + private final RequestAuthenticationCheck authenticationInterceptor; + + @Autowired + public AuthenticationConfig(RequestAuthenticationCheck authenticationInterceptor) { + this.authenticationInterceptor = authenticationInterceptor; + } + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authenticationInterceptor); + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationController.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationController.java new file mode 100644 index 0000000..81d1676 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationController.java @@ -0,0 +1,35 @@ +package com.timetable.authentication; + +import com.timetable.outlook.OutlookConnector; +import com.timetable.user.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class AuthenticationController { + AuthenticationService authenticationService; + + @Autowired + AuthenticationController(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @PostMapping("/login") + public Map login(@RequestBody User user) { + try { + authenticationService.login(user.getEmail(), user.getPassword()); + String token = authenticationService.generateNewToken(); + Map tokenJson = new HashMap<>(); + tokenJson.put("token", token); + return tokenJson; + } catch (Exception ex) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN); + } + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationMySQLRepository.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationMySQLRepository.java new file mode 100644 index 0000000..10ae3ca --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationMySQLRepository.java @@ -0,0 +1,35 @@ +package com.timetable.authentication; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class AuthenticationMySQLRepository implements AuthenticationRepository { + private final JdbcTemplate jdbcTemplate; + + @Autowired + public AuthenticationMySQLRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void saveToken(String token, Long expireTime) { + jdbcTemplate.update( + "INSERT INTO token (content, expireTime) values (?, ?)", + token, expireTime); + } + + @Override + public void renewTokens(Long currentTime) { + jdbcTemplate.update( + "DELETE FROM token WHERE expireTime < ?", currentTime); + } + + @Override + public boolean tokenExists(String token) { + Long exists = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM token WHERE content = ?", Long.class, token); + return exists != null && exists.compareTo(0L) > 0; + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationRepository.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationRepository.java new file mode 100644 index 0000000..7b28f3a --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationRepository.java @@ -0,0 +1,7 @@ +package com.timetable.authentication; + +public interface AuthenticationRepository { + void saveToken(String token, Long expireTime); + void renewTokens(Long currentTime); + boolean tokenExists(String token); +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationService.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationService.java new file mode 100644 index 0000000..14f2818 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/AuthenticationService.java @@ -0,0 +1,48 @@ +package com.timetable.authentication; + +import com.timetable.outlook.OutlookConnector; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Service; + +import java.security.SecureRandom; +import java.util.Base64; + +@Service +@PropertySource("classpath:security.properties") +public class AuthenticationService { + private final SecureRandom secureRandom = new SecureRandom(); + private final Base64.Encoder base64Encoder = Base64.getUrlEncoder(); + private final OutlookConnector outlookConnector; + private final AuthenticationRepository authenticationRepository; + private final Long tokenLifetime; + + @Autowired + AuthenticationService(OutlookConnector outlookConnector, + AuthenticationRepository authenticationRepository, + @Value("${token.lifetime}") Long tokenLifetime) { + this.outlookConnector = outlookConnector; + this.authenticationRepository = authenticationRepository; + this.tokenLifetime = tokenLifetime; + } + + public void login(String email, String password) throws Exception { + outlookConnector.setCredentials(email, password); + } + + public String generateNewToken() { + byte[] randomBytes = new byte[24]; + secureRandom.nextBytes(randomBytes); + String token = base64Encoder.encodeToString(randomBytes); + Long currentTime = System.currentTimeMillis(); + authenticationRepository.saveToken(token, currentTime + tokenLifetime); + return token; + } + + public boolean checkToken(String token) { + Long currentTime = System.currentTimeMillis(); + authenticationRepository.renewTokens(currentTime); + return authenticationRepository.tokenExists(token); + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/RequestAuthenticationCheck.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/RequestAuthenticationCheck.java new file mode 100644 index 0000000..14057c3 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/authentication/RequestAuthenticationCheck.java @@ -0,0 +1,39 @@ +package com.timetable.authentication; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class RequestAuthenticationCheck implements HandlerInterceptor { + private final AuthenticationService authenticationService; + + @Autowired + public RequestAuthenticationCheck(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //System.out.println(request.getServletPath()); + if (request.getServletPath().equals("/") || + request.getServletPath().equals("/login") || + request.getServletPath().endsWith(".html") || + request.getServletPath().endsWith(".js") || + request.getServletPath().endsWith(".css") || + request.getServletPath().endsWith(".ico")) + return true; + System.out.println("catch"); + String token = request.getHeader("Authorization"); + if (authenticationService.checkToken(token)) + return true; + else throw new ResponseStatusException(HttpStatus.FORBIDDEN); + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventController.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventController.java index e3b9da3..6fdb747 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventController.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventController.java @@ -2,15 +2,14 @@ import org.apache.tomcat.util.buf.UDecoder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriUtils; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Date; -import java.util.List; +import java.util.*; @RestController @RequestMapping(path = "/events") @@ -23,17 +22,16 @@ public EventController(EventService eventService) { } @PostMapping - @CrossOrigin(origins = "http://localhost:63342") - public String createEvent(@RequestBody Event event) { - //System.out.println(event.getEndDate()); - String eventId = null; + public Map createEvent(@RequestBody Event event) { + Map eventIdMap = new HashMap<>(); try { - eventId = eventService.createEvent(event); + String eventId = eventService.createEvent(event); + eventIdMap.put("eventId", eventId); } catch (Exception ex) { ex.printStackTrace(); } - return eventId; + return eventIdMap; } @GetMapping ("/{eventId}") @@ -100,6 +98,15 @@ public List getAllEvents() { return events; } + @GetMapping("/names") + public List getAllNames() { + List names = null; + try { + names = getAllEvents().stream().map(Event::getName).toList(); + } catch (Exception ex) {} + return names; + } + @PatchMapping ("/{eventId}/invite/{mailingListTextIdentifier}") public void inviteAllFromMailingList(@PathVariable String eventId, @PathVariable String mailingListTextIdentifier) { diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventService.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventService.java index 227f068..a775cb6 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventService.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/EventService.java @@ -31,7 +31,9 @@ public List getAllEvents() throws Exception { List eventIds = eventRepository.getAllIds(); List events = new ArrayList<>(); for (String id : eventIds) { - events.add(getEvent(id)); + try { + events.add(getEvent(id)); + } catch(Exception ignored) {} } return events; } @@ -39,9 +41,11 @@ public List getAllEvents() throws Exception { public List getEventsFromTimeInterval(Date start, Date end) throws Exception { List filteredEvents = new ArrayList<>(); for (Event event : getAllEvents()) { - if (event.getStartDate().after(start) && event.getEndDate().before(end)) { - filteredEvents.add(event); - } + try { + if (event.getStartDate().after(start) && event.getEndDate().before(end)) { + filteredEvents.add(event); + } + } catch (Exception ignored) {} } return filteredEvents; } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/OutlookEventManager.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/OutlookEventManager.java index 0b7cae7..22f3a68 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/OutlookEventManager.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/event/OutlookEventManager.java @@ -13,6 +13,7 @@ import microsoft.exchange.webservices.data.search.FindItemsResults; import microsoft.exchange.webservices.data.search.ItemView; import microsoft.exchange.webservices.data.search.filter.SearchFilter; +import microsoft.exchange.webservices.data.core.enumeration.service.ConflictResolutionMode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -83,7 +84,7 @@ public void updateEvent(String encodedId, Event event) throws Exception { appointment.setStart(event.getStartDate()); appointment.setEnd(event.getEndDate()); appointment.setLocation(event.getLocation()); - appointment.save(); + appointment.update(ConflictResolutionMode.AutoResolve); } public void cancelEvent(String eventId) throws Exception { diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/jdbc/SpringJdbcConfig.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/jdbc/SpringJdbcConfig.java index c6e37d6..6c0fec0 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/jdbc/SpringJdbcConfig.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/jdbc/SpringJdbcConfig.java @@ -7,6 +7,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; @@ -15,7 +16,6 @@ @PropertySource("classpath:application.properties") public class SpringJdbcConfig { @Bean - //@Primary public DataSource mySqlDataSource( @Value("${spring.datasource.username}") String username, @Value("${spring.datasource.url}") String url, @@ -27,7 +27,10 @@ public DataSource mySqlDataSource( dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); - +// System.out.println("username " + username); +// System.out.println("url " + url); +// System.out.println("password " + password); +// System.out.println("driver " + driverClassName); return dataSource; } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingList.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingList.java index 0a3e4da..ceaa05c 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingList.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingList.java @@ -3,7 +3,9 @@ import microsoft.exchange.webservices.data.property.complex.EmailAddress; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Objects; public class MailingList { private final String textIdentifier; @@ -23,19 +25,17 @@ public List getEmails() { } -// public List getEmails() { -// return emails; -// } -// -// public void setEmails(List emails) { -// this.emails = emails; -// } -// -// public void addEvent(EmailAddress email) { -// emails.add(email); -// } -// -// public void excludeEmail(EmailAddress email) { -// emails.remove(email); -// } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MailingList that = (MailingList) o; + return textIdentifier.equals(that.textIdentifier) + && (new HashSet<>(emails)).equals(new HashSet<>(that.emails)); + } + + @Override + public int hashCode() { + return Objects.hash(textIdentifier, new HashSet<>(emails)); + } } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListController.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListController.java index 004a238..33a3c84 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListController.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListController.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; + import java.util.List; @RestController @@ -36,6 +37,20 @@ public void deleteEmailsFromList(@PathVariable String textIdentifier, mailingListService.deleteEmailsFromList(textIdentifier, emails); } + @PatchMapping("/{textIdentifier}/emails/update") + public void updateEmailsFromList(@PathVariable String textIdentifier, + @RequestBody List emails) { + mailingListService.updateEmailsFromList(textIdentifier, emails); + } + + @PatchMapping("/{textIdentifier}") + public void updateTextIdentifier( + @PathVariable String textIdentifier, + @RequestParam String newTextIdentifier) { + mailingListService.updateTextIdentifier(textIdentifier, newTextIdentifier); + } + + @GetMapping("/{textIdentifier}/emails") public List getEmailsFormList(@PathVariable String textIdentifier) { return mailingListService.getEmailsFromList(textIdentifier); diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListManager.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListManager.java new file mode 100644 index 0000000..a54cc09 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListManager.java @@ -0,0 +1,8 @@ +package com.timetable.mailing_list; + +import java.util.List; + +public interface MailingListManager { + List importMailingLists() throws Exception; + void cancelInvitations(String eventId, List emails) throws Exception; +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListMySQLRepository.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListMySQLRepository.java index 02f86cd..03a7b13 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListMySQLRepository.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListMySQLRepository.java @@ -1,8 +1,11 @@ package com.timetable.mailing_list; +import microsoft.exchange.webservices.data.core.service.item.Appointment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.stereotype.Repository; import java.util.ArrayList; @@ -19,7 +22,6 @@ public MailingListMySQLRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - @Override public List getMailingListsNames() { return jdbcTemplate.queryForList( @@ -104,12 +106,16 @@ public void deleteEmailFromList(Long mailingListId, String emailAddress) { Long emailId = jdbcTemplate.queryForObject( """ SELECT email.id FROM email - JOIN emailBelonging ON email.id = emailBelonging.emailId - WHERE email.emailAddress = ?; + JOIN emailBelonging ON email.id = emailBelonging.emailId + WHERE email.emailAddress = ? + LIMIT 1; """, Long.class, emailAddress); jdbcTemplate.update( "DELETE FROM emailBelonging WHERE mailingListId = ? AND emailId = ?", mailingListId, emailId); +// List list = jdbcTemplate.queryForList( +// "SELECT id FROM emailBelonging", String.class +// ); jdbcTemplate.update( """ DELETE FROM email @@ -138,19 +144,63 @@ public void updateMailingList(MailingList mailingList) { emailsToAdd.forEach(email -> addEmailToList(id, email)); } + @Override + public void updateTextIdentifier(String textIdentifier, String newTextIdentifier) { + jdbcTemplate.update( + """ + UPDATE mailingList + SET textIdentifier = ? + WHERE textIdentifier = ?; + """, newTextIdentifier, textIdentifier); + } + - private List getEmailsByListId(Long mailingListId) { + @Override + public List getEmailsByListId(Long mailingListId) { String getEmailsSqlRequest = """ SELECT DISTINCT emailAddress FROM email LEFT JOIN emailBelonging ON email.id = emailBelonging.emailId - WHERE mailingListId = ? + WHERE mailingListId = ?; """; return jdbcTemplate.queryForList( getEmailsSqlRequest, String.class, mailingListId); } + public void init() { + jdbcTemplate.update( + """ + CREATE DATABASE IF NOT EXISTS timetable; + USE timetable; + + SET SQL_SAFE_UPDATES = 0; + + CREATE TABLE IF NOT EXISTS mailingList ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + textIdentifier VARCHAR(40) UNIQUE + ); + + CREATE TABLE IF NOT EXISTS email( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + emailAddress VARCHAR(60) UNIQUE + ); + + CREATE TABLE IF NOT EXISTS emailBelonging( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + emailId BIGINT, + mailingListId BIGINT, + FOREIGN KEY (emailId) REFERENCES email (id) ON DELETE CASCADE, + FOREIGN KEY (mailingListId) references mailingList (id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS event( + outlookAppointmentId varchar(400) PRIMARY KEY + ); + """ + ); + } + private String getTextIdentifier(Long mailingListId) { String getMailingListTextIdentifierSql = "SELECT textIdentifier FROM mailingList WHERE id = ?"; diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListRepository.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListRepository.java index defb31a..91e6d21 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListRepository.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListRepository.java @@ -16,4 +16,7 @@ public interface MailingListRepository { void deleteEmailFromList(Long mailingListId, String emailAddress); boolean mailingListExists(String textIdentifier); void updateMailingList(MailingList mailingList); + void updateTextIdentifier(String textIdentifier, String newTextIdentifier); + List getEmailsByListId(Long id); + void init(); } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListService.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListService.java index 37005d7..7bd58c5 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListService.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/MailingListService.java @@ -2,7 +2,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + +import java.util.HashSet; import java.util.List; +import java.util.Set; @Service public class MailingListService { @@ -36,14 +39,29 @@ public void deleteMailingList(String textIdentifier) { public void addEmailsToList(String textIdentifier, List emails) { Long mailingListId = mailingListRepository.getMailingListId(textIdentifier); - emails.forEach(emailAddress -> - mailingListRepository.addEmailToList(mailingListId, emailAddress)); + emails.forEach(emailAddress -> { + System.out.println("1"); + mailingListRepository.addEmailToList(mailingListId, emailAddress); + }); } public void deleteEmailsFromList(String textIdentifier, List emails) { Long mailingListId = mailingListRepository.getMailingListId(textIdentifier); - emails.forEach(emailAddress -> - mailingListRepository.deleteEmailFromList(mailingListId, emailAddress)); + for (String emailAddress : emails) { + try { + System.out.println("2"); + //System.out.println("before " + getEmailsFromList(textIdentifier).toString()); + mailingListRepository.deleteEmailFromList(mailingListId, emailAddress); + //System.out.println("after " + getEmailsFromList(textIdentifier).toString()); + } catch(Exception ex){ + ex.printStackTrace(); + } + } + } + + public void updateEmailsFromList(String textIdentifier, List emails) { + MailingList updatedMailingList = new MailingList(textIdentifier, emails); + updateExistingMailingList(updatedMailingList); } public MailingList getMailingList(String textIdentifier) { @@ -65,7 +83,7 @@ public void importMailingList() throws Exception { mailingLists.forEach(mailingList -> { // Long id = mailingListRepository.getMailingListId(mailingList.getTextIdentifier()); if (mailingListRepository.mailingListExists(mailingList.getTextIdentifier())) { - mailingListRepository.updateMailingList(mailingList); + updateExistingMailingList(mailingList); } else { mailingListRepository.createMailingList(mailingList); } @@ -76,4 +94,22 @@ public void cancelInvitations(String eventId, String textIdentifier) throws Exce List emails = getEmailsFromList(textIdentifier); mailingListManager.cancelInvitations(eventId, emails); } + + public void updateTextIdentifier(String textIdentifier, String newTextIdentifier) { + mailingListRepository.updateTextIdentifier(textIdentifier, newTextIdentifier); + } + + private void updateExistingMailingList(MailingList mailingList) { + Long id = mailingListRepository.getMailingListId(mailingList.getTextIdentifier()); + Set oldEmails = new HashSet<>(mailingListRepository.getEmailsByListId(id)); + Set newEmails = new HashSet<>(mailingList.getEmails()); + Set emailsToDelete = new HashSet<>(oldEmails); + emailsToDelete.removeAll(newEmails); + Set emailsToAdd = new HashSet<>(newEmails); + emailsToAdd.removeAll(oldEmails); + emailsToDelete.forEach(email -> + mailingListRepository.deleteEmailFromList(id, email)); + emailsToAdd.forEach(email -> + mailingListRepository.addEmailToList(id, email)); + } } diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/OutlookMailingListManager.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/OutlookMailingListManager.java index 5da2fee..95de6a8 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/OutlookMailingListManager.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/mailing_list/OutlookMailingListManager.java @@ -27,7 +27,7 @@ import java.util.List; @Component -public class OutlookMailingListManager { +public class OutlookMailingListManager implements MailingListManager { private final ExchangeService service; private final OutlookEventManager eventManager; @@ -69,7 +69,7 @@ public void cancelInvitations(String eventId, List emails) throws Except appointment.update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone); } - public void filterInvitations(AttendeeCollection attendees, List emails) { + private void filterInvitations(AttendeeCollection attendees, List emails) { for (int i = 0; i <= attendees.getCount(); i++) { Attendee attendee = attendees.getPropertyAtIndex(i); if (emails.contains(attendee.getAddress())) { diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/outlook/OutlookConnector.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/outlook/OutlookConnector.java index 26c7e74..ee12f96 100644 --- a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/outlook/OutlookConnector.java +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/outlook/OutlookConnector.java @@ -10,26 +10,36 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; +import java.net.URI; + @Component @PropertySource("classpath:personal.properties") public class OutlookConnector { private ExchangeService service; - private ExchangeCredentials credentials; + //private ExchangeCredentials credentials; @Autowired public OutlookConnector( @Value("${personal.email}") String personalEmail, @Value("${personal.password}") String personalPassword) { - service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); + service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); +// service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); +// ExchangeCredentials credentials = new WebCredentials(personalEmail, personalPassword); +// service.setCredentials(credentials); +// try { +// service.autodiscoverUrl( +// personalEmail, new OutlookConnector.RedirectionUrlCallback()); +// } catch (Exception e) { +// e.printStackTrace(); +// } + + } + + public void setCredentials(String personalEmail, String personalPassword) throws Exception { ExchangeCredentials credentials = new WebCredentials(personalEmail, personalPassword); service.setCredentials(credentials); - try { - service.autodiscoverUrl( - personalEmail, new OutlookConnector.RedirectionUrlCallback()); - } catch (Exception e) { - e.printStackTrace(); - } - + service.autodiscoverUrl( + personalEmail, new OutlookConnector.RedirectionUrlCallback()); } public ExchangeService getService() { diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/user/User.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/user/User.java new file mode 100644 index 0000000..01e205b --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/user/User.java @@ -0,0 +1,15 @@ +package com.timetable.user; + +public class User { + private String email; + private String password; + + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} diff --git a/src/backend/TimetableLinkAPI/src/main/java/com/timetable/view/ViewController.java b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/view/ViewController.java new file mode 100644 index 0000000..370db37 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/java/com/timetable/view/ViewController.java @@ -0,0 +1,19 @@ +package com.timetable.view; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class ViewController { + @GetMapping("") + public String login() { + return "login.html"; + } + + @GetMapping("/create/mailing") + public String create_mailing(){ return "post_mailing_list.html";} + + @GetMapping("/edit/mailing") + public String edit_mailing(){ return "edit_mailing_list.html";} +} + diff --git a/src/backend/TimetableLinkAPI/src/main/resources/.DS_Store b/src/backend/TimetableLinkAPI/src/main/resources/.DS_Store new file mode 100644 index 0000000..2a43b86 Binary files /dev/null and b/src/backend/TimetableLinkAPI/src/main/resources/.DS_Store differ diff --git a/src/backend/TimetableLinkAPI/src/main/resources/application.properties b/src/backend/TimetableLinkAPI/src/main/resources/application.properties index 7359aad..c3c5bfd 100644 --- a/src/backend/TimetableLinkAPI/src/main/resources/application.properties +++ b/src/backend/TimetableLinkAPI/src/main/resources/application.properties @@ -1,4 +1,6 @@ spring.datasource.username=root spring.datasource.url=jdbc:mysql://localhost:3306/timetable spring.datasource.password=pass -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver \ No newline at end of file +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.sql.init.mode=always +server.port=${PORT:8080} \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/schema.sql b/src/backend/TimetableLinkAPI/src/main/resources/schema.sql new file mode 100644 index 0000000..4dda7e8 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/schema.sql @@ -0,0 +1,39 @@ +CREATE DATABASE IF NOT EXISTS timetable; +USE timetable; + +SET SQL_SAFE_UPDATES = 0; + +CREATE TABLE IF NOT EXISTS mailingList ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + textIdentifier VARCHAR(40) UNIQUE +); + +CREATE TABLE IF NOT EXISTS email( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + emailAddress VARCHAR(60) UNIQUE +); + +CREATE TABLE IF NOT EXISTS emailBelonging( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + emailId BIGINT, + mailingListId BIGINT, + FOREIGN KEY (emailId) REFERENCES email (id) ON DELETE CASCADE, + FOREIGN KEY (mailingListId) references mailingList (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS event( + outlookAppointmentId VARCHAR(400) PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS token( + content VARCHAR(100), + expireTime BIGINT +); + +-- CREATE DATABASE IF NOT EXISTS test; +-- USE test; +-- +-- CREATE TABLE LIKE timetable.mailingList; +-- CREATE TABLE LIKE timetable.email; +-- CREATE TABLE LIKE timetable.emailBelonging; +-- CREATE TABLE LIKE timetable.event; \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/security.properties b/src/backend/TimetableLinkAPI/src/main/resources/security.properties new file mode 100644 index 0000000..1affa37 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/security.properties @@ -0,0 +1 @@ +token.lifetime=3600000 \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/authorise.js b/src/backend/TimetableLinkAPI/src/main/resources/static/authorise.js new file mode 100644 index 0000000..4be1bff --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/authorise.js @@ -0,0 +1,39 @@ +let loginBtn = document.getElementById('login-btn') + +loginBtn.addEventListener('click', (event) => { + event.preventDefault() + + fetch('/login', { + + method: 'POST', + body: JSON.stringify({ + email: document.getElementById('username').value, + password: document.getElementById('password').value + }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => { + console.log(response); + if (response.status === 403) { + processWrongData(); + return; + } + return response.json(); + }) + .then(data => { + console.log(data) + loginBtn.setAttribute('disabled', 'true') + let currentToken = data["token"]; + sessionStorage.setItem("token", currentToken); + window.location.replace("index.html"); + }) + .catch(error => console.log(error)) +}) + +function processWrongData() { + alert('Wrong data. Make sure you use Innopolis account. Try again...'); + document.getElementById('username').value = ''; + document.getElementById('password').value = ''; +} diff --git a/src/frontend/static/bootstrap.css b/src/backend/TimetableLinkAPI/src/main/resources/static/bootstrap.css similarity index 100% rename from src/frontend/static/bootstrap.css rename to src/backend/TimetableLinkAPI/src/main/resources/static/bootstrap.css diff --git a/src/frontend/static/bootstrap.js b/src/backend/TimetableLinkAPI/src/main/resources/static/bootstrap.js similarity index 100% rename from src/frontend/static/bootstrap.js rename to src/backend/TimetableLinkAPI/src/main/resources/static/bootstrap.js diff --git a/src/frontend/static/dayjs.js b/src/backend/TimetableLinkAPI/src/main/resources/static/dayjs.js similarity index 100% rename from src/frontend/static/dayjs.js rename to src/backend/TimetableLinkAPI/src/main/resources/static/dayjs.js diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.html b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.html new file mode 100644 index 0000000..3c0b424 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.html @@ -0,0 +1,110 @@ + + + + + Edit event + + + + + + +
+
+
+ + +

+ + + +
+ + + + + +
+ + + + + +

+

Get events from the time interval

+
+ + + + + +
+ + + + + +
+ + +

+

Set new name for event

+
+ + + +

+ +

Set new location

+
+ + + + +

+

Edit dates

+
+ + +
+ + + +
+ + +
+ + + + + + +

Interaction with mailing lists

+
+ +

Send invitations to chosen event to all people from a chosen mail list

+ + +

Cancel invitations to chosen event to all people from a chosen mail list

+ +
+ + + + \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.js b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.js new file mode 100644 index 0000000..af7988a --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_event.js @@ -0,0 +1,344 @@ +let currentEventName +let currentEventId +let currentMailingListName +let currentEventLocation +let currentEventStartDate +let currentEventEndDate + +/** + * Fetch events and mailing lists + */ +async function start () { + try { + const response = await fetch('/events', { + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }); + console.log(response); + const data = await response.json(); + console.log(data); + createEventsNameList(data); + + const mailResponse = await fetch('/mailingLists/names'); + const mailData = await mailResponse.json(); + createMailNameList(mailData); + } catch (e) { + console.log(e); + //alert('There was a problem fetching the events and/or mailing lists names.') + } +} + +/** + * Load event in selector form + */ +function createEventsNameList (eventList) { + let selectField = document.createElement("select"); + let defOp = document.createElement("option"); + defOp.innerHTML = "Choose an event"; + selectField.appendChild(defOp); + eventList.forEach(event => { + let eventId = event["id"]; + let eventName = event["name"]; + let option = document.createElement("option"); + option.setAttribute("value", eventId); + option.innerHTML = eventName; + selectField.appendChild(option); + }); + selectField.addEventListener('change', () => { + loadEventById(selectField.options[selectField.selectedIndex].value); + }); + document.getElementById('event-name').appendChild(selectField); +} + +function stringToDate(datetimeString){ + let parts = datetimeString.split("T"); + let datePart = new Date(parts[0]); + console.log(datePart); +} + +/** + * Load chosen event + */ +function loadEventById(eventId){ + if (name !== 'Choose an event') { + currentEventId = eventId; + deleteEventBtn.removeAttribute('disabled') + searchEventByDateBtn.removeAttribute('disabled') + newEventNameBtn.removeAttribute('disabled') + newEventLocationBtn.removeAttribute('disabled') + newEventStartDateBtn.removeAttribute('disabled') + newEventEndDateBtn.removeAttribute('disabled') + let link = `/events/${eventId}` + + fetch(link, { + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => { + console.log(response); + return response.json(); + }) + .then(data => { + + currentEventLocation = data["location"] + currentEventStartDate = data["startDate"] + currentEventEndDate = data["endDate"] + console.log(data); + + console.log(currentEventLocation); + console.log(currentEventStartDate); + stringToDate(currentEventStartDate); + console.log(currentEventEndDate); + + let startTimeParts = currentEventStartDate.split('T'); + let endTimeParts = currentEventEndDate.split('T') + + document.getElementById('event-location').value = currentEventLocation + document.getElementById('event-start-date').value = startTimeParts[0] + document.getElementById('event-start-time').value = startTimeParts[1].substr(0,5) + document.getElementById('event-end-date').value = endTimeParts[0] + document.getElementById('event-end-time').value = endTimeParts[1].substr(0,5) + }) + .catch(error => console.log(error)); + } else { + deleteEventBtn.setAttribute('disabled', 'disabled') + newEventNameBtn.setAttribute('disabled', 'disabled') + newEventLocationBtn.setAttribute('disabled', 'disabled') + newEventStartDateBtn.setAttribute('disabled', 'disabled') + newEventEndDateBtn.setAttribute('disabled', 'disabled') + searchEventByDateBtn.setAttribute('disabled', 'disabled') + } +} + +/** + * Load mailing list in selector form + */ +function createMailNameList (nameList) { + document.getElementById('list-name').innerHTML = ` + + ` +} + +const deleteEventBtn = document.getElementById('delete-event-btn') +const searchEventByDateBtn = document.getElementById('search-event-by-date-btn') +const newEventNameBtn = document.getElementById('new-event-name-btn') +const newEventLocationBtn = document.getElementById('new-event-location-btn') +const newEventStartDateBtn = document.getElementById('new-event-start-date-btn') +const newEventEndDateBtn = document.getElementById('new-event-end-date-btn') + +const sendInvitationToOneMailingListBtn = document.getElementById('send-invitation-to-one-mailing-list-btn'); +const cancelInvitationToOneMailingListBtn = document.getElementById('cancel-invitation-to-one-mailing-list-btn') + +/** + * Load chosen mailing list + */ +async function loadMailingListByName (name) { + if (name !== 'Choose a mailing list') { + currentMailingListName = name; + sendInvitationToOneMailingListBtn.removeAttribute('disabled') + cancelInvitationToOneMailingListBtn.removeAttribute('disabled') + } else { + sendInvitationToOneMailingListBtn.setAttribute('disabled', 'disabled') + cancelInvitationToOneMailingListBtn.setAttribute('disabled', 'disabled') + } +} + +/** + * Delete an event + */ +deleteEventBtn.addEventListener('click', (event) => { + event.preventDefault() + const isAgree = confirm(`Delete the '${currentEventName}' event?`) + if (isAgree) { + fetch(`/events/${currentEventName}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => { + console.log(response); + return response.json(); + }) + .then(() => { + }) + .catch(error => console.log(error)) + } +}) + +/** + * Get events from time interval + */ +searchEventByDateBtn.addEventListener('click', (event) => { + event.preventDefault() + const startDate = document.getElementById('search-event-start-date').value + "T" + + document.getElementById('search-event-start-time').value; + const endDate = document.getElementById('search-event-end-date').value + "T" + + document.getElementById('search-event-end-time').value; + + fetch(`events/eventsFromTimeInterval?start=${startDate}&end=${endDate}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(data => { + console.log("EVENTS ARRAY FROM TIME INTERVAL: " + data) + }) + .catch(error => console.log(error)) +}) + + + +/** + * Change/update an event's name + */ +newEventNameBtn.addEventListener('click', (event) => { + event.preventDefault() + const newName = document.getElementById('new-event-name').value + fetch(`events/${currentEventName}`, { + method: 'PUT', + body: JSON.stringify({ + 'name': newName, + 'location': currentEventLocation, + 'startDate': currentEventStartDate, + 'endDate': currentEventEndDate} + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) +}) + +/** + * Change/update an event's location + */ +newEventLocationBtn.addEventListener('click', (event) => { + event.preventDefault() + const newLocation = document.getElementById('new-event-location').value + fetch(`events/${currentEventName}`, { + method: 'PUT', + body: JSON.stringify({ + 'name': currentEventName, + 'location': newLocation, + 'startDate': currentEventStartDate, + 'endDate': currentEventEndDate} + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) +}) + +/** + * Change/update an event's start date + */ +newEventStartDateBtn.addEventListener('click', (event) => { + event.preventDefault() + const newStartDate = document.getElementById('new-event-start-date').value + fetch(`events/${currentEventName}`, { + method: 'PUT', + body: JSON.stringify({ + 'name': currentEventName, + 'location': currentEventLocation, + 'startDate': newStartDate, + 'endDate': currentEventEndDate} + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) +}) + +/** + * Change/update an event's end date + */ +newEventStartDateBtn.addEventListener('click', (event) => { + event.preventDefault() + const newEndDate = document.getElementById('new-event-end-date').value + fetch(`events/${currentEventName}`, { + method: 'PUT', + body: JSON.stringify({ + 'name': currentEventName, + 'location': currentEventLocation, + 'startDate': currentEventStartDate, + 'endDate': newEndDate} + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) +}) + +/** + * Send invitation to people from one mailing list + */ +sendInvitationToOneMailingListBtn.addEventListener('click', (event) => { + event.preventDefault() + fetch(`/events/${currentEventName}/invite/${currentMailingListName}`, { + method: 'PATCH', + body: JSON.stringify( + currentMailingListName + ), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) + +}) + +/** + * Cancel invitation to people from one mailing list + */ +cancelInvitationToOneMailingListBtn.addEventListener('click', (event) => { + event.preventDefault() + fetch(`/events/${currentEventName}/cancelInvitation/${currentMailingListName}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.json()) + .then(() => { + }) + .catch(error => console.log(error)) + +}) + +start() diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.html b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.html new file mode 100644 index 0000000..3de4847 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.html @@ -0,0 +1,65 @@ + + + + + Edit mailing list + + + + + + + +
+
+ +

+ + + + +
+ + +
+ +
+ + +

+

Import contact lists from Outlook

+ + +

+

Add emails

+
+ +
+ +
+ + +

+

Delete emails

+
+ +
+ +
+ +
+ + + + diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.js b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.js new file mode 100644 index 0000000..d393f2c --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_mailing_list.js @@ -0,0 +1,254 @@ +let currentMailingListName +let currentMailingListEmails +let currentPatchEmails +let currentAddEmails +let currentDeleteEmails + +/** + * Fetch mailing lists + */ +async function start () { + try { + console.log(sessionStorage.getItem("token")) + const response = await fetch('/mailingLists/names', { + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }) + const data = await response.json() + createMailNameList(data) + } catch (e) { + //alert('There was a problem fetching the mailing list\'s names.') + } +} + +/** + * Load mailing list in selector form + */ +function createMailNameList (nameList) { + document.getElementById('list-name').innerHTML = ` + + ` +} + +const deleteMailingListBtn = document.getElementById('delete-mailing-list-btn') +const deleteEmailsBtn = document.getElementById('delete-emails-btn') +const addEmailsBtn = document.getElementById('add-mails-btn') +const newMailingListNameBtn = document.getElementById('new-mailing-list-name-btn') +const importOutlookContactsBtn = document.getElementById('import-outlook-contacts-btn') +const changeEmailsBtn = document.getElementById('change-emails-btn') + +/** + * Load chosen mailing list + */ +async function loadByName (name) { + if (name !== 'Choose a mailing list') { + deleteMailingListBtn.removeAttribute('disabled') + importOutlookContactsBtn.removeAttribute('disabled') + currentMailingListName = name + + let link2 = `/mailingLists/${name}/emails` + const response2 = await fetch(link2, { + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }) + let mailsArray = await response2.json(); + console.log(mailsArray); + currentMailingListEmails = mailsArray; + let areaStr = '' + for (let i = 0; i < mailsArray.length; i++) { + areaStr = areaStr + mailsArray[i] + ' ' + } + areaStr = areaStr.substring(0, areaStr.length-1); + currentPatchEmails = areaStr; + document.getElementById('emails-area').value = areaStr + document.getElementById('mailing-list-name').value = name; + } else { + deleteMailingListBtn.setAttribute('disabled', 'disabled') + importOutlookContactsBtn.setAttribute('disabled', 'disabled') + } +} + +/** + * Delete a mailing list + */ +deleteMailingListBtn.addEventListener('click', (event) => { + const isAgree = confirm(`Delete the '${currentMailingListName}' mailing list?`) + if (isAgree) { + event.preventDefault() + fetch(`/mailingLists/${currentMailingListName}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => { + return response.json(); + }) + .then(() => { + }) + .catch(error => console.log(error)) + deleteEmailsBtn.disabled = true; + } +}) + +/** + * Change/update a mailing list's name + */ +newMailingListNameBtn.addEventListener('click', (event) => { + event.preventDefault() + const newName = document.getElementById('mailing-list-name').value + if (newName === "") { + alert("Name cannot be empty. Try again.") + } else { + fetch(`mailingLists/${currentMailingListName}?newTextIdentifier=${newName}`, { + method: 'PATCH', + headers: { + 'Authorization': sessionStorage.getItem("token"), + } + }) + .then(response => { + return response.json(); + }) + .then(() => { + }) + .catch(error => console.log(error)) + } +}) + +/** + * Change/update emails + */ +changeEmailsBtn.addEventListener('click', (event) => { + event.preventDefault(); + + const emailsStr = document.getElementById('emails-area').value; + const emailsArray = emailsStr.split(' ').filter(el => el !== ''); + + if (validateEmails(emailsArray)) { + fetching(`/mailingLists/${currentMailingListName}/emails/update`, emailsArray); + changeEmailsBtn.setAttribute('disabled', 'disabled') + currentPatchEmails = emailsStr; + } else { + alertIncorrectEmailError(); + } +}) + +/** + * Alert wrong emails error + */ +function alertIncorrectEmailError() { + alert('Emails are not in correct form. Make sure your set "innopolis.university" emails. Try again') +} + +/** + * Import contact lists from Outlook + */ +importOutlookContactsBtn.addEventListener('click', (event) => { + event.preventDefault() + fetch(`/mailingLists/importOutlookMailingLists`, { + method: 'PATCH', + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => { + return response.json(); + }) + .then(() => { + }) + .catch(error => console.log(error)) + importOutlookContactsBtn.setAttribute('disabled', 'disabled') +}) + +function fetching (PATH, mailsArray) { + fetch(PATH, { + method: 'PATCH', + body: JSON.stringify( + mailsArray), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.text()) + .then((data) => { + console.log(data); + return data; + }) + .catch(error => console.log(error)); +} + +/** + * Add email(s) to a mailing list + */ +addEmailsBtn.addEventListener('click', (event) => { + event.preventDefault() + const emailsStr = document.getElementById('add-mails').value + const emailsArray = emailsStr.split(' ').filter(el => el !== '') + + if (validateEmails(emailsArray)) { + fetching(`/mailingLists/${currentMailingListName}/emails/add`, emailsArray) + addEmailsBtn.setAttribute('disabled', 'disabled') + currentAddEmails = emailsStr; + } else { + alertIncorrectEmailError() + } +}) + +/** + * Delete email(s) from a mailing list + */ +deleteEmailsBtn.addEventListener('click', (event) => { + const isAgree = confirm(`\Delete email(s) from the ${currentMailingListName} mailing list?`) + + if (isAgree) { + event.preventDefault() + const emailsStr = document.getElementById('delete-mails').value + const emailsArray = emailsStr.split(' ').filter(el => el !== '') + + if (validateEmails(emailsArray)) { + fetching(`/mailingLists/${currentMailingListName}/emails/delete`, emailsArray) + deleteEmailsBtn.setAttribute('disabled', 'disabled') + currentDeleteEmails = emailsStr; + } + } +}) + +function validateEmails (mailsArray) { + for (const email of mailsArray) { + if (!isEmail(email)) { + return false + } + } + return true +} + +function isEmail (str) { + const pattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@innopolis.university$/ + return !!(str.match(pattern)) +} + +function enableDisableNameField(txt) { + newMailingListNameBtn.disabled = txt.value.trim() === "" || txt.value.trim() === currentMailingListName; +} + +function enableDisableEmailsArea(txt) { + changeEmailsBtn.disabled = txt.value.trim() === "" || txt.value.trim() === currentPatchEmails; +} + +function enableDisableAddEmailsArea(txt) { + addEmailsBtn.disabled = txt.value.trim() === "" || txt.value.trim() === currentAddEmails; +} + +function enableDisableDeleteEmailsArea(txt) { + deleteEmailsBtn.disabled = txt.value.trim() === "" || txt.value.trim() === currentDeleteEmails; +} +start().then() diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/edit_pages.css b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_pages.css new file mode 100644 index 0000000..a1dba7f --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/edit_pages.css @@ -0,0 +1,25 @@ +textarea { + text-align: left; + border-radius: 5px; + width: 50vw; + height: 20vh; +} +input { + border-radius: 5px; +} +label { + font-size: 20px +} +.major { + margin-left: 4vw; + margin-top: 7vh; + margin-bottom: 2vh +} +h3 { + margin-left: 40px; + display: inline-block; +} +h2 { + margin-left: 40px; + margin-top: 60px; +} \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/index.html b/src/backend/TimetableLinkAPI/src/main/resources/static/index.html new file mode 100644 index 0000000..dfd0c19 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/index.html @@ -0,0 +1,126 @@ + + + + + + + + + Document + + + + + +
+
+
+

Choose mailing list

+
+
+ + +
+ +
+
+

OR create new mailings lists

+ +
+ +
+ +

+ +
+ +
+ +
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + + diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/load_mail_lists_to_calendar.js b/src/backend/TimetableLinkAPI/src/main/resources/static/load_mail_lists_to_calendar.js new file mode 100644 index 0000000..b402a5d --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/load_mail_lists_to_calendar.js @@ -0,0 +1,43 @@ +async function start() { + const mailResponse = await fetch('/mailingLists/names', { + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }); + const nameArray = await mailResponse.json(); + loadCheckbox(nameArray); +} + +function loadCheckbox(nameArray) { + let cb = document.getElementById("cb"); + let cnt = 0; + + for (name of nameArray) { + let checkbox = document.createElement('input'); + checkbox.type = "checkbox"; + checkbox.id = `cb${cnt}`; + + let br = document.createElement('br'); + let label = document.createElement('label') + label.htmlFor = `cb${cnt++}`; + label.appendChild(document.createTextNode(name)) + + cb.append(checkbox); + cb.appendChild(label); + cb.append(br); + } +} + +// console always displays "unchecked" +let checkboxes = document.querySelectorAll('.loadedByScript'); +for (let checkbox of checkboxes) { + checkbox.addEventListener('click', () => { + if (checkbox.checked === true) { + console.log(this.value); + } else { + console.log('unchecked') + } + }) +} + +start().then() \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/login.html b/src/backend/TimetableLinkAPI/src/main/resources/static/login.html new file mode 100644 index 0000000..c9275f3 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/login.html @@ -0,0 +1,32 @@ + + + + + + + + Please sign in + + + + +
+ +
+ + + + + diff --git a/src/frontend/static/moment.js b/src/backend/TimetableLinkAPI/src/main/resources/static/moment.js similarity index 100% rename from src/frontend/static/moment.js rename to src/backend/TimetableLinkAPI/src/main/resources/static/moment.js diff --git a/src/frontend/static/popper.js b/src/backend/TimetableLinkAPI/src/main/resources/static/popper.js similarity index 100% rename from src/frontend/static/popper.js rename to src/backend/TimetableLinkAPI/src/main/resources/static/popper.js diff --git a/src/backend/TimetableLinkAPI/src/main/resources/static/post_mailing_list.js b/src/backend/TimetableLinkAPI/src/main/resources/static/post_mailing_list.js new file mode 100644 index 0000000..3a1fb4e --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/post_mailing_list.js @@ -0,0 +1,50 @@ +const btn = document.getElementById('create-btn') +btn.addEventListener('click', (event) => { + event.preventDefault() + const mailsStr = document.getElementById('emails').value + const mailsArray = mailsStr.split(' ').filter(el => el !== '') + + if (validateEmails(mailsArray)) { + const mailsName = document.getElementById('mailing-list-name').value + + fetch('/mailingLists', { + method: 'POST', + body: JSON.stringify({ + emails: mailsArray, + textIdentifier: mailsName + }), + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => response.text()) + .then((data) => { + console.log(data); + }) + .catch(error => console.log(error)) + } else { + alert('Emails are not in correct form. Try again') + } +}) + +function validateEmails (mailsArray) { + for (const email of mailsArray) { + if (!isEmail(email)) { + return false + } + } + return true +} + +function isEmail (str) { + const pattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@innopolis.university$/ + return !!(str.match(pattern)) +} + +function enableDisable(txt1, txt2) { + if (txt1.value === "" || txt2.value.trim() === "") + btn.setAttribute('disabled', 'disabled') + else + btn.removeAttribute('disabled') +} \ No newline at end of file diff --git a/src/frontend/static/script.js b/src/backend/TimetableLinkAPI/src/main/resources/static/script.js similarity index 54% rename from src/frontend/static/script.js rename to src/backend/TimetableLinkAPI/src/main/resources/static/script.js index a9b83b6..87e20e4 100644 --- a/src/frontend/static/script.js +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/script.js @@ -25,7 +25,6 @@ //Draw Month this.drawMonth(); - this.drawLegend(); } Calendar.prototype.drawHeader = function() { @@ -58,8 +57,8 @@ let self = this; this.events.forEach(function(ev) { - d = Math.random() * (29 - 1) + 1 - ev.date = self.current.clone().date(d); + let m = moment(ev.startDate, "YYYY-MM-DDTHH:mm:ss.SSS+HH:mm"); + ev.date = m; }); @@ -132,9 +131,10 @@ this.getWeek(day); //Outer Day - let outer = createElement('div', this.getDayClass(day)); + let classes = this.getDayClass(day); + let outer = createElement('div', classes); outer.addEventListener('click', function() { - self.openDay(this); + if (!classes.includes("other")) self.openDay(this); }); //Day Name @@ -165,7 +165,7 @@ todaysEvents.forEach(function(ev) { - let evSpan = createElement('span', ev.color); + let evSpan = createElement('span', "blue"); element.appendChild(evSpan); }); } @@ -181,6 +181,16 @@ return classes.join(' '); } + Calendar.prototype.getEventType = function(day) { + classes = ['day']; + if(day.month() !== this.current.month()) { + classes.push('other'); + } else if (today.isSame(day, 'day')) { + classes.push('today'); + } + return classes.join(' '); + } + Calendar.prototype.openDay = function(el) { let details, arrow; let dayNumber = +el.querySelectorAll('.day-number')[0].innerText || +el.querySelectorAll('.day-number')[0].textContent; @@ -189,13 +199,6 @@ let currentOpened = document.querySelector('.details'); - //Check to see if there is an open detais box on the current row - //if (true){ //(currentOpened && currentOpened.parentNode === el.parentNode) { - // details = currentOpened; - // arrow = document.querySelector('.arrow'); - // } else { - //Close the open events on differnt week row - //currentOpened && currentOpened.parentNode.removeChild(currentOpened); if(currentOpened) { currentOpened.addEventListener('webkitAnimationEnd', function() { currentOpened.parentNode.removeChild(currentOpened); @@ -237,94 +240,151 @@ arrow.style.left = el.offsetLeft - el.parentNode.offsetLeft + 27 + 'px'; } + function getEventForm(eventName = "", eventloc = "", starttime = "Start", endtime = "End", eventid=null){ + let br = createElement('br'); + let eventForm = createElement('form'); + eventForm.setAttribute('method', 'post'); + let eventTitle = createElement('input', 'eventtitle'); + eventTitle.setAttribute('id', 'eventtitle'); + eventTitle.setAttribute('type', 'text'); + eventTitle.setAttribute('placeholder', 'Event Title'); + eventTitle.setAttribute('value', eventName); + let eventLocation = createElement('input', 'eventlocation'); + eventLocation.setAttribute('id', 'eventlocation'); + eventLocation.setAttribute('type', 'text'); + eventLocation.setAttribute('placeholder', 'Event Location'); + eventLocation.setAttribute('value', eventloc); + let times = [ + '00:00', '00:30', '01:00', '01:30', '02:00', '02:30', '03:00', + '03:30', '04:00', '04:30', '05:00', '05:30', '06:00', '06:30', + '07:00', '07:30', '08:00', '08:30', '09:00', '09:30', '10:00', + '10:30', '11:00', '11:30', '12:00', '12:30', '13:00', '13:30', + '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00', + '17:30', '18:00', '18:30', '19:00', '19:30', '20:00', '20:30', + '21:00', '21:30', '22:00', '22:30', '23:00', '23:30' + ] + const eventStart = createElement('select', 'eventstarttime'); + eventStart.appendChild(new Option(starttime, starttime, true)); + eventStart.setAttribute('id', 'eventstarttime'); + const eventEnd = createElement('select', 'eventendtime'); + eventEnd.appendChild(new Option(endtime, endtime, true)); + eventEnd.setAttribute('id', 'eventendtime'); + times.forEach(function(item, _){ + eventStart.appendChild(new Option(item, item)); + eventEnd.appendChild(new Option(item, item)); + }); + const submitForm = createElement('button', 'eventSubmit'); + submitForm.innerText = '➔'; + eventForm.appendChild(eventTitle); + eventForm.appendChild(eventLocation); + eventForm.appendChild(br.cloneNode()); + eventForm.appendChild(eventStart); + eventForm.appendChild(eventEnd); + eventForm.appendChild(submitForm); + submitForm.addEventListener('click', (event) => { + event.preventDefault(); + let date = document.getElementById('datecontainer').dataset['date']; + let startTime = document.getElementById('eventstarttime').value; + let endTime = document.getElementById('eventendtime').value; + let name = document.getElementById('eventtitle').value; + let location = document.getElementById('eventlocation').value; + let dict = { + 'January': '01', 'February': '02', 'March': '03', 'April': '04', 'May': '05', 'June': '06', 'July': '07', + 'August': '08', 'September': '09', 'October': '10', 'November': '11', 'December': '12' + }; + let monyear = document.getElementById('monthname').innerHTML.split(' '); + let month = dict[monyear[0]]; + let year = monyear[1]; + let formattedStart = `${year}-${month}-${date < 10 ? '0' : ''}${date}T${startTime}:00.000+03:00`; + let formattedEnd = `${year}-${month}-${date < 10 ? '0' : ''}${date}T${endTime}:00.000+03:00`; + + if (eventid != null){ + fetch(`/events/${eventid}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + }, + body: JSON.stringify({'name': name, + 'location': location, + 'startDate': formattedStart, + 'endDate': formattedEnd}) + }) + .then (response => { + if (response.status == 200){ + window.location.reload(); + } else { + return; + }; + }) + .then ((data) => { + console.log(data); + document.getElementsByClassName('event empty')[0].innerHTML = 'Event changed Successfully!'; + window.location.reload(); + }) + .catch(error => console.log(error)); + } else { + fetch('/events', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': sessionStorage.getItem("token") + }, + body: JSON.stringify({'name': name, + 'location': location, + 'startDate': formattedStart, + 'endDate': formattedEnd}) + }) + .then (response => { + response.json(); + }) + .then ((data) => { + console.log(data); + document.getElementsByClassName('event empty')[0].innerHTML = 'Event Added Successfully!'; + window.location.reload(); + }) + .catch(error => console.log(error)); + } + }); + return eventForm; + } Calendar.prototype.renderEvents = function(events, ele) { //Remove any events in the current details element let currentWrapper = ele.querySelector('.events'); let wrapper = createElement('div', 'events in' + (currentWrapper ? ' new' : '')); + function remAndAdd(elem, sq) { + console.log(elem); + p = elem.parentNode; + p.removeChild(elem); + p.removeChild(sq); + console.log(elem.dataset.eventstart); + starttime = moment(elem.dataset.eventstart).format("HH:mm"); + endtime = moment(elem.dataset.eventend).format("HH:mm"); + p.appendChild(getEventForm(elem.dataset.eventname, elem.dataset.eventlocation, starttime, endtime, elem.dataset.eventid)); + } + events.forEach(function(ev) { let div = createElement('div', 'event'); - let square = createElement('div', 'event-category ' + ev.color); - let span = createElement('span', '', ev.eventName); - + let square = createElement('div', 'event-category ' + 'blue'); + let span = createElement('span', '', ev.name); + span.setAttribute('data-eventId', ev.id); + span.setAttribute('data-eventName', ev.name); + span.setAttribute('data-eventLocation', ev.location); + span.setAttribute('data-eventStart', ev.startDate); + span.setAttribute('data-eventEnd', ev.endDate); div.appendChild(square); div.appendChild(span); wrapper.appendChild(div); + span.addEventListener('click', () => remAndAdd(span, square)); }); if(!events.length) { let div = createElement('div', 'event empty'); - let br = createElement('br'); - let eventForm = createElement("form"); - eventForm.setAttribute("method", "post"); - let eventTitle = createElement('input', 'eventtitle'); - eventTitle.setAttribute('id', 'eventtitle'); - eventTitle.setAttribute('type', 'text'); - eventTitle.setAttribute('placeholder', 'Event Title'); - let eventLocation = createElement('input', 'eventlocation'); - eventLocation.setAttribute('id', 'eventlocation'); - eventLocation.setAttribute('type', 'text'); - eventLocation.setAttribute('placeholder', 'Event Location'); - let times = [ - '00:00', '00:30', '01:00', '01:30', '02:00', '02:30', '03:00', - '03:30', '04:00', '04:30', '05:00', '05:30', '06:00', '06:30', - '07:00', '07:30', '08:00', '08:30', '09:00', '09:30', '10:00', - '10:30', '11:00', '11:30', '12:00', '12:30', '13:00', '13:30', - '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00', - '17:30', '18:00', '18:30', '19:00', '19:30', '20:00', '20:30', - '21:00', '21:30', '22:00', '22:30', '23:00', '23:30' - ] - const eventStart = createElement('select', 'eventstarttime'); - eventStart.appendChild(new Option('Start', 'Start', true)); - eventStart.setAttribute('id', 'eventstarttime'); - const eventEnd = createElement('select', 'eventendtime'); - eventEnd.appendChild(new Option('End', 'End', true)); - eventEnd.setAttribute('id', 'eventendtime'); - times.forEach(function(item, _){ - eventStart.appendChild(new Option(item, item)); - eventEnd.appendChild(new Option(item, item)); - }); - const submitForm = createElement('button', 'eventSubmit'); - submitForm.innerText = '➔'; - eventForm.appendChild(eventTitle); - eventForm.appendChild(eventLocation); - eventForm.appendChild(br.cloneNode()); - eventForm.appendChild(eventStart); - eventForm.appendChild(eventEnd); - eventForm.appendChild(submitForm); + let eventForm = getEventForm(); div.appendChild(eventForm); wrapper.appendChild(div); - submitForm.addEventListener('click', (event) => { - event.preventDefault(); - let date = document.getElementById('datecontainer').dataset['date']; - let startTime = document.getElementById('eventstarttime').value; - let endTime = document.getElementById('eventendtime').value; - let name = document.getElementById('eventtitle').value; - let location = document.getElementById('eventlocation').value; - let dict = { - "January": '01', "February": "02", "March": "03", "April": "04", "May": "05", "June": "06", "Jule": "07", - "August": "08", "September": "09", "October": "10", "November": "11", "December": "12" - }; - let monyear = document.getElementById('monthname').innerHTML.split(" "); - let month = dict[monyear[0]]; - let year = monyear[1]; - let formattedStart = `${year}-${month}-${date}T${startTime}:00.000+03:00`; - let formattedEnd = `${year}-${month}-${date}T${endTime}:00.000+03:00`; - - fetch("/events", { - method: 'POST', - body: JSON.stringify({"name": name, - "location": location, - "startDate": formattedStart, - "endDate": formattedEnd}) - }) - .then (response => response.json()) - .then (() => { - document.getElementsByClassName('event empty')[0].innerHTML = "Event Added Successfully!"; - }) - .catch(error => console.log(error)); - }); } if(currentWrapper) { @@ -394,16 +454,55 @@ }(); !function() { - let data = [ - ]; - + let self = this; + fetch('/events', { + method: 'GET', + headers: { + 'Authorization': sessionStorage.getItem("token") + } + }) + .then(response => { + if (response.status == 403){ + console.log("Crying"); + return; + } + return response.json(); + }) + .then(data => { + let calendar = new Calendar('#calendar', data); + }) + .catch(err => { + console.log(err); + let calendar = new Calendar('#calendar', [ + { + "id": "abcdefg", + "name": "Event 1", + "location": "1234", + "startDate": "2022-07-04T09:30:00.000Z", + "endDate": "2022-07-04T10:30:00.000Z" + } + ]); + }); function addDate(ev) { } - let calendar = new Calendar('#calendar', data); - }(); +let showCreateMailingListPageBtn = document.getElementById('show-create-mailing-list-page-btn') +showCreateMailingListPageBtn.addEventListener('click', (event) => { + event.preventDefault(); + let createMailingListPage = document.getElementById('create-mailing-list-page') + + if (showCreateMailingListPageBtn.innerText === 'Create') { + createMailingListPage.removeAttribute('disabled') + showCreateMailingListPageBtn.innerText = 'Cancel' + } else { + createMailingListPage.setAttribute('disabled', 'true'); + showCreateMailingListPageBtn.innerText = 'Create'; + } + +}) + diff --git a/src/frontend/static/style.css b/src/backend/TimetableLinkAPI/src/main/resources/static/style.css similarity index 95% rename from src/frontend/static/style.css rename to src/backend/TimetableLinkAPI/src/main/resources/static/style.css index aa3a3bd..cdb8fb1 100644 --- a/src/frontend/static/style.css +++ b/src/backend/TimetableLinkAPI/src/main/resources/static/style.css @@ -3,7 +3,7 @@ } body { - overflow: hidden; + /*overflow: hidden;*/ font-family: 'HelveticaNeue-UltraLight', 'Helvetica Neue UltraLight', 'Helvetica Neue', Arial, Helvetica, sans-serif; font-weight: 100; color: rgba(255, 255, 255, 1); @@ -25,7 +25,7 @@ width: 420px; margin: 0 auto; height: 570px; - overflow: hidden; + /*overflow: hidden;*/ } .header { @@ -138,7 +138,12 @@ color: rgba(255, 255, 255, .5); letter-spacing: .7px; } - + + ul { + text-align: center; + list-style: inside; + } + .day-number { font-size: 24px; letter-spacing: 1.5px; @@ -151,14 +156,13 @@ text-align: center; height: 12px; line-height: 6px; - overflow: hidden; + /*overflow: hidden;*/ } .day .day-events span { vertical-align: top; display: inline-block; padding: 0; - margin: 0; width: 5px; height: 5px; line-height: 5px; @@ -241,6 +245,8 @@ letter-spacing: .5px; padding: 2px 16px; vertical-align: top; + color: black; + font-weight: 400; } .event.empty { @@ -305,7 +311,7 @@ } @-webkit-keyframes moveToTopFade { - to { opacity: .3; height:0px; margin-top:0px; opacity: 0.3; -webkit-transform: translateY(-100%); } + to { opacity: .3; height:0px; margin-top:0px; -webkit-transform: translateY(-100%); } } @-moz-keyframes moveToTopFade { to { height:0px; -moz-transform: translateY(-100%); } @@ -431,8 +437,14 @@ } .eventsubmit { - background-color: transparent; - color: rgba(0, 0, 0); - width: 40px; - border-color: transparent; -} \ No newline at end of file + background-color: transparent; + color: rgba(0, 0, 0, 1); + width: 40px; + border-color: transparent; +} + +textarea { + width: 30vw; +} + + diff --git a/src/backend/TimetableLinkAPI/src/test/java/com/timetable/TimetableLinkApiApplicationTests.java b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/TimetableLinkApiApplicationTests.java index 58795b0..9fb5229 100644 --- a/src/backend/TimetableLinkAPI/src/test/java/com/timetable/TimetableLinkApiApplicationTests.java +++ b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/TimetableLinkApiApplicationTests.java @@ -1,11 +1,8 @@ -package com.timetable; +// package com.timetable; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.junit.Assert; +// import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest -class TimetableLinkApiApplicationTests { +// @SpringBootTest +// class TimetableLinkApiApplicationTests { -} +// } diff --git a/src/backend/TimetableLinkAPI/src/test/java/com/timetable/event/EventMySQLRepositoryTest.java b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/event/EventMySQLRepositoryTest.java new file mode 100644 index 0000000..2932cfc --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/event/EventMySQLRepositoryTest.java @@ -0,0 +1,32 @@ +// package com.timetable.event; + +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; +// import org.springframework.jdbc.core.JdbcTemplate; +// import org.springframework.test.context.jdbc.Sql; + +// import java.util.List; + +// import static org.junit.jupiter.api.Assertions.*; + +// @DataJdbcTest +// @Sql({"/h2_schema.sql"}) +// class EventMySQLRepositoryTest { +// JdbcTemplate jdbcTemplate; +// EventMySQLRepository repo; + +// @Autowired +// EventMySQLRepositoryTest(JdbcTemplate jdbcTemplate) { +// this.jdbcTemplate = jdbcTemplate; +// this.repo = new EventMySQLRepository(jdbcTemplate); +// } + +// @Test +// void operateEvents() { +// repo.saveEvent("1"); +// assertEquals(repo.getAllIds(), List.of("1")); +// repo.deleteEvent("1"); +// assertEquals(repo.getAllIds(), List.of()); +// } +// } \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListMySQLRepositoryTest.java b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListMySQLRepositoryTest.java new file mode 100644 index 0000000..195bd79 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListMySQLRepositoryTest.java @@ -0,0 +1,158 @@ +// package com.timetable.mailing_list; + +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; +// import org.springframework.jdbc.core.JdbcTemplate; +// import org.springframework.jdbc.core.SqlOutParameter; +// import org.springframework.test.context.jdbc.Sql; + +// import java.util.*; + +// import static org.junit.jupiter.api.Assertions.*; + +// @DataJdbcTest +// @Sql({"/h2_schema.sql"}) +// class MailingListMySQLRepositoryTest { +// JdbcTemplate jdbcTemplate; +// MailingListMySQLRepository repo; +// List mailingLists; + +// @Autowired +// MailingListMySQLRepositoryTest(JdbcTemplate jdbcTemplate) { +// this.jdbcTemplate = jdbcTemplate; +// this.repo = new MailingListMySQLRepository(jdbcTemplate); +// mailingLists = new ArrayList<>(); +// MailingList mailingList1 = new MailingList( +// "list1", Arrays.asList("a", "b", "c", "d")); +// MailingList mailingList2 = new MailingList( +// "list2", Arrays.asList("e", "f", "g", "d")); +// mailingLists.add(mailingList1); +// mailingLists.add(mailingList2); +// } + +// @Test +// void createAndGetMailingListsNames() { +// MailingList mailingList = mailingLists.get(0); +// repo.createMailingList(mailingList); +// Long id = repo.getMailingListId("list1"); +// MailingList currentMailingList = repo.getMailingList(id); +// assertEquals(currentMailingList, mailingList); +// System.out.println(repo.getMailingListId("list1")); +// } + +// @Test +// void getAllMailingLists() { +// mailingLists.forEach(mailingList -> repo.createMailingList(mailingList)); +// Set allMailingLists = new HashSet<>(repo.getAllMailingLists()); +// Set expectedMailingLists = new HashSet<>(); +// expectedMailingLists.add(mailingLists.get(0)); +// expectedMailingLists.add(mailingLists.get(1)); +// assertEquals(expectedMailingLists, allMailingLists); + +// } + +// @Test +// void deleteMailingList() { +// mailingLists.forEach(mailingList -> repo.createMailingList(mailingList)); +// Long id = repo.getMailingListId("list2"); +// repo.deleteMailingList(id); +// assertThrows(org.springframework.dao.EmptyResultDataAccessException.class, () -> repo.getMailingList(id)); +// } + +// @Test +// void addEmailToList() { +// repo.createMailingList(mailingLists.get(0)); +// String newEmail = "r"; +// Long id = repo.getMailingListId("list1"); +// repo.addEmailToList(id, newEmail); +// assertTrue(repo.getMailingList(id).getEmails().contains(newEmail)); +// } + +// @Test +// void deleteEmailFromList() { +// repo.createMailingList(mailingLists.get(0)); +// repo.createMailingList(mailingLists.get(1)); +// List emails = new ArrayList<>(List.of("a", "b", "c", "d")); +// Long id1 = repo.getMailingListId("list1"); +// for (String emailToDelete : emails) { +// System.out.println(repo.getEmailsByListId(id1)); +// repo.deleteEmailFromList(id1, emailToDelete); +// assertFalse(repo.getMailingList(id1).getEmails().contains(emailToDelete)); +// } +// Long id2 = repo.getMailingListId("list2"); +// assertTrue(repo.getMailingList(id2).getEmails().containsAll(mailingLists.get(1).getEmails())); +// emails.addAll(List.of("e", "f", "g", "l", "t", "p")); +// for (String emailToAdd : emails) { +// repo.addEmailToList(id1, emailToAdd); +// assertTrue(repo.getMailingList(id1).getEmails().contains(emailToAdd)); +// } +// //System.out.println(repo.getMailingList(id1).getEmails().toString()); +// for (String emailToDelete : mailingLists.get(1).getEmails()) { +// repo.deleteEmailFromList(id2, emailToDelete); +// assertFalse(repo.getMailingList(id2).getEmails().contains(emailToDelete)); +// } +// //System.out.println(repo.getEmailsByListId(id1).size() + repo.getEmailsByListId(id2).size());; +// } + +// @Test +// void addAndDeleteEmails() { +// MailingList mailingList = mailingLists.get(0); +// repo.createMailingList(mailingLists.get(0)); +// Long id = repo.getMailingListId(mailingList.getTextIdentifier()); +// for (String email : mailingList.getEmails()) { +// assertTrue(repo.getEmailsByListId(id).contains(email)); +// repo.deleteEmailFromList(id, email); +// assertFalse(repo.getEmailsByListId(id).contains(email)); +// } +// assertEquals(repo.getEmailsByListId(id).size(), 0); +// List emailsToAdd = new ArrayList<>(mailingLists.get(0).getEmails()); +// emailsToAdd.addAll(List.of("o", "l", "t")); +// for (String email : emailsToAdd) { +// assertFalse(repo.getEmailsByListId(id).contains(email)); +// repo.addEmailToList(id, email); +// assertTrue(repo.getEmailsByListId(id).contains(email)); +// } +// List currentEmails = repo.getEmailsByListId(id); +// assertTrue(currentEmails.containsAll(emailsToAdd)); +// assertEquals(currentEmails.size(), emailsToAdd.size()); +// } + +// @Test +// void mailingListExists() { +// repo.createMailingList(mailingLists.get(0)); +// assertTrue(repo.mailingListExists("list1")); +// assertFalse(repo.mailingListExists("list3")); +// } + +// @Test +// void updateMailingList() { +// MailingList mailingList = mailingLists.get(0); +// repo.createMailingList(mailingList); +// Long id = repo.getMailingListId(mailingList.getTextIdentifier()); +// MailingList updatedMailingList = new MailingList( +// "list1", new ArrayList<>() +// ); +// repo.updateMailingList(updatedMailingList); +// assertEquals(updatedMailingList, repo.getMailingList(id)); +// } + +// @Test +// void updateTextIdentifier() { +// MailingList mailingList = mailingLists.get(0); +// repo.createMailingList(mailingList); +// Long id = repo.getMailingListId(mailingList.getTextIdentifier()); +// String newTextIdentifier = "list2"; +// repo.updateTextIdentifier(mailingList.getTextIdentifier(), newTextIdentifier); +// assertEquals(new MailingList(newTextIdentifier, mailingList.getEmails()), +// repo.getMailingList(id)); +// } + +// @Test +// void getEmailsByListId() { +// MailingList mailingList = mailingLists.get(0); +// repo.createMailingList(mailingList); +// Long id = repo.getMailingListId(mailingList.getTextIdentifier()); +// assertEquals(mailingList.getEmails(), repo.getEmailsByListId(id)); +// } +// } \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListServiceTest.java b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListServiceTest.java new file mode 100644 index 0000000..f9b12fb --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/test/java/com/timetable/mailing_list/MailingListServiceTest.java @@ -0,0 +1,99 @@ +// package com.timetable.mailing_list; + +// import org.junit.jupiter.api.AfterAll; +// import org.junit.jupiter.api.AfterEach; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.mockito.ArgumentCaptor; +// import org.mockito.Mock; +// import org.mockito.Mockito; +// import org.mockito.MockitoAnnotations; +// import org.springframework.boot.test.mock.mockito.MockBean; + +// import java.util.Arrays; +// import java.util.List; + +// import static org.junit.jupiter.api.Assertions.*; +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.ArgumentMatchers.anyLong; +// import static org.mockito.Mockito.times; +// import static org.mockito.Mockito.verify; + +// class MailingListServiceTest { +// @Mock +// private MailingListMySQLRepository repo; +// @Mock +// private OutlookMailingListManager manager; +// private AutoCloseable autoCloseable; +// private MailingListService underTest; +// MailingList mailingList; + +// MailingListServiceTest() { +// mailingList = new MailingList( +// "list1", Arrays.asList("a", "b", "c", "d")); +// } + +// @BeforeEach +// void setUp() { +// autoCloseable = MockitoAnnotations.openMocks(this); +// underTest = new MailingListService(repo, manager); +// } + +// @AfterEach +// void tearDown() throws Exception { +// autoCloseable.close(); +// } + +// @Test +// void getMailingListsNames() { +// underTest.getMailingListsNames(); + +// verify(repo).getMailingListsNames(); +// } + +// @Test +// void createMailingList() { +// underTest.createMailingList(mailingList); + +// verify(repo, times(1)).createMailingList(mailingList); +// } + +// @Test +// void deleteMailingList() { +// underTest.createMailingList(mailingList); + +// verify(repo, times(1)).createMailingList(mailingList); +// } + +// @Test +// void addEmailsToList() { +// List newEmails = List.of("k", "l"); +// Mockito.doReturn(1L) +// .when(repo) +// .getMailingListId(mailingList.getTextIdentifier()); + +// underTest.addEmailsToList("list1", newEmails); + +// verify(repo, times(1)).getMailingListId(mailingList.getTextIdentifier()); + +// for (String email : newEmails) { +// verify(repo, times(1)).addEmailToList(1L, email); +// } +// } + +// @Test +// void deleteEmailsFromList() { +// List newEmails = List.of("k", "l"); +// Mockito.doReturn(1L) +// .when(repo) +// .getMailingListId(mailingList.getTextIdentifier()); + +// underTest.deleteEmailsFromList("list1", newEmails); + +// verify(repo, times(1)).getMailingListId(mailingList.getTextIdentifier()); + +// for (String email : newEmails) { +// verify(repo, times(1)).deleteEmailFromList(1L, email); +// } +// } +// } \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/test/resources/application.properties b/src/backend/TimetableLinkAPI/src/test/resources/application.properties new file mode 100644 index 0000000..c3fae13 --- /dev/null +++ b/src/backend/TimetableLinkAPI/src/test/resources/application.properties @@ -0,0 +1,7 @@ +spring.datasource.url=jdbc:h2:mem:timetable;MODE=MYSQL +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console +spring.sql.init.mode=never \ No newline at end of file diff --git a/src/backend/TimetableLinkAPI/src/test/resources/h2_data.sql b/src/backend/TimetableLinkAPI/src/test/resources/h2_data.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/TimetableLinkAPI/init.sql b/src/backend/TimetableLinkAPI/src/test/resources/h2_schema.sql similarity index 91% rename from src/backend/TimetableLinkAPI/init.sql rename to src/backend/TimetableLinkAPI/src/test/resources/h2_schema.sql index 4436203..4112626 100644 --- a/src/backend/TimetableLinkAPI/init.sql +++ b/src/backend/TimetableLinkAPI/src/test/resources/h2_schema.sql @@ -1,5 +1,4 @@ -CREATE DATABASE IF NOT EXISTS timetable; -USE timetable; +SET MODE=MYSQL; CREATE TABLE IF NOT EXISTS mailingList ( id BIGINT PRIMARY KEY AUTO_INCREMENT, @@ -21,4 +20,4 @@ CREATE TABLE IF NOT EXISTS emailBelonging( CREATE TABLE IF NOT EXISTS event( outlookAppointmentId varchar(400) PRIMARY KEY -); \ No newline at end of file +); diff --git a/src/frontend/index.html b/src/frontend/index.html deleted file mode 100644 index 75ac096..0000000 --- a/src/frontend/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - Document - - - -
-
-
- - - - - - - \ No newline at end of file diff --git a/wwsssswswswsws b/wwsssswswswsws new file mode 100644 index 0000000..513abad --- /dev/null +++ b/wwsssswswswsws @@ -0,0 +1,647 @@ +commit 7f58c735383b002049588d6872d142a8af6343bc (HEAD) +Merge: cffe345 545d767 +Author: OldCoachman +Date: Sat Jul 2 19:20:16 2022 +0300 + + Merge remote-tracking branch 'origin/backend_development' into EventFrontend + +commit 545d767ff6a0ab46c02dec46d5335c34613fa70a (backend_development) +Merge: 553ee90 2d5286c +Author: FK12344321 +Date: Sat Jul 2 19:18:26 2022 +0300 + + Merge branch 'backend_development' of https://github.com/InnoSWP/CrosslinkTimetable into backend_development + +commit 553ee9034460258ac4c9258cbe920ca27e1a3cb4 +Author: FK12344321 +Date: Sat Jul 2 19:17:57 2022 +0300 + + started implementing authentication + +commit cffe3452e36af5deb3e7c533a5d0b1fb0e7856cc +Merge: aa85cc4 2d5286c +Author: OldCoachman +Date: Sat Jul 2 19:15:26 2022 +0300 + + Merge branch 'backend_development' of https://github.com/InnoSWP/CrosslinkTimetable into EventFrontend + +commit 2d5286c33e6007a8ed0e45193f2e67806fa88787 +Author: Ivan Kornienko <101111710+OldCoachman@users.noreply.github.com> +Date: Sat Jul 2 19:14:57 2022 +0300 + + Delete src/backend/TimetableLinkAPI/data/demo #jdbc:h2: directory + +commit aa85cc47cebe97ed3a32efef6a14690e42934385 +Merge: 7a6d985 97f687c +Author: OldCoachman +Date: Sat Jul 2 19:02:09 2022 +0300 + + merged + +commit 2f219fa4cf298cd1129208029556f6b5fe37188f +Merge: 2da87d9 7a6d985 +Author: FK12344321 +Date: Sat Jul 2 10:58:37 2022 +0300 + + merged EventFrontend + +commit 2da87d9f7e98b97e02223bd96aab0ee0096cc6b1 +Author: FK12344321 +Date: Sat Jul 2 10:48:42 2022 +0300 + + Add more tests + +commit 7a6d985965c88c1cc9ba5769e231229c8c1e6507 (origin/EventFrontend) +Author: OldCoachman +Date: Fri Jul 1 21:44:29 2022 +0300 + + test + +commit 3246e7af0cecc0b237ca7c00ea91fc7e1a1ae035 +Merge: 80b828a 842f1b1 +Author: OldCoachman +Date: Fri Jul 1 12:58:05 2022 +0300 + + Merge remote-tracking branch 'origin/EventFrontend' into EventFrontend + +commit 80b828ab0dd19d1ea4f360a5660668587a79a899 +Author: OldCoachman +Date: Fri Jul 1 12:56:39 2022 +0300 + + did demo index + +commit 842f1b16f034ec1dee283428036c5cf0a7d184cc +Author: Snapman7 +Date: Fri Jul 1 10:43:44 2022 +0300 + + completed some parts of the Readme, and also added a demo image. + +commit 97f687c81147ae12b658993de9cb35f765739a7b (back_development) +Author: OldCoachman +Date: Thu Jun 30 22:00:05 2022 +0300 + + Merge branch 'back_development' of D:\Codes\crosslink with conflicts. + +commit f03db72a89315ec52c8bec2d5cf4f94a1f993942 +Author: OldCoachman +Date: Thu Jun 30 13:27:50 2022 +0300 + + set disable/unable btn + +commit 8506dd33d1460979c79ccea45d1a2ad4126b7a68 +Author: OldCoachman +Date: Thu Jun 30 13:20:09 2022 +0300 + + edited design & changed edit mail list logic + +commit 4d83def37c149b488df79abb6ecc90fa078ca15a (origin/back_development) +Author: FK12344321 +Date: Thu Jun 30 10:23:01 2022 +0300 + + eventId is returned as Json + +commit 9c740d71cae1fbe7cfcb0935f5a31f28a77326d0 +Author: pptx704 +Date: Thu Jun 30 09:24:25 2022 +0300 + + Patch: Shows created events on calendar. Edit functionality still to be built. Bug fixed on email validator + +commit 7680b9ffbbbc09a0cfd6e7fb2cc3691809ef12fb +Author: pptx704 +Date: Thu Jun 30 04:22:32 2022 +0300 + + Small bugfix. Nothing useful + +commit 8c4495d226328bae9717f422a8ff6c222ed3519b +Author: OldCoachman +Date: Wed Jun 29 21:47:43 2022 +0300 + + edited time slots and edit mailing list + +commit 4e017ec34b3465cf7dc9f2d6e41e0c6233a83b33 +Author: OldCoachman +Date: Wed Jun 29 21:32:31 2022 +0300 + + test + +commit af36a33665b9ef212577984ec9b20bb6edcc4cd4 +Author: FK12344321 +Date: Wed Jun 29 16:59:10 2022 +0300 + + new tests + +commit 64c87519eca01d39e4ba19d65fc6478f894a655b +Merge: a047994 3e8b0cc +Author: OldCoachman +Date: Wed Jun 29 16:50:06 2022 +0300 + + Merge remote-tracking branch 'origin/EventFrontend' into EventFrontend + +commit a04799477939be9cf2bd23971d2f3afe9cd4ad95 +Author: OldCoachman +Date: Wed Jun 29 16:49:45 2022 +0300 + + pain + +commit 3e8b0cc87236f8e35bbaf471ab43b88f10d3dedf +Author: Snapman7 +Date: Wed Jun 29 15:55:28 2022 +0300 + + Added License.txt with MIT license and updated README.md + +commit 6534ef9fd4444ec8942bae904d8694ca9c36cf62 +Author: OldCoachman +Date: Tue Jun 28 22:04:52 2022 +0300 + + removed center alignment and fixed headers + +commit 354bca07b91451e02a42068a9d35f36fd4452f76 +Author: Snapman7 +Date: Tue Jun 28 19:27:30 2022 +0300 + + all date inputs edited on "date" + "time" type + +commit c0ee1538122d13d7e87d4c4f7475792ce94cc765 +Author: Snapman7 +Date: Tue Jun 28 18:58:19 2022 +0300 + + customized "Create mailing list", "Edit mailing list" and "edit event" pages. Also added scrolling on all pages. + +commit 719cc9cf3faa9465180927d5e7eb5d10a876423b +Merge: 676bba6 b1f7c44 +Author: FK12344321 +Date: Tue Jun 28 17:15:44 2022 +0300 + + pulled changes + +commit 676bba663fe6380a4e9f7e32283e58bc70075589 +Author: FK12344321 +Date: Tue Jun 28 17:14:04 2022 +0300 + + normal tests + +commit a8f97d12b74d4a72e31087494981d2ef24f2431f +Author: OldCoachman +Date: Tue Jun 28 14:58:51 2022 +0300 + + added method to mailing list and methods for events. DEMO + +commit 9ff26636810d4ad0e2db26dc3026dcb18b69902d +Author: OldCoachman +Date: Mon Jun 27 20:05:34 2022 +0300 + + test + +commit 556b526022eaa573e55528372fd5777462f618bb +Author: Snapman7 +Date: Mon Jun 27 15:26:57 2022 +0300 + + Added headers to edit_mailing_list.html and post_mailing_list.html + +commit 891fbff785ef1081f3fda08bea3d6fe78907f5de +Author: Snapman7 +Date: Mon Jun 27 14:25:05 2022 +0300 + + edited ids for code style + +commit b1f7c4489687a3b5dd2a0d5b9df76ef4fb22e1eb +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 23:04:51 2022 +0300 + + Update mvn.yml + +commit 487408941efca1536f3da12a9754becb0cc775e7 +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:57:08 2022 +0300 + + Update TimetableLinkApiApplicationTests.java + +commit 82198100ab95927801df891c01a7f4b926fe2cd9 +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:45:07 2022 +0300 + + Update and rename mvn to mvn.yml + +commit 1b0bd18651711c53380062f2e79e3360b0523f3c +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:43:26 2022 +0300 + + Create mvn + +commit ffeac972699747369d43b592d614b100b259ab4d +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:30:39 2022 +0300 + + Delete MailingListMySQLRepositoryTest.java + +commit 52bbe4a15b6beb7e033b462680318ac16b381f40 +Merge: 2060698 5507c2f +Author: FK12344321 +Date: Sat Jun 25 22:29:52 2022 +0300 + + Merge branch 'back_development' of https://github.com/InnoSWP/CrosslinkTimetable into back_development + +commit 5507c2f7805d3ec17fb05ea958c024917edb6e91 +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:24:34 2022 +0300 + + Update linter.yml + +commit 62ec2618acb052abda3edc768fbc1f8bcf1ab2bf +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 22:24:04 2022 +0300 + + Update linter.yml + +commit 206069815db28d1b9ff085d2792d56a4e7ff59ee +Author: FK12344321 +Date: Sat Jun 25 22:07:23 2022 +0300 + + fixed tests + +commit 09526e33a06db764f7400bacd6ae1908fd851eff +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Sat Jun 25 21:53:37 2022 +0300 + + update linter + +commit 19dd18123bacf7456feb9f06c8d59b3cdc10bef9 +Author: FK12344321 +Date: Sat Jun 25 21:03:05 2022 +0300 + + add tests + +commit 9d30fa2aabf328e8e6282f8696e121c613751a95 +Author: OldCoachman +Date: Sat Jun 25 20:05:00 2022 +0300 + + undo commented function and set left text align + +commit 9afdc720789570491ae5a53e69f65896a0f01aba +Author: Snapman7 +Date: Sat Jun 25 17:54:44 2022 +0300 + + fixed some bugs + +commit 9331651558458d953f9e85a84d326846d6295ed7 +Author: Snapman7 +Date: Sat Jun 25 17:46:28 2022 +0300 + + implemented method "fetching" + +commit c0fa4d462c74fe935fef4731d5cd2bb8ac044f1f +Author: Snapman7 +Date: Sat Jun 25 17:43:33 2022 +0300 + + implemented method "fetching" + +commit 20e20e16918e3f2608c0efda19d576f14e490416 +Author: Snapman7 +Date: Sat Jun 25 17:33:58 2022 +0300 + + edited files for linter AND erased in in edit_mailing_list.js in 87 \ + +commit f0962ef279799424c6c9aef746ce37fc495b5fc7 +Author: Snapman7 +Date: Sat Jun 25 17:21:20 2022 +0300 + + edited files for linter + +commit da59746b4ec6281dd33475dbed2479761a6a0386 +Author: Snapman7 +Date: Sat Jun 25 17:07:24 2022 +0300 + + edited file for linter + +commit d5dc654ca2c93feddf601f74a3abec0b8b4807f8 +Author: Snapman7 +Date: Sat Jun 25 16:41:44 2022 +0300 + + replaced all " by ' + +commit 894349e652593c3313499a15c75602b8742b1130 +Author: Snapman7 +Date: Sat Jun 25 16:34:11 2022 +0300 + + added .idea to gitignore + +commit b6d9b20f0041e8c45a4132a686ecf1f34cf5a925 +Author: Snapman7 +Date: Sat Jun 25 16:33:17 2022 +0300 + + Deleted .idea + +commit 3053067abf46331dfc15336744a35cc1c20bf53a +Author: Snapman7 +Date: Sat Jun 25 16:27:23 2022 +0300 + + edited all html and js files, because should be id-class-value. + +commit fedb12b066120d4d2143f8e5b70c8c22b1ac91de +Author: Snapman7 +Date: Sat Jun 25 16:22:06 2022 +0300 + + edited all html and js files, because should be id-class-value. + +commit 0aba80c946d3a1c04eb9e56c3aaf5aa85e8249dd +Author: OldCoachman +Date: Sat Jun 25 15:45:10 2022 +0300 + + completed to write all the mailing lists' methods + +commit 6dd71ccb9a886cee3aef77f138e7690abb6ab114 +Author: OldCoachman +Date: Sat Jun 25 14:34:25 2022 +0300 + + updated mailing list methods + +commit 5866fe9dec3a86089270ad3fda037f86b45bef26 +Merge: 925d23b cfa59ee +Author: OldCoachman +Date: Sat Jun 25 10:57:40 2022 +0300 + + merged back_debelopment + +commit cfa59eefc9fe89567654715a84ebffd279cd8d57 +Author: FK12344321 +Date: Sat Jun 25 10:50:52 2022 +0300 + + fixed some bugs in docker-compose + +commit 925d23bc077bc7347257b2f13e89b49d5f3d2092 +Author: OldCoachman +Date: Sat Jun 25 10:47:11 2022 +0300 + + commit + +commit 4905b54316ca9048716f39b0cec4fa348de88711 +Author: OldCoachman +Date: Fri Jun 24 18:42:49 2022 +0300 + + added methods for mailing lists + +commit b3070b6bcbd98a0e73281567bd9db039c0b9053c +Merge: 32a891a 855615e +Author: OldCoachman +Date: Fri Jun 24 17:17:06 2022 +0300 + + Merge branch 'back_development' of https://github.com/InnoSWP/CrosslinkTimetable into back_development + +commit a7ced7dbf4c53fb529eb9a81aecf469bf4281659 +Author: OldCoachman +Date: Fri Jun 24 17:16:00 2022 +0300 + + added api methods + +commit 02d124241f721490cd1a58e96da7c0d3b8174c94 +Author: OldCoachman +Date: Thu Jun 23 20:13:39 2022 +0300 + + added pages for mailing list methods + +commit 62834d3f6a7c4fd0e02403c36b897bfe2a584300 +Merge: 22d14b6 32a891a +Author: OldCoachman +Date: Thu Jun 23 20:13:18 2022 +0300 + + Merge branch 'back_development' into EventFrontend + +commit 855615e1ffda1d8049c8c1675dde239f11c68e57 +Author: FK12344321 +Date: Thu Jun 23 20:00:50 2022 +0300 + + Finished dockerization + +commit 32a891a0c35600269336020bbcb966559033c479 +Author: OldCoachman +Date: Thu Jun 23 16:36:47 2022 +0300 + + set up the service + +commit 22d14b65eef11ac820f296d4a9ec3804c04fe7e0 +Author: OldCoachman +Date: Thu Jun 23 15:25:10 2022 +0300 + + created separate pages for editing mailing lists and events + +commit e8efbbc2391ae1f652f377234948b7835820e462 +Merge: e682f7e 9c91ab7 +Author: FK12344321 +Date: Thu Jun 23 14:39:24 2022 +0300 + + merged with main + +commit 9c91ab7cb392c1ffa759f2b7f62f8f8e13bc85d6 +Author: FK12344321 +Date: Thu Jun 23 14:35:35 2022 +0300 + + Dockerize the whole project + +commit 6aa34bec5c6fd002ef79649d2251287947a35f5c +Author: FK12344321 +Date: Thu Jun 23 14:33:42 2022 +0300 + + dockerize the project + +commit db76a303d12cc3847898e6543627559faf6111c9 +Author: FK12344321 +Date: Thu Jun 23 13:45:09 2022 +0300 + + Implemented view controller in backend + +commit f0058c857bcd8ade1eea12e10199d277839a93f2 +Author: OldCoachman +Date: Thu Jun 23 11:58:34 2022 +0300 + + updated POST mailing list + +commit ef9f272b07558b082b9c42f26128529cf3a96d3b +Author: OldCoachman +Date: Thu Jun 23 11:55:32 2022 +0300 + + added POST mailing list + +commit 5ee6a7c9fe531fcdf8217264ad81df7941bd5945 +Merge: 739ca83 007b142 +Author: FK12344321 +Date: Wed Jun 22 22:42:53 2022 +0300 + + Merge branch 'main' of https://github.com/InnoSWP/CrosslinkTimetable + Connection of frontend and backend + +commit 739ca831911d7a6708dee7a7dc49ed5627bbc69f +Author: FK12344321 +Date: Wed Jun 22 22:39:42 2022 +0300 + + Connected frontend and backend + +commit 007b1420458a8f0be4bb930326527aef8393921e +Author: Renat +Date: Wed Jun 22 12:52:59 2022 +0300 + + POST methods (#10) + + * Update - Event add frontend done - @pptx704 + + * Patch - Changed pop-up color to tinted green and made all frontend dependencies offline - @pptx704 + + * Added button description and link to import mailing list - @Snapman7 + + * Connected event add frontend to backend - @OldCoachman + + * Replaced all var on let - @Snapman7 + + * Replaced all var on let and added events POST method - @Snapman7 + + * Fixed details button - @Snapman7 + + * Bug fixes on script.js - @pptx704 + + Checked and Bugfixes by - @pptx704 + +commit 9ac9afbfb2c4e9d35e09c61950d4cabb81c25679 (origin/new_front) +Merge: f0efe81 4a98a81 +Author: Rafeed <57295797+pptx704@users.noreply.github.com> +Date: Wed Jun 22 12:48:57 2022 +0300 + + Merge branch 'main' into EventFrontend + +commit f0efe81bfe94255524f5ba9e4d732af34be8aa23 +Author: Md Motasim Bhuiyan +Date: Wed Jun 22 12:43:52 2022 +0300 + + Bug fixes on script.js + +commit 4a98a814082ad5af9c46dd0b3c4fcbf14f4e6a72 +Author: Rafeed <57295797+pptx704@users.noreply.github.com> +Date: Wed Jun 22 12:28:05 2022 +0300 + + Backend Implementation (#6) + + Implemented all fundumental features of the API + Now user can: + - create, update, delete events + - create, update, delete mailing lists + - import mailing lists + - invite students from mailing lists to the created events + + Co-authored-by: FK12344321 + Co-authored-by: FK12344321 <69464701+FK12344321@users.noreply.github.com> + +commit e682f7e125fbecee40932031233106ac485b082b +Merge: 62d37c8 dafea19 +Author: FK12344321 +Date: Wed Jun 22 12:19:25 2022 +0300 + + Merge branch 'back_development' of https://github.com/InnoSWP/CrosslinkTimetable into back_development + back_development and origin/back_development are the same branches + +commit 62d37c810072c7e770dabbb8cc0607dd00f10d78 +Author: FK12344321 +Date: Wed Jun 22 12:17:30 2022 +0300 + + Finished the implementation of the project + +commit 9afbc60273aab9c6b3fee0cb3acfdc7191e48fb6 +Author: Snapman7 +Date: Wed Jun 22 11:37:49 2022 +0300 + + fixed details button + +commit be88284f4229f4420eb4cb4e284d8946e6ffc017 +Author: Snapman7 +Date: Wed Jun 22 11:29:02 2022 +0300 + + replaced all var on let and added events POST method + +commit 2c97a3e1fdc11469ae24586db421e643cf62784d +Author: Snapman7 +Date: Wed Jun 22 10:25:12 2022 +0300 + + replaced all var on let + +commit dafea194a239b022e5f75ce300d9ab65331f972a +Author: FK12344321 <69464701+FK12344321@users.noreply.github.com> +Date: Tue Jun 21 22:12:12 2022 +0300 + + Delete src/backend/TimetableAPI directory + + deleted deprecated project implementation + +commit 3f0d4f0c711af0e07594248f275318f504a822d7 +Author: Snapman7 +Date: Tue Jun 21 21:11:48 2022 +0300 + + added button description and link to import mailing list + +commit f2dc99f87f12a6eb6ddc14031b6b573563886ae3 +Author: Renat +Date: Mon Jun 20 15:15:52 2022 +0300 + + Created linter.yml (#9) + + Linter added. Approved. + +commit 603ea22db9db649f02164d96e43af6ee25408463 +Author: Rafeed <57295797+pptx704@users.noreply.github.com> +Date: Sat Jun 18 15:17:55 2022 +0300 + + Update - Event add frontend complete + + * Update - Event add frontend done + + * Patch - Changed pop-up color to tinted green and made all frontend dependencies offline + +commit 3765740489615c6febd3a4a6c71d809f30bad22a +Author: FK12344321 +Date: Sat Jun 18 15:15:39 2022 +0300 + + Backend implementation + +commit de0c2f3232d6820c21f0c60928bbf4b3d091f588 +Author: Md Motasim Bhuiyan +Date: Sat Jun 18 15:15:02 2022 +0300 + + Patch - Changed pop-up color to tinted green and made all frontend dependencies offline + +commit eef34672d1f3b31515cf29440928e17f159b19e3 +Author: FK12344321 +Date: Thu Jun 16 18:47:34 2022 +0300 + + Implemented Mailing list + +commit 473ebb0299eb1ad1580a839fd3250de655e4ad43 +Author: FK12344321 +Date: Wed Jun 15 18:18:20 2022 +0300 + + Basic functionality + +commit 090287c6a0f61798bd64d24844843b4dd8bd67b4 +Author: FK12344321 +Date: Tue Jun 14 14:17:17 2022 +0300 + + blank project + +commit fd51257f7deb59783b6d8fdc8595d69fdf6978a9 +Author: Md Motasim Bhuiyan +Date: Sun Jun 12 20:35:37 2022 +0300 + + Update - Event add frontend done + +commit 0fe3baf80428947440497ab9ec09b40d63013350 +Author: Md Motasim Bhuiyan +Date: Wed Jun 8 18:09:22 2022 +0300 + + Project code template + +commit f4c2cb489c886444cf2777572b0682d88b291b84 +Author: Md Motasim Bhuiyan +Date: Wed Jun 8 18:05:44 2022 +0300 + + Git process + +commit 2ea1a9cb91e54839e11e8f27f1e65814b120c5af +Author: Rafeed <57295797+pptx704@users.noreply.github.com> +Date: Mon May 30 19:20:06 2022 +0300 + + Initial commit