Commit 8c4ac477 authored by Florent Chehab's avatar Florent Chehab

v1.0.0

parents
.idea
*.iml
out
target/
oracle.env
v1.0.0 2019-05-07 Release initiale (tout semble fonctionner)
\ No newline at end of file
FROM maven:3.6.0-jdk-11-slim as build
WORKDIR /usr/src/app
# Un peu d'optimization du dockerfile pour éviter de
# télécharger les dépendances à cahque fois.
COPY pom.xml pom.xml
COPY src/lib src/lib
RUN mvn clean
RUN mvn dependency:go-offline --fail-never
COPY src src
RUN mvn package
FROM openjdk:11-jre-slim
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/target/*.jar app.jar
COPY db db
CMD java -jar app.jar
BSD 2-Clause License
Copyright (c) 2019, Florent Chehab
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
dev:
docker-compose -f docker-compose.dev.yml up --build
prod:
docker-compose -f docker-compose.prod.yml up -d
API DB UTC
=========
Ce répo contient une application Java permettant d'exposer des webservices sur la base de données de l'UTC.
## Technos
- Spring Boot,
- Hibernate,
- Swagger,
- etc.
## Utilisation
Le détail des webservices mis à disposition est visible depuis l'interface de Swagger, sur la page http://localhost:8080/swagger-ui.html#/. (si l'application est lancée en mode développement)
----
L'application possède deux modes de fonctionnement:
- En vue d'une connexion avec la BDD oracle de l'UTC,
- En vue d'une connexion avec une base de données en mémoire H2 préchargée avec des données (pour un usage local/en développement).
La distinction entre les deux se fait uniquement selon un profil Spring, qui se choisit avec une simple variable d'environnement.
### En production (avec Oracle)
Copier le contenu du fichier `oracle.env.example` dans un fichier `oracle.env` et modifier les variables appropriées (ne touchez pas au profil). Puis lancer le conteneur Docker:
***Attention le fichier `docker-compose.prod.yml` utilisait dans ce cas expose le port `8080` du conteneur sur le port `8080` de la machine hôte.***
```bash
make prod
```
### En développement (avec H2)
(Rien de spécial n’est à faire)
```bash
make dev
```
*Le port `8080` du conteneur est également répliqué.*
En mode développement, des données exemples sont chargés dans la BDD H2. Vous pouvez changer ces données en mappant un volume dans le fichier `docker-compose.dev.yml` (voir exemple dans ce fichier).
\ No newline at end of file
DROP TABLE IF EXISTS
public.REX_ETAB_PARTENAIRES,
public.REX_DESTINATIONS_OUVERTES,
public.REX_ECHANGES_REALISES,
public.REX_ENSEIGNEMENTS
CASCADE;
CREATE TABLE public.REX_ETAB_PARTENAIRES
(
ID_ETAB BIGINT PRIMARY KEY,
NOM_ETAB VARCHAR(80),
ADR1 VARCHAR(100),
ADR2 VARCHAR(100),
CODE_POSTAL VARCHAR(40),
VILLE VARCHAR(40),
PAYS VARCHAR(50),
CODE_ISO VARCHAR(2)
);
CREATE TABLE public.REX_DESTINATIONS_OUVERTES
(
ID BIGSERIAL PRIMARY KEY,
ID_ETAB BIGINT references public.REX_ETAB_PARTENAIRES (ID_ETAB),
SEMESTRE VARCHAR(5),
COMMENTAIRE VARCHAR(500),
NOMBRE_PLACES BIGINT,
DOUBLE_DIPLOME VARCHAR(1),
MASTER VARCHAR(1),
BRANCHES VARCHAR(40)
);
CREATE TABLE public.REX_ECHANGES_REALISES
(
ID_DEPART BIGINT PRIMARY KEY,
ID_ETAB BIGINT references public.REX_ETAB_PARTENAIRES (ID_ETAB),
SEMESTRE_DEPART VARCHAR(5),
DUREE BIGINT,
DOUBLE_DIPLOME VARCHAR(1),
MASTER VARCHAR(1),
DIPLOME VARCHAR(20),
SPECIALITE VARCHAR(7),
"OPTION" VARCHAR(7),
AUTORISATION_TRANSFERT_UV VARCHAR(1),
AUTORISATION_TRANSFERT_LOGIN VARCHAR(1)
);
CREATE TABLE public.REX_ENSEIGNEMENTS
(
ID_ENSEIG BIGINT PRIMARY KEY,
ID_DEPART BIGINT references public.REX_ECHANGES_REALISES (ID_DEPART),
CODE VARCHAR(10),
TITRE VARCHAR(200),
LIEN VARCHAR(500),
ECTS NUMERIC,
CATEGORIE VARCHAR(5),
PROFIL VARCHAR(10),
CATEGORIE_TSH VARCHAR(21),
"LOGIN" VARCHAR(8)
);
CREATE VIEW public.VW_REX_ETAB_PARTENAIRES AS
SELECT *
FROM public.REX_ETAB_PARTENAIRES;
CREATE VIEW public.VW_REX_DESTINATIONS_OUVERTES AS
SELECT ID_ETAB, SEMESTRE, COMMENTAIRE, NOMBRE_PLACES, DOUBLE_DIPLOME, MASTER, BRANCHES
FROM public.REX_DESTINATIONS_OUVERTES;
CREATE VIEW VW_REX_ECHANGES_REALISES AS
SELECT *
FROM public.REX_ECHANGES_REALISES;
CREATE VIEW VW_REX_ENSEIGNEMENTS AS
SELECT ID_ENSEIG,
ID_DEPART,
CODE,
TITRE,
LIEN,
ECTS,
CATEGORIE,
PROFIL,
CATEGORIE_TSH,
"LOGIN"
FROM public.REX_ENSEIGNEMENTS;
This diff is collapsed.
version: "3.6"
services:
api:
build: .
env_file: h2.env
ports:
- 8080:8080
volumes:
- ./db:/usr/src/app/db:ro # Chargement des données SQL exemples
version: "3.6"
services:
api:
build: .
env_file: oracle.env
ports:
- 8080:8080
# Ne pas modifier celui-ci
PROFILE=h2
# Ne pas modifier celui-ci
PROFILE=oracle
# veiller à spécifier les variables ci-dessous
ORACLE_USER=utilisateur
ORACLE_PASSWORD=mot_de_passe
ORACLE_HOST=hôte:port:SID
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>utc.fr</groupId>
<artifactId>db-connector</artifactId>
<version>1.0.0</version>
<name>utc-db-connector</name>
<description>Mise à disposition de webservices sur la BDD de l'UTC</description>
<properties>
<java.version>8</java.version>
<swagger.version>2.9.2</swagger.version>
<oracle-driver.version>18.3.0.0.0</oracle-driver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<!-- Swagger pour documenter l'api -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--
Pour se simplifier la vie, dans le jar final viendra avec le driver postgres et celui d'oracle.
Le choix de la BDD se fait par les profils springs et donc par une variable d'env.
-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<!-- Installer dans le .m2 local avec le plugin dessous -->
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>${oracle-driver.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--Oracle lib-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>install-oracle-jdbc</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>clean</phase>
<configuration>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>${oracle-driver.version}</version>
<packaging>jar</packaging>
<generatePom>true</generatePom>
<createChecksum>true</createChecksum>
<file>${project.basedir}/src/lib/ojdbc8.jar</file>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package utc.fr.connector;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import utc.fr.connector.course.Course;
import utc.fr.connector.course.CourseRepository;
import utc.fr.connector.destination.OpenedDestination;
import utc.fr.connector.destination.OpenedDestinationRepository;
import utc.fr.connector.exchange.Exchange;
import utc.fr.connector.exchange.ExchangeRepository;
import utc.fr.connector.university.University;
import utc.fr.connector.university.UniversityRepository;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/")
public class Api {
private final UniversityRepository universityRepository;
private final OpenedDestinationRepository openedDestinationRepository;
private final ExchangeRepository exchangeRepository;
private final CourseRepository courseRepository;
@Autowired
public Api(CourseRepository courseRepository, UniversityRepository universityRepository, ExchangeRepository exchangeRepository, OpenedDestinationRepository openedDestinationRepository) {
this.courseRepository = courseRepository;
this.universityRepository = universityRepository;
this.exchangeRepository = exchangeRepository;
this.openedDestinationRepository = openedDestinationRepository;
}
@GetMapping(value = "universities")
@ApiOperation(value = "Lister les universités partenaires (paginé)")
public Page<University> getUniversities(@PageableDefault Pageable pageRequest) {
return universityRepository.findAll(pageRequest);
}
@GetMapping(value = "openedDestinations")
@ApiOperation(value = "Lister les destinations ouvertes (paginé)")
public Page<OpenedDestination> getOpenedDestinations(@PageableDefault Pageable pageRequest) {
return openedDestinationRepository.findAll(pageRequest);
}
@GetMapping(value = "courses")
@ApiOperation(value = "Lister les cours suivis à l'étanger (paginé)")
public Page<Course> getCourses(@PageableDefault Pageable pageRequest) {
return courseRepository.findAll(pageRequest);
}
@GetMapping(value = "courses/{login}")
@ApiOperation(value = "Lister les cours suivis à l'étranger par un étudiant")
public List<Course> getCoursesForStudent(@ApiParam(value = "Login UTC de l'étudiant", required = true) @PathVariable String login) {
return courseRepository.findAllByLogin(login);
}
@GetMapping(value = "exchanges")
@ApiOperation(value = "Lister les échanges réalisés (paginé)")
public Page<Exchange> getExchanges(@PageableDefault Pageable pageRequest) {
return exchangeRepository.findAll(pageRequest);
}
@GetMapping(value = "exchanges/{login}")
@ApiOperation(value = "Lister les échanges réalisés par un étudiant")
public List<Exchange> getExchangesForLogin(@ApiParam(value = "Login UTC de l'étudiant", required = true) @PathVariable String login) {
List<Long> exchangesId = courseRepository.findAllByLogin(login)
.stream()
.map(Course::getIdDepart)
.collect(Collectors.toList());
return exchangeRepository.findAllById(exchangesId);
}
}
\ No newline at end of file
package utc.fr.connector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(ConnectorApplication.class, args);
}
}
package utc.fr.connector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket apiDoc() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("utc.fr.connector"))
.paths(PathSelectors.any())
.build();
}
}
package utc.fr.connector.core;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class FrenchStringBooleanConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean value) {
if (value == null) {
return null;
} else {
return value ? "O" : "N";
}
}
@Override
public Boolean convertToEntityAttribute(String value) {
if (value == null) {
return null;
} else {
return "O".equals(value);
}
}
}
\ No newline at end of file
package utc.fr.connector.core;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
/**
* Pour être sûr que tous les identifiants de tables/colonnes sont correctement convertis en CAPS_LOCK
* et snack_case.
* <p>
* Pour corriger les ennuis du à la présence de "OPTION" comme nom de colonne.
*/
public class ToOracleCase extends PhysicalNamingStrategyStandardImpl {
public static final String CAMEL_CASE_REGEX = "([a-z]+)([A-Z]+)";
public static final String SNAKE_CASE_PATTERN = "$1\\_$2";
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
return formatIdentifier(super.toPhysicalCatalogName(name, context));
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
return formatIdentifier(super.toPhysicalSchemaName(name, context));
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
return formatIdentifier(super.toPhysicalTableName(name, context));
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
return formatIdentifier(super.toPhysicalSequenceName(name, context));
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
return formatIdentifier(super.toPhysicalColumnName(name, context));
}
private Identifier formatIdentifier(
Identifier identifier) {
if (identifier != null) {
String name = identifier.getText();
String formattedName = name
.replaceAll(CAMEL_CASE_REGEX, SNAKE_CASE_PATTERN)
.toLowerCase();
return !formattedName.equals(name) ?
Identifier.toIdentifier(formattedName.toUpperCase(), identifier.isQuoted())
:
identifier;
} else {
return null;
}
}
}
package utc.fr.connector.course;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "VW_REX_ENSEIGNEMENTS")
@NoArgsConstructor
@ToString
@Getter
public class Course {
@Id
@Column(name = "id_enseig")
@ApiModelProperty(notes = "Identifiant unique du cours suivi lors d'un départ")
private Long idCours;
@ApiModelProperty(notes = "Identifiant unique du départ associé")
private Long idDepart;
@ApiModelProperty(notes = "Immatriculation locale du cours")
private String code;
@ApiModelProperty(notes = "Intitulé du cours")
private String titre;
@ApiModelProperty(notes = "Lien vers la description du cours")
private String lien;
@ApiModelProperty(notes = "Catégorie du cours: CS, TM, etc.")
private String categorie;
@ApiModelProperty(notes = "Dans quel profil le cours a été associé: PCB, PSF, etc.")
private String profil;
@ApiModelProperty(notes = "Catégorie TSH du cours")
private String categorieTsh;
@Column(name = "\"LOGIN\"")
@ApiModelProperty(notes = "Login UTC de l'étudiant qui a effectué le départ")
private String login;
}
package utc.fr.connector.course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findAllByLogin(String login);
}
\ No newline at end of file
package utc.fr.connector.destination;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import utc.fr.connector.core.FrenchStringBooleanConverter;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "VW_REX_DESTINATIONS_OUVERTES")
@NoArgsConstructor
@ToString
@Getter
public class OpenedDestination {
@Id
@ApiModelProperty(notes = "Identidiant de l'établissement concerné")
private Long idEtab;
@ApiModelProperty(notes = "Semestre de départ")
private String semestre;
@ApiModelProperty(notes = "Commentaire de la DRI")
private String commentaire;
@ApiModelProperty(notes = "Nombre de places disponibles")
private Long nombrePlaces;
@Convert(converter = FrenchStringBooleanConverter.class)
@ApiModelProperty(notes = "Destination pouvant donné lieu à l'obtention d'un double-diplôme")
private Boolean doubleDiplome;
@Convert(converter = FrenchStringBooleanConverter.class)
@ApiModelProperty(notes = "Destination pouvant donné lieu à l'obtention d'un master")
private Boolean master;
@ApiModelProperty(notes = "Branches concernées par la destination")
private String branches;
}
package utc.fr.connector.destination;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OpenedDestinationRepository extends JpaRepository<OpenedDestination, Long> {
}
package utc.fr.connector.exchange;
import io.swagger.annotations.ApiModelProperty;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import utc.fr.connector.core.FrenchStringBooleanConverter;
import javax.persistence.*;
@Entity
@Table(name = "VW_REX_echanges_realises")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@ToString
@Getter
public class Exchange {
@Id
@ApiModelProperty(notes = "Identifiant unique de l'échange")
private Long idDepart;
@ApiModelProperty(notes = "Identifiant de l'établissement dans lequel l'échange a été effectué")
private Long idEtab;
@ApiModelProperty(notes = "Semestre de départ")
private String semestreDepart;
@ApiModelProperty(notes = "Durée de l'échange en semestre(s)")
private Long duree;
@Convert(converter = FrenchStringBooleanConverter.class)
@ApiModelProperty(notes = "Un double-diplôme a-t-il était obtenu lors de l'échange ?")
private Boolean doubleDiplome;
@Convert(converter = FrenchStringBooleanConverter.class)
@ApiModelProperty(notes = "Un master a-t-il était obtenu lors de l'échange ?")
private Boolean master;
@ApiModelProperty(notes = "Master, HuTech, TC ou Branche")
private String diplome;